diff --git a/docs/nextgraph-current-state.md b/docs/nextgraph-current-state.md index 0d0a2c4..9127d18 100644 --- a/docs/nextgraph-current-state.md +++ b/docs/nextgraph-current-state.md @@ -135,6 +135,27 @@ So a per-document inbox is **not an anticipation**: it is an engine capability t code path exercises automatically and that no level-2 or level-3 API exposes. This lib implements it aligned on the engine's model. +**An inbox address is TRANSMITTED, never published — and nothing in the engine says who +may open one.** Two facts that decide more than they look: + +- `inboxes: HashMap` is a field of the **Verifier** + (`engine/verifier/src/verifier.rs:105`), rebuilt empty on each construction (`:520`, + `:2820`). The inbox → repo association is **local to a session**, not a published + fact. A depositor learns a pubkey because it was **sent** to them — in a + `ContactDetails` message (`contact.inbox`) or through a profile QR code; the reply + path reads its own `repo.inbox` to include it (`request_processor.rs:736-750`). +- There is therefore **no engine guard on who opens an inbox for a repo**. + `AddInboxCap` lands on the committer's OWN User branch, so anyone may write one naming + anyone's repo. It simply reaches nobody: no one was told that pubkey means that + document. + +*Consequence for this lib, and it is a real divergence:* we **publish** the address on +the document (its Header branch) because that is the only way a third party can find it +in an emulation with no message channel. That creates a vector the engine does not have +— whoever can write the document can redirect its deposits — so `openDocumentInbox` +guards on ownership. That guard compensates OUR design; it does not mirror an upstream +rule. Do not cite it as one. + A non-editor can deposit into an inbox without being invited as an editor; the owner moderates. NURI: `did:ng:d:`. Content: the `InboxMsgContent` enum (`ContactDetails`, `DialogRequest`, `Link`, `Patch`, `ServiceRequest`, `ExtRequest`, diff --git a/packages/client/src/store-registry.ts b/packages/client/src/store-registry.ts index e8172fa..b570e2a 100644 --- a/packages/client/src/store-registry.ts +++ b/packages/client/src/store-registry.ts @@ -1139,8 +1139,21 @@ export async function openDocumentInbox(doc: Nuri): Promise { // OWNERSHIP is the criterion, and holding a cap is NOT ownership — a cap can be // received. Opening an inbox is what PUBLISHES this document's address, so a // non-owner doing it would route the owner's deposits to itself, silently, on a - // document it merely reads. Upstream the equivalent act is the owner committing - // `AddInboxCap` with the repo's own key; nobody else can. + // document it merely reads. + // + // **This guard compensates OUR design, not an upstream constraint** — an earlier + // comment here claimed "upstream only the owner can commit `AddInboxCap`", which is + // false: that commit lands on the committer's OWN User branch, so anyone may write + // one naming anyone's repo. What protects upstream is that an inbox address is never + // PUBLISHED — it is TRANSMITTED (in a `ContactDetails` message, or a profile QR + // code), and `inboxes: PubKey → RepoId` is a per-verifier local table + // (`engine/verifier/src/verifier.rs:105`, rebuilt empty each session). A forged pair + // reaches nobody, because nobody was told about it. + // + // We publish instead of transmitting — the only way a third party can find the + // address at all here — which creates a vector upstream does not have: whoever can + // write the document can redirect its deposits. Hence this guard. It is a real + // divergence, deliberately taken; see `docs/briefs/2026-08-03-document-inbox-addressing.md`. if (!(await ownsDocument(doc))) { throw new Error( "[ng-eventually] openDocumentInbox: refused — you may only open an inbox on a document " +