docs: describe self-service registration and restate what replaced the old guard
README gains an Admission section covering both routes, the admin commands, and the six new config keys. The author's side leads with Twt-Register and says plainly not to accept this mailbox: doing so makes a client attach an accept token to every later mail, and a record is verified by rebuilding the exact signed bytes, which cannot include one — so every post would be refused. audit.md's "requests-tier mail is structurally invisible" was a stated hardening property, and this change deliberately reverses it. Rewritten as the chain of gates that replaces it, each named with the probe that proves it, and the feed-path property restated: the grammar widened for host-namespaced slugs, so traversal stopped being structurally impossible and became a rule that has to be right. Added the subject-truncation property the rig found. PLAN.md was the design document and is kept as the record of intent, with its status corrected — it still claimed nothing in src/ existed — and a "since implemented" note wherever the shipped behaviour diverges, rather than quietly editing the original intent away.
This commit is contained in:
parent
1171d2157d
commit
4dab1540ac
2 changed files with 53 additions and 5 deletions
7
audit.md
7
audit.md
|
|
@ -20,9 +20,12 @@ The reader of a feed verifies the author's own §5.3 payload signature, rebuilt
|
|||
## Security properties, each with its enforcement point and proof
|
||||
|
||||
- **Feed-line injection is impossible.** Post and repost text is rejected on TAB, LF, CR or U+2028 (post rule 4, `rules.rs`), so a record can never introduce a second line; a text posing as `# smol:mail` or `# smol:repost` publishes only as timestamped post text. Proven by the pentest's injection and poser probes.
|
||||
- **The feed path cannot escape its directory.** Usernames are validated as 1-30 bytes of `[a-z0-9-]` before any filesystem access (`feedstore.rs`), so traversal, encoding, null-byte, unicode and dotfile forms all 404 without touching a file outside `feeds/`. Proven by nine pentest path probes.
|
||||
- **The feed path cannot escape its directory.** A slug is validated before any filesystem access (`feedstore::valid_slug`), and the join is then checked to have stayed inside `feeds/` — so traversal, encoding, null-byte, unicode and dotfile forms all 404 without touching a file outside it. Proven by nine pentest path probes, a reject table and an HTTP property test.
|
||||
|
||||
The grammar widened when remote authors got host-namespaced slugs (`alice@other.org`), and that changed the guard's character rather than just its range: a path could not previously contain a dot, which is *why* traversal was unreachable. Dots are legal now, so `..`, adjacent separators, uppercase, `%`, `/` and empty DNS labels are each refused explicitly. The nick rule (`valid_username`, 1-30 bytes of `[a-z0-9-]`) is deliberately a separate function and was left untouched, because a nick reaches `# nick =` metadata that other twtxt clients parse; a test asserts the two have not been conflated.
|
||||
- **A record sealed to another instance cannot be laundered here.** The payload signature covers `to`; the feed's `smol:host_key` is the only `to` accepted. Proven by the vectors' `invalid_foreign_host` case.
|
||||
- **Requests-tier mail is structurally invisible.** fumi's store splits requests-tier rows into a folder the daemon never lists; unadmitted senders publish nothing, get no feed, and draw no reply. Proven by the pentest's mallory probe.
|
||||
- **An unknown sender is answered only after proving they hold their address.** Self-service registration deliberately replaced the older property that requests-tier mail was structurally invisible — the daemon now reads that folder. What replaces it is a chain of gates, each proven by a pentest probe: no signed `Reply-To` draws nothing; a `Reply-To` naming an address the sender does not hold draws nothing and pins nothing; an address nobody holds draws nothing; a blocked author stays blocked, checked before any network call; and `registration = silent`, the default, drops unknown senders exactly as before. The resolve is what carries this: a signed `Reply-To` proves only that the sender signed that URI, so answering one without resolving the address would let anyone take a feed in someone else's name — and replying to the claimed `smol://` URI would pin the sender's key as verified under that address, permanently blocking the real owner. Outbound volume is bounded by two global hourly caps, a per-sender reply budget, a per-cycle dial budget, and `register_hosts` when the operator does not want the daemon dialling hosts a stranger chose.
|
||||
- **Reply subjects cannot be truncated by their own copy.** A rejection reason is interpolated into the reply's `Subject`, and a frontmatter value carrying a line break makes the whole block malformed, so smolmail fails closed toward display (SPEC.md §5.5) and the reply arrives with no subject at all. Found in the rig after five catalogue entries were wrapped for readability; every reason is single-line now, enforced by a test over the whole enum.
|
||||
- **Forged records are detectable, not trusted.** `smolboid check` exits nonzero on any invalid record; the signature check is the load-bearing mechanism, not the host's say-so. Proven by the tamper probe.
|
||||
- **Reply floods are bounded.** At most 10 error replies per sender per hour, and never twice for the same rejected payload (state ledger plus dedupe ring). Proven by both suites: 15 and ~39 rejections drew at most 10 answers.
|
||||
- **Quotas and retention hold under rapid fire.** The daily cap is never exceeded even by a 40-send burst, and paced sends land exactly on it; FIFO prunes only what the owner directed; hard-limit mode never silently deletes. Proven by the stress suite.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue