feat(client): OFF-by-default document access log, prefixed by active identity
Observability probe for the shared-wallet isolation footgun: on one physical
wallet several virtual identities coexist, and a read must never surface a doc
scoped to another identity. When it does (B reading A's doc), the leak is
invisible in the data — it looks like a normal read. This makes it VISIBLE.
Every real read/write is logged, prefixed by the ACTIVE virtual identity
(getCurrentUser → the account the op is scoped under, NOT the constant shared
physical wallet id). Reads append the row count — a strong leak signal:
[urn:festipod:user:bob] READ did:ng:o:docA (readDoc) → 3 rows
Instrumented at the LOW common point in docs.ts: every read routes through
sparqlQuery, every write through sparqlUpdate, container creation through
docCreate. Callers pass a semantic label (readDoc|readUnion|listMyEntityDocs|
writeEntity|deposit|…) that is a lib-internal probe param, NOT forwarded to the
real `ng` (preserves docs.test.ts exact-forwarding assertions).
OFF by default → one boolean read on the hot path, zero output. On via
configure({ debugAccessLog: true }) or env NG_EVENTUALLY_ACCESS_LOG=1 (no code
change). Polyfill-era; removed at the real multi-store migration.
tsc --noEmit: 0 errors. bun test: 91 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
import type { NgLike, UseShapeLike, PrincipalId } from "./types";
|
||||
import type { RegistrySession } from "./store-registry";
|
||||
import { CapRegistry } from "./caps";
|
||||
import { setAccessLog } from "./access-log";
|
||||
|
||||
/**
|
||||
* Consumer-injected dependencies of the storeRegistry (polyfill-era). The
|
||||
@@ -34,6 +35,13 @@ export interface EventuallyConfig {
|
||||
sharedWallet?: { name: string; secret: string };
|
||||
/** Initial current user; may also be set later via {@link setCurrentUser}. */
|
||||
currentUser?: PrincipalId;
|
||||
/**
|
||||
* Turn on the OFF-by-default document access log (see {@link ./access-log}):
|
||||
* every real read/write is printed, prefixed by the active identity, to
|
||||
* diagnose the shared-wallet isolation leak. Also enablable without a code
|
||||
* change via the env var `NG_EVENTUALLY_ACCESS_LOG=1`. Default: false.
|
||||
*/
|
||||
debugAccessLog?: boolean;
|
||||
/** REAL `@ng-org/web` `init` (lifecycle) — forwarded by the lib's `init()`. */
|
||||
init?: (...args: any[]) => any;
|
||||
/** REAL `@ng-org/orm` `initNg` (ORM signals) — forwarded by the lib's `initNg()`. */
|
||||
@@ -50,6 +58,7 @@ let caps = new CapRegistry();
|
||||
export function configure(c: EventuallyConfig): void {
|
||||
cfg = c;
|
||||
currentUser = c.currentUser ?? null;
|
||||
setAccessLog(c.debugAccessLog ?? false);
|
||||
}
|
||||
|
||||
/** @internal — used by the SDK-shaped wrappers to reach the injected real SDK. */
|
||||
|
||||
Reference in New Issue
Block a user