Skip to content

fix(security): harden mail handling against header/terminal injection (0.2.5)#7

Merged
bscott merged 3 commits intomasterfrom
fix/security-hardening-0.2.5
Apr 27, 2026
Merged

fix(security): harden mail handling against header/terminal injection (0.2.5)#7
bscott merged 3 commits intomasterfrom
fix/security-hardening-0.2.5

Conversation

@bscott
Copy link
Copy Markdown
Owner

@bscott bscott commented Apr 27, 2026

Summary

Broad security audit of the codebase surfaced four classes of newly-introduced (concrete) vulnerabilities outside the path-traversal fix in v0.2.3. This PR ships a new internal/safetext package and applies it across SMTP, IMAP, and CLI output to close them.

Severity Issue Fix
HIGH SMTP header injection via Message-ID (copied into In-Reply-To/References) and Subject (ASCII CRLF bypassed RFC 2047 encoding) All header values go through safetext.SanitizeHeaderValue before reaching the DATA stream. Envelope addresses (MAIL FROM / RCPT TO) are rejected outright if they contain CR/LF.
MEDIUM InsecureSkipVerify: true for IMAP+SMTP without enforcing the localhost trust assumption — a tampered config or --config redirect would ship the Bridge password to a remote host Connect() and Send() refuse to dial anything that's not a loopback address.
MEDIUM mail watch --exec passes the template to sh -c. Currently safe (only numeric SeqNum in {}) but latent RCE if extended to email-derived data Email metadata exposed via env vars (PM_MSG_SEQ, PM_MSG_UID, PM_MSG_FROM, PM_MSG_SUBJECT) so users have a safe path forward without string substitution.
MEDIUM ANSI/OSC escape injection — email Subject/From/body/attachment-filename printed verbatim to TTY All email-derived text-mode prints go through safetext.SanitizeForTerminal. JSON output unchanged.

Bumps version to 0.2.5.

Files changed

  • internal/safetext/ — new package with SanitizeHeaderValue and SanitizeForTerminal plus tests
  • internal/smtp/client.go — header sanitization in writeMessage, loopback check in Send, CR/LF rejection on envelope addresses
  • internal/imap/client.go — loopback check in Connect, header sanitization in buildDraftMessage
  • internal/cli/mail.go — terminal sanitization on every email-derived text print sink (list, read, search, watch, thread, attachment download); env-var injection in watch --exec
  • internal/cli/cli.go — version bump to 0.2.5
  • docs/commands.md — document new env vars for mail watch --exec

Test plan

  • go test ./... — full suite passes (new tests: safetext, IMAP loopback rejection, draft header injection; SMTP loopback rejection, header injection, address-list sanitization)
  • go vet ./... clean
  • gofmt -l clean
  • pm-cli version reports 0.2.5
  • Manual smoke test against running Proton Bridge (send/reply/forward, watch with --exec)

What this PR explicitly does NOT change

The audit also surfaced four LOW-severity findings (template YAML pre-parse substitution, password-in-Go-string lifetime, --out accepting arbitrary paths in agent contexts, htmlToText href URL filtering). All four are defense-in-depth rather than concrete exploits and are tracked as separate follow-up issues.

bscott added 3 commits April 27, 2026 08:49
Adds a new internal/safetext package and applies it across SMTP, IMAP,
and CLI output to address a set of vulnerabilities discovered in a
broad security audit of the codebase:

- SMTP header injection (HIGH): Subject, In-Reply-To, References,
  From/To/Cc derived from received emails could carry CR/LF and inject
  arbitrary headers (e.g., Bcc) when replying or forwarding. Subject in
  particular bypassed RFC 2047 encoding when the value was pure ASCII.
  All header values now go through SanitizeHeaderValue, which strips
  CR/LF before they reach the DATA stream. Envelope addresses (MAIL
  FROM / RCPT TO) are now rejected outright if they contain CR/LF.

- TLS misconfiguration (MEDIUM): IMAP and SMTP clients use
  InsecureSkipVerify because Proton Bridge presents a self-signed cert
  on localhost. The localhost trust assumption was never enforced — a
  tampered config file or a --config redirect to a remote host would
  ship the Bridge password to an attacker over a TLS channel with no
  cert verification. Connect/Send now refuse to dial anything that is
  not a loopback address.

- Latent shell injection in mail watch --exec (MEDIUM): the exec
  template is passed to `sh -c`, with `{}` substituted for a numeric
  sequence number. This is currently safe because only a uint32 is
  interpolated, but extending the template to email-derived data would
  make it RCE. Email metadata is now exposed via environment variables
  (PM_MSG_SEQ, PM_MSG_UID, PM_MSG_FROM, PM_MSG_SUBJECT) so users have a
  safe way to reference it without string substitution.

- ANSI escape injection in terminal output (MEDIUM): email Subject,
  From, body, attachment filename, and content type are printed
  verbatim to stdout in mail list/read/search/watch/thread. An
  attacker email can embed ANSI/OSC escape sequences to obscure
  output, spoof terminal hyperlinks (OSC 8), or manipulate the
  terminal clipboard (OSC 52). All email-derived text-mode prints now
  go through SanitizeForTerminal. JSON output is unchanged.

Also bumps version to 0.2.5 and documents the new env vars in
docs/commands.md for `mail watch --exec`.
`bd init` adds export-state/ to .beads/.gitignore and appends a
session-completion checklist to AGENTS.md. No code change.
@bscott bscott merged commit b9666ae into master Apr 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant