Skip to content

Fix forgeable transport-auth verdict; add SECURITY.md (#101) - #103

Merged
asherp merged 1 commit into
stagingfrom
claude/eloquent-volta-t3c4cj
Jun 16, 2026
Merged

Fix forgeable transport-auth verdict; add SECURITY.md (#101)#103
asherp merged 1 commit into
stagingfrom
claude/eloquent-volta-t3c4cj

Conversation

@asherp

@asherp asherp commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Closes #101.

verify_transport_authentication trusted the plaintext Authentication-Results header it selected with .pop() — the bottom-most, sender-injectable header. A sender could forge dmarc=pass for a spoofed From: and set transport_verified = true.

Security fixes (must-fix)

  1. Select the first (top-most) A-R header, stamped by the closest hop (the user's own receiving provider), not the last. Relays prepend trace headers, so a sender-injected A-R sits at the bottom — exactly what .pop() selected.
  2. Gate on authserv-id. An A-R header is honored only when its organizational domain matches the user's provider, derived from the email/IMAP host with a known-provider map (so Gmail's mx.google.com is trusted even though the mailbox is gmail.com). Fails closed for unknown authserv-ids: a sender-forged A-R is rejected even when it's the only one present (defends the "provider added no A-R" case, where selecting "first" alone is insufficient).
  3. SPF ≠ From-authentication. Dropped the spf=pass → verified branch; SPF authenticates the envelope MAIL FROM, not the visible From:. Kept for diagnostics only.

Correctness (should-fix)

  1. Relaxed DMARC alignment. DKIM alignment now compares organizational domains (RFC 7489 relaxed), so a legitimate subdomain signature (From: example.com / header.d: mail.example.com) is no longer a false negative.

The derived trusted-authserv set is threaded through both call sites — the inbox filter and the spam-rescue path (should_rescue_messagemove_nostr_from_folderrescue_nostr_emails_from_spam).

SECURITY.md (new)

Adds the doc requested in the issue: threat model (malicious/compromised provider + header-injection adversary), the npub-rooted trust model (self-asserted addresses; resolve trusted npub → email, never email → key), the three trust states (verified / signed-but-unverified / unsigned), what transport-auth does and does not prove, and the planned SecureJoin-style email-ownership binding (with the request+response-pair caveat).

Tests

10 new unit tests cover the exploit (forged bottom header ignored), the authserv-id gate (forged-only rejected), SPF-only, DMARC pass, relaxed/unaligned DKIM, header-selection skip, and fail-closed behavior.

  • cargo test --lib: 208 passed, 0 failed
  • cargo test --test email_integration: 21 passed
  • No new compiler warnings

Out of scope

The optional "real cryptographic DKIM verification in Rust" item is left for a follow-up.

https://claude.ai/code/session_01PyRdoum6RhwJnVyQnVrEJ3


Generated by Claude Code

verify_transport_authentication trusted the plaintext Authentication-Results
header it selected with .pop() — the bottom-most, sender-injectable header.
A sender could forge `dmarc=pass` for a spoofed From: and set
transport_verified = true.

Security fixes:
- Select the FIRST (top-most) A-R header, stamped by the closest hop (our
  receiving provider), not the last.
- Gate on authserv-id: honor an A-R header only when its organizational
  domain matches the user's provider (derived from email/IMAP host, with a
  known-provider map so Gmail's mx.google.com is trusted; fails closed
  otherwise). A sender-forged A-R with an unknown authserv-id is rejected
  even when it is the only one present.
- SPF no longer counts as From-authentication (it authenticates the envelope
  MAIL FROM, not the visible From:).

Correctness:
- DKIM alignment now uses DMARC relaxed (organizational-domain) matching, so
  legitimate subdomain signatures are not false negatives.

Threads the derived trusted-authserv set through the inbox filter and the
spam-rescue path. Adds unit tests covering the exploit, the authserv-id gate,
SPF-only, relaxed alignment, header selection, and fail-closed behavior.

Adds SECURITY.md documenting the threat model (malicious provider /
header-injection adversary), the npub-rooted trust model, the three trust
states, what transport-auth does and does not prove, and the planned
email-ownership binding.

https://claude.ai/code/session_01PyRdoum6RhwJnVyQnVrEJ3
@asherp
asherp merged commit 7a516d9 into staging Jun 16, 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.

2 participants