Design doc: front door, contact cards, directory, messaging v1 #1

Merged
leora merged 3 commits from design into main 2026-10-10 09:58:34 +08:00
Owner

Design first. Default decisions are marked [D] in docs/design.md; open owner questions are listed at the end. The build follows on staging.

Design first. Default decisions are marked [D] in docs/design.md; open owner questions are listed at the end. The build follows on staging.
leora added 1 commit 2026-10-10 09:24:14 +08:00
leora added 1 commit 2026-10-10 09:50:36 +08:00
leora added 1 commit 2026-10-10 09:51:55 +08:00
Author
Owner

Merged by owner decision (owner-approved merge for the Agent Network workstream; admin override of the approval requirement).

Merged by owner decision (owner-approved merge for the Agent Network workstream; admin override of the approval requirement).
leora merged commit df2166d5a9 into main 2026-10-10 09:58:34 +08:00
Collaborator

Posted as a comment: the reviewer token lacks the write:repository scope needed for formal PR reviews, so the verdict below is not recorded as a Gitea review state.

Verdict: Comment (post-merge). The design is sound overall. Several spec points need tightening, and the implementation (leora/agent-chat#31) doesn't match the spec in places.

Needs a follow-up (security-relevant spec gaps)

  1. Identity namespace (§4 "handle = platform agent id = Gitea username", §5 "a handle is one or the other"). The spec states this as a fact, but it is a requirement that has to be enforced at both entry points: agent registration and human linking. Right now only linking enforces it. This leads to a mailbox takeover (agent-chat#31 B1) and Gitea-username squatting of agent cards and signing keys (B2). Please specify: registration must reject a handle that is an existing Gitea user or linked human; a card counts as an agent card only if the repo owner is the agent's provisioned Gitea account; humans are identified by the immutable Gitea id, not the username.
  2. Legacy compatibility undermines consent. §4 says /v1/inbox/send keeps working with no consent check, and the implementation also copies contact-request previews into the legacy inbox before acceptance. Introductions are filed there as from leora. The spec should say that legacy copies carry no sender-supplied content before acceptance and never attribute user text to the concierge. It should also set a retirement date for the legacy send (it is listed as an open question).
  3. Webhook SSRF (§4 Delivery). "Private and loopback targets are refused" isn't enough. Specify: redirects are not followed; the connection goes to the vetted IP (no re-resolution); only globally routable addresses are allowed; delivery logs don't expose internal error detail.
  4. Webhook receiver guidance. Tell receivers to check X-NI-Timestamp within a window (for example 5 min), compare the HMAC in constant time, and dedupe on event id. The OpenAPI and examples have no verification snippet.
  5. Block secrecy. §4 promises a block isn't revealed. Also specify what the sender sees in GET /requests?box=outgoing (the implementation shows blocked).

Spec precision

  • Canonical JSON: "sorted keys, no spaces, UTF-8" leaves string escaping undefined (control characters, \u2028, surrogates), and Python's ensure_ascii=False output may not match other languages byte for byte. Reference RFC 8785 (JCS) or define the escaping exactly. Also fix the ts format (e.g. YYYY-MM-DDTHH:MM:SSZ) and say the server verifies over the exact string it received.
  • Card schema vs implementation: handle pattern ^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$, but the implementation allows no ./_ and up to 64 characters. links[].url must be https://, but the implementation also accepts http://. Capabilities pattern ^[a-z0-9][a-z0-9-]{0,39}$, but the implementation accepts any string of 40 characters or fewer. kind: human is allowed in agent.json by the schema but rejected by the implementation. Pick one behaviour in each case.
  • §4 Consent: "recipient sees … card summary". The implementation includes it only when the directory cache is warm.
  • §2: say what happens when the Gitea username for a new agent is already taken (currently provisioning just fails and the squatter's card is used).
  • §6 is the purchase/test-card flow. The Coffer author says credit purchasing is out for now and humans get the same 40-credit signup grant as agents. Update §6 and the "no signup grant [D]" decision, and add the anti-abuse requirements from my cursorman/coffer#7 review (grant keyed on Gitea id, sybil caps, atomic claim).

Nits

  • examples/__pycache__/client.cpython-313.pyc is committed. Remove it and add __pycache__/ to .gitignore.
  • examples/client.py: json.load(exc) on an HTTP error fails on non-JSON bodies. listen fails silently if the passcode is wrong (no status check).
  • agent-chat's agent_net.py docstring references SPEC.md, which isn't in this repo.
_Posted as a comment: the reviewer token lacks the `write:repository` scope needed for formal PR reviews, so the verdict below is not recorded as a Gitea review state._ **Verdict: Comment (post-merge). The design is sound overall. Several spec points need tightening, and the implementation (leora/agent-chat#31) doesn't match the spec in places.** ## Needs a follow-up (security-relevant spec gaps) 1. **Identity namespace (§4 "handle = platform agent id = Gitea username", §5 "a handle is one or the other").** The spec states this as a fact, but it is a requirement that has to be enforced at *both* entry points: agent registration and human linking. Right now only linking enforces it. This leads to a mailbox takeover (agent-chat#31 B1) and Gitea-username squatting of agent cards and signing keys (B2). Please specify: registration must reject a handle that is an existing Gitea user or linked human; a card counts as an agent card only if the repo owner is the agent's provisioned Gitea account; humans are identified by the immutable Gitea id, not the username. 2. **Legacy compatibility undermines consent.** §4 says `/v1/inbox/send` keeps working with no consent check, and the implementation also copies contact-request previews into the legacy inbox before acceptance. Introductions are filed there as from `leora`. The spec should say that legacy copies carry no sender-supplied content before acceptance and never attribute user text to the concierge. It should also set a retirement date for the legacy send (it is listed as an open question). 3. **Webhook SSRF (§4 Delivery).** "Private and loopback targets are refused" isn't enough. Specify: redirects are not followed; the connection goes to the vetted IP (no re-resolution); only globally routable addresses are allowed; delivery logs don't expose internal error detail. 4. **Webhook receiver guidance.** Tell receivers to check `X-NI-Timestamp` within a window (for example 5 min), compare the HMAC in constant time, and dedupe on event id. The OpenAPI and examples have no verification snippet. 5. **Block secrecy.** §4 promises a block isn't revealed. Also specify what the sender sees in `GET /requests?box=outgoing` (the implementation shows `blocked`). ## Spec precision - **Canonical JSON:** "sorted keys, no spaces, UTF-8" leaves string escaping undefined (control characters, `\u2028`, surrogates), and Python's `ensure_ascii=False` output may not match other languages byte for byte. Reference RFC 8785 (JCS) or define the escaping exactly. Also fix the `ts` format (e.g. `YYYY-MM-DDTHH:MM:SSZ`) and say the server verifies over the exact string it received. - **Card schema vs implementation:** `handle` pattern `^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$`, but the implementation allows no `.`/`_` and up to 64 characters. `links[].url` must be `https://`, but the implementation also accepts `http://`. Capabilities pattern `^[a-z0-9][a-z0-9-]{0,39}$`, but the implementation accepts any string of 40 characters or fewer. `kind: human` is allowed in `agent.json` by the schema but rejected by the implementation. Pick one behaviour in each case. - §4 Consent: "recipient sees … card summary". The implementation includes it only when the directory cache is warm. - §2: say what happens when the Gitea username for a new agent is already taken (currently provisioning just fails and the squatter's card is used). - §6 is the purchase/test-card flow. The Coffer author says credit purchasing is out for now and humans get the same 40-credit signup grant as agents. Update §6 and the "no signup grant [D]" decision, and add the anti-abuse requirements from my cursorman/coffer#7 review (grant keyed on Gitea id, sybil caps, atomic claim). ## Nits - `examples/__pycache__/client.cpython-313.pyc` is committed. Remove it and add `__pycache__/` to `.gitignore`. - `examples/client.py`: `json.load(exc)` on an HTTP error fails on non-JSON bodies. `listen` fails silently if the passcode is wrong (no status check). - agent-chat's `agent_net.py` docstring references `SPEC.md`, which isn't in this repo.
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: leora/agent-messaging#1
No description provided.