Skip to content
Merged

Dev1 #11

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a72fa43
fix: If the target URL is unreachable during the preflight check, the…
canaria-computer Jan 3, 2026
df25b86
impr: Compress PNG image with save
canaria-computer Jan 4, 2026
6f7599c
chore: Add Report.md to .gitignore
canaria-computer Jan 4, 2026
d4c8e8a
feat(appconfig): add email security options for TLS and HTML handling
canaria-computer Jan 4, 2026
1e578e3
docs(appconfig): add email security options to template
canaria-computer Jan 4, 2026
40d26ce
refactor(cmd/email): add security options for TLS verification and un…
canaria-computer Jan 4, 2026
2c01902
docs: add security options for TLS verification and unsafe HTML handling
canaria-computer Jan 4, 2026
aae2007
fix: Revise the contents of the warning.
canaria-computer Jan 4, 2026
9819d8b
feat(email): implement email message building from evidence reports
canaria-computer Jan 4, 2026
0dc6861
fix(email): correct import order and add missing math/rand import
canaria-computer Jan 4, 2026
92778e4
refactor(cmd/email): integrate BuildEmailMessage function from intern…
canaria-computer Jan 4, 2026
8d4b1ac
fix(email): use correct email type from go-simple-mail
canaria-computer Jan 4, 2026
f9807e2
fix(cmd/email): correct email type references and message API calls
canaria-computer Jan 4, 2026
088c226
feat: Add functionality to build email messages from report data, inc…
canaria-computer Jan 4, 2026
6b7f8c5
feat: Introduce email command for drafting and sending evidence repor…
canaria-computer Jan 5, 2026
8ef3f5d
feat(email): refactor email message building and enhance report conte…
canaria-computer Jan 6, 2026
32fdfb5
fix(cmd/email): handle error when displaying command help
canaria-computer Jan 6, 2026
061b019
fix(cmd/lite): improve error handling during browser cleanup
canaria-computer Jan 6, 2026
05278e1
fix(cmd/lite): improve error handling during screenshot saving
canaria-computer Jan 6, 2026
7dff459
refactor(config): clean up code formatting and improve isValidDirecto…
canaria-computer Jan 6, 2026
3ee36f5
refactor(appconfig): replace strings.Replace with strings.ReplaceAll …
canaria-computer Jan 6, 2026
1dc9979
refactor(cmd/lite): simplify condition in isPublicIP function for cla…
canaria-computer Jan 6, 2026
441ed57
fix(cmd/lite): standardize error message formatting in performPreflig…
canaria-computer Jan 6, 2026
13ab863
fix(cmd/init): standardize error message casing in runInteractiveForm…
canaria-computer Jan 6, 2026
47d97ec
refactor(utils/reachability): improve response body closure handling …
canaria-computer Jan 6, 2026
1d7feeb
refactor(utils/access_info): improve response body closure handling w…
canaria-computer Jan 6, 2026
75dac0e
refactor(report): enhance error handling during report file closure
canaria-computer Jan 6, 2026
c45a4f3
refactor(network/reachability): enhance error handling for connection…
canaria-computer Jan 8, 2026
7452c69
refactor(email/message): enhance error handling for evidence file clo…
canaria-computer Jan 8, 2026
df45689
refactor(email/message): improve screenshot retrieval logic and error…
canaria-computer Jan 8, 2026
21b1d9c
feat(utils/obfuscator): URL and domain obfuscation tool
canaria-computer Jan 8, 2026
30b1e81
refactor(email/message): update evidence handling and improve screens…
canaria-computer Jan 8, 2026
5e855b6
refactor(evidence): create evidence snapshot with report_id if not pr…
canaria-computer Jan 8, 2026
66c33cc
fix(email/message): correct screenshot attachment logic and improve w…
canaria-computer Jan 10, 2026
04b0c3c
feat: add IMAP draft upload functionality
canaria-computer Jan 10, 2026
eece4ee
feat: add global debug mode with --debug/-d flag
canaria-computer Jan 10, 2026
db1f435
feat(email): add support for placeholders in From and Reply-To email …
canaria-computer Jan 10, 2026
6dec51f
fix(doc/email-imap): update recommended encryption methods and remove…
canaria-computer Jan 10, 2026
e13ba59
Refactor report handling and email template placeholders
canaria-computer Jan 10, 2026
7358e54
docs(email-imap): expand explanation of IMAP draft saving feature and…
canaria-computer Jan 10, 2026
a923f01
refactor: remove unused imports and clean up code formatting
canaria-computer Jan 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,7 @@ $RECYCLE.BIN/

