fix(client): reserve the discovery @index account key (prevent user collision)

The special discovery-index account was keyed as "@index", which the consumer's
normalizeUsername collapses to "index" — colliding with a real user named
"index" (who could then hijack/tamper the global index document). Introduce a
reserved-account namespace (a sentinel key unreachable by any typed username) so
the index account can never collide with user input. Test proves a user named
"index"/"@index" resolves to a DIFFERENT document than the reserved index
account. 80 tests pass; tsc rc=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-07-04 09:58:52 +02:00
parent 9951cd5223
commit 42174608f8
3 changed files with 75 additions and 12 deletions
+7 -5
View File
@@ -40,16 +40,18 @@
*/
import * as inbox from "./inbox";
import { ensureAccount } from "./store-registry";
import { ensureAccount, reservedAccount } from "./store-registry";
import type { Nuri, PrincipalId } from "./types";
/**
* The reserved SPECIAL ACCOUNT that owns the global discovery index in the
* polyfill. A normal shim account (username), so its scope documents are created
* on first sight like any other account — but it is never a real user; it only
* hosts the index document. Disappears at migration (see file header).
* polyfill. It hosts the index document but is never a real user. It lives in
* the registry's RESERVED namespace ({@link reservedAccount}), whose key
* `normalizeUser` can never produce — so a user named "index"/"@index" can NOT
* hijack it (they would normalize to "index", a disjoint key). Disappears at
* migration (see file header).
*/
export const INDEX_ACCOUNT = "@index";
export const INDEX_ACCOUNT = reservedAccount("index");
/** One entry as materialized from the discovery index. */
export interface IndexEntry {