Summary
A : character in a collection or table name breaks namespace isolation. doc(db, \tenant:${tenantId}`)` — the natural multi-tenant pattern — produces keys that collide with other namespaces and leak into foreign prefix scans. For relational tables, catalog entries can be shadowed. Silent data corruption, no error.
Audit ID: B7 (High) — pre-announcement blocker
Root cause
Document keys are built as string concatenation:
function keyOf(collection: string, id: string): Uint8Array {
return utf8.encode(`${collection}:${id}`);
}
prefixRange assumes collection: is a byte boundary. If collection contains :, boundaries lie.
Name validation (assertUserName in catalog.ts) checks reserved markers but not : or empty string.
Proposed fix
Extend assertUserName (or lens-local validator) to reject:
- Empty name
- Names containing
: (U+003A)
- Optionally other structural chars already reserved (
\0, etc.) — follow existing loud-error style in catalog.ts
Apply at doc() and table() entry points (both call assertUserName today — verify and extend).
Tests
doc(db, "tenant:abc") → throws with clear message.
doc(db, "") → throws.
- Valid names still work; relational + document unaffected.
Acceptance criteria
Related issues
- Independent; lenses only (kernel unchanged).
Agent constraints
- Do not change on-disk key encoding for existing valid names.
- English errors with
libredb: prefix per convention.
Verification
Summary
A
:character in a collection or table name breaks namespace isolation.doc(db, \tenant:${tenantId}`)` — the natural multi-tenant pattern — produces keys that collide with other namespaces and leak into foreign prefix scans. For relational tables, catalog entries can be shadowed. Silent data corruption, no error.Audit ID: B7 (High) — pre-announcement blocker
Root cause
Document keys are built as string concatenation:
prefixRangeassumescollection:is a byte boundary. Ifcollectioncontains:, boundaries lie.Name validation (
assertUserNameincatalog.ts) checks reserved markers but not:or empty string.Proposed fix
Extend
assertUserName(or lens-local validator) to reject::(U+003A)\0, etc.) — follow existing loud-error style incatalog.tsApply at
doc()andtable()entry points (both callassertUserNametoday — verify and extend).Tests
doc(db, "tenant:abc")→ throws with clear message.doc(db, "")→ throws.Acceptance criteria
:and empty names rejected at lens boundary._or encode tenant id only in collection name).src/lens/document.test.ts/catalog.test.ts.bun run gategreen.Related issues
Agent constraints
libredb:prefix per convention.Verification