# Application generated files
evidence.yaml
screenshot.*
screenshot.*
Report.md
report.eml
evidence.snapshot.yaml
240 changes: 208 additions & 32 deletions cmd/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,25 @@ import (
"sort"
"strings"

"github.com/canaria-computer/down-force/internal/appconfig"
"github.com/canaria-computer/down-force/internal/email"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/log"
"github.com/spf13/cobra"
mail "github.com/xhit/go-simple-mail/v2"
)

var (
// Persistent flags for email command
emailReportDir string

// Security flags for email command
emailIgnoreTLSErrors bool
emailAllowUnsafeHTML bool

// IMAP timeout override flag
emailIMAPTimeout int

// Flags for draft subcommand
emailDraftUpload bool
emailDraftExport bool
Expand All @@ -44,23 +55,39 @@ Available subcommands:
draft Create email drafts (upload to IMAP, export .eml, or preview)
send-with-confirm Send email after user confirmation

Security Options:
--ignore-tls-errors Disable TLS certificate verification (development only)
--allow-unsafe-html Allow unsafe HTML embedding in emails (trusted sources only)

These options should only be used in development/testing environments with
self-signed certificates or controlled HTML sources. Production deployments
should use the secure defaults.

Examples:
# Preview email draft from latest report
down-force email draft --preview

# Upload draft to IMAP server
down-force email draft --upload
# Upload draft to IMAP server with TLS verification disabled (dev only)
down-force email draft --upload --ignore-tls-errors

# Export draft as .eml file
down-force email draft --export
# Export draft as .eml file with unsafe HTML allowed (trusted source only)
down-force email draft --export --allow-unsafe-html

# Send email with confirmation prompt
down-force email send-with-confirm

# Use specific report directory
down-force email draft --preview --report report-20240101-120000`,
down-force email draft --preview --report report-20240101-120000

Configuration Precedence:
1. CLI flags (--ignore-tls-errors, --allow-unsafe-html)
2. Environment variables (DOWNFORCE_EMAIL_SECURITY_IGNORE_TLS_ERRORS, etc.)
3. Configuration file (~/.config/down-force/config.yaml)
4. Defaults (secure by default)`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
if err := cmd.Help(); err != nil {
log.Fatal("Failed to display help: %v", err)
}
},
}

Expand All @@ -75,6 +102,7 @@ The draft command generates a properly formatted email with:
- HTML and plain-text body with report summary
- Attachments (screenshots, HTML files, evidence data)
- Proper MIME encoding and headers
- Security headers (DKIM, SPF compatibility)

Output modes:
--upload Upload draft to IMAP server's Drafts folder
Expand All @@ -87,6 +115,12 @@ If no mode is specified, --preview is used by default.
The command automatically detects the latest report directory (report-*)
in the current working directory. Use --report to specify a different report.

Security Options:
--ignore-tls-errors Skip TLS certificate verification (SMTP/IMAP)
WARNING: Only use in development with self-signed certs
--allow-unsafe-html Disable HTML sanitization in email body
WARNING: Only use with trusted HTML sources

Examples:
# Preview draft from latest report
down-force email draft
Expand All @@ -104,8 +138,12 @@ Examples:
# Use specific report directory
down-force email draft --preview --report report-20240101-120000

# Development: disable TLS verification for self-signed certificates
down-force email draft --upload --ignore-tls-errors

Configuration:
IMAP/SMTP credentials should be configured via environment variables or config file.`,
IMAP/SMTP credentials configured via environment variables or config file.
Use 'down-force config email' to set up email credentials.`,
Run: runEmailDraft,
}

Expand All @@ -125,6 +163,12 @@ This command:
The Message-ID header is automatically generated by default for email tracking and threading.
Use --no-set-message-id to skip Message-ID generation and let the mail server assign one.

Security Options:
--ignore-tls-errors Disable TLS certificate verification (SMTP)
WARNING: Only use in development with self-signed certs
--allow-unsafe-html Disable HTML sanitization in email body
WARNING: Only use with trusted HTML sources

Examples:
# Send email with confirmation (default behavior)
down-force email send-with-confirm
Expand All @@ -135,13 +179,18 @@ Examples:
# Send from specific report directory
down-force email send-with-confirm --report report-20240101-120000

