policy: reject out-of-range seed boundaries; carry baselines through session-query

Review fixes (ds-review-bot on #623):

- overrideOf (both knobs) rejects a seedLength past the log end before
  slicing: a malformed durable boundary would otherwise empty the
  own-switch slice until the log outgrew it, letting a wide baseline
  shadow a REAL later tightening. Malformed durable metadata fails loud,
  never open.
- The session-query derived index carries the two baseline fields end to
  end: schema columns on both session tables (SESSION_QUERY_SQLITE_SCHEMA
  _VERSION 6 — derived, rebuilds in place), inserts, header selects, the
  candidates CTE, rowHeader, sameHeader, and the cross-source
  assertSessionHeadersCompatible — so a search hit's header keeps the
  child's inherited confinement and conflicting live/persisted baselines
  reject.

Red-first: out-of-range seedLength tests in both policy suites;
baseline round-trip and live/persisted baseline-conflict tests in the
session-query sqlite suite.
This commit is contained in:
kingwl
2026-07-26 23:31:01 +08:00
parent 99f5fab7bc
commit 9aaa4a871f
10 changed files with 98 additions and 11 deletions

View File

@@ -78,7 +78,14 @@ export function sandboxOverrideOf(session: Session): SandboxMode | undefined {
if (!SANDBOX_MODES.includes(baseline as SandboxMode)) {
throw new Error(`session header sandboxMode "${baseline}" is outside the closed mode vocabulary`)
}
const own = effectiveSandboxMode(session.events.slice(session.header.seedLength ?? 0))
// A boundary past the log would make the own-switch slice empty until the
// log grows past it — a wide baseline would then shadow a REAL later
// tightening. Malformed durable metadata fails loud, never fails open.
const seedLength = session.header.seedLength ?? 0
if (seedLength > session.events.length) {
throw new Error(`session header seedLength ${seedLength} exceeds the log length ${session.events.length}`)
}
const own = effectiveSandboxMode(session.events.slice(seedLength))
return own ?? baseline as SandboxMode
}

View File

@@ -218,4 +218,15 @@ describe('delegation inheritance (overrideOf over the header baseline)', () => {
expect(ctx.sandboxPolicy.overrideOf(child)).toBe('read-only')
expect(ctx.sandboxPolicy.resolve({ session: child }).mode).toBe('read-only')
})
it('rejects a seed boundary past the log end instead of silently ignoring own switches', async () => {
const ctx = await mounted()
// A malformed durable seedLength beyond the log would make the own-switch
// slice empty until the log grows past it — a wide baseline would then
// shadow a REAL later tightening. Fail loud at the durable boundary.
const child = inheritedSession('sess-inherit-oob', { sandboxMode: 'danger-full-access', seedLength: 100 })
setSandboxMode(child, 'read-only')
expect(() => ctx.sandboxPolicy.overrideOf(child)).toThrow(/seedLength/)
})
})