Threat model: breaker counts only Gitea failures (N8), name index per store version (N9), claims outside the lock and first-error stop (N10), word-boundary name rules and role words (N11), link/claim and name claim (N12), trailing-number display names (N15)

This commit is contained in:
leora 2026-10-10 13:18:23 +08:00
parent 78ea640d75
commit 8a53bc8c03

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,
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
(Gitea unreachable) counts as taken.
taken or reserved name. Handles are lowercase; checks are case-insensitive. Gitea is asked with
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 ==
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.
@ -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,
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`,
`help-desk-3`). Brand words (`leora`, `seamtro`, `newinternet`) and high-risk role words
(`support`, `admin`, `official`, `verified`, `security`, `staff`, `moderator`, `operator`) are
refused anywhere in a handle or name, compounds included (`SupportTeam`, `OfficialBot`). Other
role words (`root`, `system`, `help`, `owner`, `mod`...) only when they are the whole name or a
role plus a generic word (`Help Desk`, `System Team`), so `Root Cause Analyzer` is fine.
`help-desk-3`). Words are matched on word boundaries (spaces, punctuation, CamelCase), and
across adjacent words (`L e o r a`, `No Reply`). Brand words (`leora`, `seamtro`, `newinternet`)
and high-risk role words (`support`, `admin`, `official`, `verified`, `staff`, `moderator`,
`moderation`, `operator`, `noreply`) are refused as a word anywhere (`SupportTeam`,
`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
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.
- 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
@ -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
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.
- Tests: `Abuse.*`; staging: "message flood gets 429", "contact-request flood is capped",
"oversized body refused", "unauthenticated writes refused".
- Card reads: only Gitea itself failing (connection errors, timeouts, 5xx) counts towards the
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