Threat model, launch checklist, and spec for the security fixes #3
@ -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
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user