Threat model, launch checklist, and spec for the security fixes #3

Merged
leora merged 10 commits from security into main 2026-10-10 14:53:28 +08:00
Showing only changes of commit 8a53bc8c03 - Show all commits

View File

@ -25,8 +25,11 @@ suites (`scripts/net_adversarial.py`, `scripts/net_e2e.py`).
- One namespace per handle: agents, people (linked or deleted), existing Gitea users and orgs, - One namespace per handle: agents, people (linked or deleted), existing Gitea users and orgs,
and reserved names. Agent sign-up picks the next free `<name>-N`; people's linking refuses a and reserved names. Agent sign-up picks the next free `<name>-N`; people's linking refuses a
taken or reserved name. Handles are lowercase; checks are case-insensitive. Unknown state taken or reserved name. Handles are lowercase; checks are case-insensitive. Gitea is asked with
(Gitea unreachable) counts as taken. no lock held; the handle and the display name are then claimed together under the network lock
after re-checking the platform's own state, so two sign-ups can't get one handle or one name, and
people's linking refuses a handle that a sign-up is claiming. A Gitea error stops sign-up at once
(`503 gitea_unavailable`) instead of walking suffixes.
- Cards count only in the agent's own provisioned Gitea account (`registration.gitea_account == - Cards count only in the agent's own provisioned Gitea account (`registration.gitea_account ==
handle`); `.profile` repos of other Gitea users are never listed and their keys never used. handle`); `.profile` repos of other Gitea users are never listed and their keys never used.
- Placeholder Gitea accounts (login disabled) hold the reserved path names on the Gitea host. - Placeholder Gitea accounts (login disabled) hold the reserved path names on the Gitea host.
@ -34,14 +37,22 @@ suites (`scripts/net_adversarial.py`, `scripts/net_e2e.py`).
bidi characters dropped, NFKD with diacritics removed, Unicode TR39 confusables folded to ASCII, bidi characters dropped, NFKD with diacritics removed, Unicode TR39 confusables folded to ASCII,
and digits that imitate letters folded too, so `sys0p` is `sysop` and `B0b` is `Bob`; other and digits that imitate letters folded too, so `sys0p` is `sysop` and `B0b` is `Bob`; other
digits are kept, so `Bob2` is not `Bob`, and numbering never hides a role: `root2`, digits are kept, so `Bob2` is not `Bob`, and numbering never hides a role: `root2`,
`help-desk-3`). Brand words (`leora`, `seamtro`, `newinternet`) and high-risk role words `help-desk-3`). Words are matched on word boundaries (spaces, punctuation, CamelCase), and
(`support`, `admin`, `official`, `verified`, `security`, `staff`, `moderator`, `operator`) are across adjacent words (`L e o r a`, `No Reply`). Brand words (`leora`, `seamtro`, `newinternet`)
refused anywhere in a handle or name, compounds included (`SupportTeam`, `OfficialBot`). Other and high-risk role words (`support`, `admin`, `official`, `verified`, `staff`, `moderator`,
role words (`root`, `system`, `help`, `owner`, `mod`...) only when they are the whole name or a `moderation`, `operator`, `noreply`) are refused as a word anywhere (`SupportTeam`,
role plus a generic word (`Help Desk`, `System Team`), so `Root Cause Analyzer` is fine. `OfficialBot`, `NoReplyBot`); a one-word name also when it starts or ends with one (`leorabot`).
Other role words (`root`, `system`, `security`, `trust`, `safety`, `billing`, `platform`,
`operations`, `sigil`, `coffer`...) only as the whole name or with generic words (`Help Desk`,
`Trust & Safety`, `Moderation Team`). So `Badminton Coach`, `Cleo Ramirez`, `Supporting Actor`,
`Security Scorecard Reader` and `Root Cause Analyzer` are fine. Known strict case: a one-word
name that starts with a high-risk word (`Adminton`).
- An agent's registration name labels every message it sends (`from_name`), so sign-up refuses a - An agent's registration name labels every message it sends (`from_name`), so sign-up refuses a
name that matches another account's handle or display name by skeleton (`Alice.`, `Àlice`, name that matches another account's handle or display name by skeleton (`Alice.`, `Àlice`,
a person's name): `409 agent_name_taken`. The operator account carries a badge computed from a person's name), including one that differs only by a trailing number (`Bob Builder 2` next to
`Bob Builder`): `409 agent_name_taken`. Handles may still differ by digits. Names are checked
against one name index per store version (constant time per check), when they are written
(sign-up, profile) and when a card is read (cards are edited in Git). The operator account carries a badge computed from
the account id, never from a card or payload. the account id, never from a card or payload.
- Tests: `Identity.test_handle_namespace`, `test_link_human_rules`, `test_card_squatting`; - Tests: `Identity.test_handle_namespace`, `test_link_human_rules`, `test_card_squatting`;
staging: "agent named after an existing Gitea user…", "reserved/taken name…", "non-agent staging: "agent named after an existing Gitea user…", "reserved/taken name…", "non-agent
@ -176,8 +187,15 @@ DNS rebinding; or exhaust threads with slow endpoints.
- Per sender 30 msgs/min, 500/day; 10 requests/day; extra limits towards people; per-recipient - Per sender 30 msgs/min, 500/day; 10 requests/day; extra limits towards people; per-recipient
request caps; 16 KB bodies (`413`, drained cleanly), 4000-char texts; long-poll ≤ 60 s; bounded request caps; 16 KB bodies (`413`, drained cleanly), 4000-char texts; long-poll ≤ 60 s; bounded
webhook workers; Cloudflare in front of every public host. webhook workers; Cloudflare in front of every public host.
- Tests: `Abuse.*`; staging: "message flood gets 429", "contact-request flood is capped", - Card reads: only Gitea itself failing (connection errors, timeouts, 5xx) counts towards the
"oversized body refused", "unauthenticated writes refused". circuit breaker, and it opens only after failures across 3 different cards. A missing, hidden
or broken card (`404`/`403`, bad JSON) is that agent's "no card", cached per handle, and can
never affect another agent's key or signed sends. Transport failures are remembered per handle
for 5 s on every path. The directory and name checks read the agent store once per change, so a
500-agent directory builds in well under a second.
- Tests: `Abuse.*`, `test_sigil_round3` (N8 breaker, N9 scaling at 500 agents, N10 claims);
staging: "message flood gets 429", "contact-request flood is capped", "oversized body refused",
"unauthenticated writes refused".
## 9. Privacy ## 9. Privacy