# Development: disable TLS verification for self-signed certificates
down-force email send-with-confirm --ignore-tls-errors

Configuration:
SMTP server settings must be configured before sending.
Use 'down-force config email' for SMTP setup.

Security:
- Passwords are never logged or displayed
- TLS/SSL encryption is enforced for SMTP connections
- Confirmation prompt prevents accidental sends`,
- TLS/SSL encryption is enforced by default
- Confirmation prompt prevents accidental sends
- Message-ID prevents replay attacks (when enabled)`,
Run: runEmailSend,
}

Expand All @@ -154,6 +203,16 @@ func init() {
emailCmd.PersistentFlags().StringVar(&emailReportDir, "report", "",
"Report directory to use (default: latest report-* directory)")

// Security flags (available to all email subcommands)
emailCmd.PersistentFlags().BoolVar(&emailIgnoreTLSErrors, "ignore-tls-errors", false,
"Disable TLS certificate verification (DEVELOPMENT ONLY - insecure)")
emailCmd.PersistentFlags().BoolVar(&emailAllowUnsafeHTML, "allow-unsafe-html", false,
"Allow unsafe HTML embedding without sanitization (DEVELOPMENT ONLY - XSS risk)")

// IMAP timeout override flag
emailCmd.PersistentFlags().IntVar(&emailIMAPTimeout, "imap-timeout", 0,
"IMAP connection timeout in seconds (default: uses config value or 30s)")

// Draft subcommand flags
emailDraftCmd.Flags().BoolVar(&emailDraftUpload, "upload", false,
"Upload draft to IMAP Drafts folder")
Expand All @@ -170,6 +229,21 @@ func init() {
}

func runEmailDraft(cmd *cobra.Command, args []string) {
// Load application configuration
cfg := appconfig.GetConfig()

// Apply security settings (CLI flags override config file)
effectiveIgnoreTLS := emailIgnoreTLSErrors || cfg.Email.Security.IgnoreTLSErrors
effectiveAllowHTML := emailAllowUnsafeHTML || cfg.Email.Security.AllowUnsafeHTML

// Display security configuration
if effectiveIgnoreTLS {
log.Warn("TLS certificate verification is DISABLED")
}
if effectiveAllowHTML {
log.Warn("Unsafe HTML embedding is ENABLED")
}

// Determine report directory
reportDir, err := getReportDirectory(emailReportDir)
if err != nil {
Expand All @@ -178,12 +252,13 @@ func runEmailDraft(cmd *cobra.Command, args []string) {

log.Infof("Using report directory: %s", reportDir)

// Determine output mode (default to preview if none specified)
// Determine output mode (default to export + upload if none specified)
if !emailDraftUpload && !emailDraftExport && !emailDraftPreview {
emailDraftPreview = true
emailDraftExport = true
emailDraftUpload = true
}

// Validate that only one mode is selected
// Validate that only one mode is selected (unless default behavior)
modeCount := 0
if emailDraftUpload {
modeCount++
Expand All @@ -195,35 +270,126 @@ func runEmailDraft(cmd *cobra.Command, args []string) {
modeCount++
}

if modeCount > 1 {
log.Fatal("Error: Only one output mode can be specified (--upload, --export, or --preview)")
if modeCount > 2 {
log.Fatal("Error: Only one or two output modes can be specified")
}

// Load report content
reportContent, err := email.LoadReportContent(reportDir)
if err != nil {
log.Fatalf("Failed to load report content: %v", err)
}

// Build email message (needed for both export and upload)
includeHTML := !emailDraftPlainText
msg, err := email.BuildEmailMessage(reportContent, includeHTML, emailDraftExport)
if err != nil {
log.Fatalf("Failed to build email message: %v", err)
}

// Execute based on selected mode(s)
if emailDraftExport {
log.Info("Exporting draft as .eml file...")
log.Infof("HTML Sanitization: %v", !effectiveAllowHTML)

if err := saveEmlFile(msg, reportDir); err != nil {
log.Errorf("Failed to save .eml file: %v", err)
} else {
log.Info("Draft exported successfully")
}
}

// Execute based on selected mode
if emailDraftUpload {
log.Info("Uploading draft to IMAP Drafts folder...")
// TODO: Implement IMAP upload
log.Warn("IMAP upload not yet implemented")
} else if emailDraftExport {
log.Info("Exporting draft as .eml file...")
// TODO: Implement .eml export
log.Warn(".eml export not yet implemented")
} else if emailDraftPreview {
log.Infof("TLS Error Handling: %v", effectiveIgnoreTLS)

// Validate IMAP configuration
if cfg.Email.IMAP.URI == "" {
log.Error("IMAP URI is not configured")
log.Info("Please configure IMAP settings using:")
log.Info(" down-force config init")
log.Info("Or set environment variable:")
log.Info(" export DOWNFORCE_EMAIL_IMAP_URI='imap://user:pass@imap.example.com:993'")
return
}

// Connect to IMAP server (authentication credentials only, no placeholders)
client, err := email.ConnectIMAP(cfg.Email.IMAP, effectiveIgnoreTLS, emailIMAPTimeout)
if err != nil {
log.Errorf("Failed to connect to IMAP server: %v", err)
log.Info("Check your IMAP configuration in ~/.config/down-force/config.yaml")
return
}
defer func() {
if err := client.Close(); err != nil {
log.Warnf("Failed to close IMAP connection: %v", err)
}
}()

// Find or create Drafts mailbox
draftFolder, err := email.FindOrCreateDraftMailbox(client, cfg.Email.IMAP.DraftFolder)
if err != nil {
log.Errorf("Failed to find or create Drafts mailbox: %v", err)
return
}

// Upload draft message
if err := email.UploadDraftMessage(client, msg, draftFolder); err != nil {
log.Errorf("Failed to upload draft message: %v", err)
return
}

log.Info("Draft uploaded successfully to IMAP server")

// Logout
if err := client.Logout().Wait(); err != nil {
log.Warnf("IMAP logout warning: %v", err)
}
}

if emailDraftPreview {
log.Info("Displaying email preview...")
// TODO: Implement preview display
log.Warn("Preview display not yet implemented")
log.Infof("HTML Sanitization: %v", !effectiveAllowHTML)
displayEmailPreview(string(reportContent.Body))
}

if emailDraftPlainText {
log.Info("Generating plain-text only format")
log.Info("Generated plain-text only format")
}
}

func saveEmlFile(msg *mail.Email, reportDir string) error {
emlPath := filepath.Join(reportDir, "report.eml")
err := os.WriteFile(emlPath, []byte(msg.GetMessage()), 0600)
if err != nil {
return err
}
log.Info("Draft saved to: " + emlPath)
return nil
}

fmt.Printf("\nReport directory: %s\n", reportDir)
fmt.Println("Email draft functionality will be implemented here.")
func displayEmailPreview(content string) {
out, err := glamour.Render(content, "dark")
if err != nil {
log.Errorf("Failed to render markdown preview: %v", err)
return
}
fmt.Println(out)
}

func runEmailSend(cmd *cobra.Command, args []string) {
// Determine report directory
cfg := appconfig.GetConfig()

effectiveIgnoreTLS := emailIgnoreTLSErrors || cfg.Email.Security.IgnoreTLSErrors
effectiveAllowHTML := emailAllowUnsafeHTML || cfg.Email.Security.AllowUnsafeHTML

if effectiveIgnoreTLS {
log.Warn("TLS certificate verification is DISABLED")
}
if effectiveAllowHTML {
log.Warn("Unsafe HTML embedding is ENABLED")
}

reportDir, err := getReportDirectory(emailReportDir)
if err != nil {
log.Fatalf("Failed to determine report directory: %v", err)
Expand All @@ -237,11 +403,21 @@ func runEmailSend(cmd *cobra.Command, args []string) {
log.Info("Message-ID will be generated for tracking")
}

// TODO: Implement email send with confirmation
log.Warn("Email send functionality not yet implemented")
includeHTML := cfg.Email.MimeType == "multipart/alternative"

reportContent, err := email.LoadReportContent(reportDir)
if err != nil {
log.Fatalf("Failed to load report content: %v", err)
}

fmt.Printf("\nReport directory: %s\n", reportDir)
fmt.Println("Email send-with-confirm functionality will be implemented here.")
msg, err := email.BuildEmailMessage(reportContent, includeHTML, false)
if err != nil {
log.Fatalf("Failed to build email message: %v", err)
}

// TODO: Implement email send with confirmation and security settings
log.Warn("Email send functionality not yet implemented")
_ = msg
}

// getReportDirectory determines which report directory to use
Expand Down
Loading