feat(client): real per-document isolation + bilateral connections + deposit guards
The ReadCap filter now enforces on per-entity documents (consumers create one doc per entity, so each has a declared policy — private→owner, protected→owner+ connections, public→all). Isolation is genuinely active, not dormant. - connections.ts (new): a BILATERAL connection registry — a link grants protected read only when BOTH sides have asserted it (each assertion bound to its author). A unilateral/self-declared connection grants nothing (closes the confused-deputy hole). declareConnections is authenticated to the current identity. - inbox.post: `from` is bound to the current identity — a spoofed `from` throws. - discovery.submitToIndex: PUBLIC-ONLY — a governed non-public doc is refused (no protected/private leak into the world-readable index). - docs/simulation.md: documents this as application-level emulated isolation on a shared wallet (not crypto); at NextGraph maturity → real caps, consumer unchanged. 89 tests pass (+10 covering: active protected isolation via bilateral connect, unilateral grants nothing, from-spoof rejected, non-public submit refused). tsc rc=0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -90,11 +90,31 @@ function readBindings(result: unknown): Array<Record<string, { value: string }>>
|
||||
*
|
||||
* Appends `{ from, payload, ts }` into the inbox document via `docs.sparqlUpdate`
|
||||
* (the real injected `ng`). Each deposit is a fresh RDF subject in the inbox
|
||||
* graph, so concurrent deposits don't collide. `from` is optional: pass `null`
|
||||
* for an anonymous deposit; omit it entirely to default to the current user.
|
||||
* graph, so concurrent deposits don't collide.
|
||||
*
|
||||
* `from` is BOUND TO THE CURRENT IDENTITY — it is authenticated, not
|
||||
* caller-supplied. Omit it to stamp the current user; pass `null` to deposit
|
||||
* ANONYMOUSLY (a legitimate choice — "identified if known, anonymous otherwise").
|
||||
* A `from` naming ANOTHER principal is a SPOOF and is REJECTED: in the target the
|
||||
* broker seals the sender from the wallet's own key, so a client cannot forge
|
||||
* another's identity. (At migration this check is redundant — the seal enforces
|
||||
* it — but until then it closes the spoof the shared wallet would otherwise allow.)
|
||||
*/
|
||||
export async function post(targetInbox: Nuri, opts: PostOptions): Promise<void> {
|
||||
const from = opts.from === undefined ? getCurrentUser() : opts.from;
|
||||
const current = getCurrentUser();
|
||||
let from: PrincipalId | null;
|
||||
if (opts.from === undefined) {
|
||||
from = current; // default: stamp the current identity
|
||||
} else if (opts.from === null) {
|
||||
from = null; // explicit anonymous deposit
|
||||
} else if (opts.from === current) {
|
||||
from = opts.from; // identifying as self — allowed
|
||||
} else {
|
||||
throw new Error(
|
||||
"[ng-eventually] inbox.post: `from` must be the current identity or null " +
|
||||
"(anonymous) — depositing as another principal is a spoof.",
|
||||
);
|
||||
}
|
||||
const ts = opts.ts ?? Date.now();
|
||||
const sid = await sessionId();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user