Repository navigation
fix(letters): encode the mailto address; fuzz the register check and the letters weekly - #29
Conversation
The register check accepts any address RFC 5322 allows, so a register entry's address may hold '?', '&', '#' or '%'. mailto() wrote it unencoded, so an entry such as x?cc=attacker%40evil.example%2Cy@broker.example.com.au ended the address early and added a cc field: the user's request, with their personal details, would also go to the attacker. Each part of the address around its '@' is now percent-encoded like the subject and body.
register_letters runs an entry file through the register check and, when it passes, composes its letters and checks the eml header block and the mailto fields. letter_text runs the renderer and the line wrapper on any text and width. CI fuzzes each for 5 minutes every Monday, seeded from registry/au and letters/au; Dependabot covers the fuzz crate's lockfile.
Review found that the link check let an address such as x?subject=spoof@broker.example.com.au through on the old construction, since it only counted fields. The fuzz target now decodes the link's address, subject and body and compares them with the letter's. A whole request is usually over the 2,000-character link limit (none of the 161 letters the 81 shipped entries compose gets a link), so the target also checks the link of a short copy of each letter.
The fuzz crate's lock was generated fresh, so it held newer versions of crates the parent lock pins. Seeding it from Cargo.lock means the targets fuzz the dependency versions that ship; it adds only the fuzzing crates.
|
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Complexity | 1 medium |
🟢 Metrics 11 complexity · 0 duplication
Metric Results Complexity 11 Duplication 0
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
Two issues warrant attention before merging. The fuzz dependency set is not clearly reproducibly pinned for clean workflow runs, and the address encoding logic may alter valid addresses containing an '@' within a quoted local part. No uncovered complex files were reported.
Test suggestions
- An address containing query, fragment, percent, or ampersand characters is safely encoded without adding mailto fields.
- The mailto address, subject, and body round-trip through percent decoding to their original values.
- The existing 2,000-character mailto limit remains enforced.
- Register fuzzing parses valid inputs, composes letters, validates EML headers, and checks mailto decoding.
- Letter fuzzing exercises rendering and wrapping across arbitrary text and widths.
- The fuzz workflow runs both targets weekly and on demand and uploads crash artifacts.
- The fuzz crate has a committed lockfile so locked dependency versions are reproducible.
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. The fuzz crate has a committed lockfile so locked dependency versions are reproducible.
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| [package] | ||
| name = "ghostsoap-fuzz" | ||
| version = "0.0.0" |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Commit the fuzz crate's Cargo.lock and ensure the workflow uses it so fuzz dependencies are reproducibly pinned.
TIP React 👍 if this review is right, or reply with what's wrong and how it could be better to help improve the reviewer.



Fixes a mailto injection in the letters, and adds fuzz targets for the register check and the letters.
The fix.
letters::mailtowrote the register entry's address into the link unencoded. The register check accepts any address RFC 5322 allows, including?,&,#and%in the local part, so an entry whose address isx?cc=attacker%40evil.example%2Cy@broker.example.com.aupassesregistry checkand its link opened a draft with a cc toattacker@evil.example: the user's request, with their identity details, would also go to the attacker. A link is only made for a letter short enough for one (2,000 characters): none of the 161 full letters the 80 shipped entries compose fits, but 165 single-lever letters do, so the injection is reachable. Each part of the address around its@is now percent-encoded like the subject and body. The new testa_register_address_cannot_add_fields_to_a_mailto_linkuses that address; it fails on main. No release contains the bug (GhostSoap has none yet); it came withmailtoin #3.Fuzzing.
fuzz/:register_lettersruns an entry file through the register check and, when it passes, composes its letters, checks the eml header block and decodes each mailto link's address, subject and body to check they are exactly the letter's, for the letter and for a short copy of it;letter_textruns the renderer and the line wrapper on any text and width. Own workspace, with a lockfile seeded from the shippedCargo.lockso the targets fuzz the dependency versions that ship (it adds onlyarbitrary,jobserverandlibfuzzer-sys).fuzz.yml: every Monday and on demand, 5 minutes per target on nightly with cargo-fuzz 0.13.2, seeded fromregistry/auandletters/au. A crashing input is kept as a workflow artefact.fuzz/Cargo.lock, and CONTRIBUTING says how to run a target.Checked on Linux on this branch merged with main as of #27:
cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test --locked(104 passed; 102 on Windows before #27), the release build andregistry check --path registry/au(80 entries, 0 errors). Each target ran for 180 seconds without a crash:letter_text2,146,088 inputs andregister_letters1,596,439. A throwaway harness ran the target's decoding check over every shipped entry: all 80 pass and their 64 short-copy links decode exactly, and it rejects the old unencoded link for an added entry whose address isx?subject=spoof@acceleon.com.au.Alcuin approved it with no findings.