Files
stack/apps
shaggy 3d348772ad test(gateway): bound the KBN-101-02a child-env guard's claim instead of escalating it
Three review rounds found the same defect in this guard: the value validated
and the value used came from two different reads. Each repair narrowed the
window and the next round reopened it -- a Proxy, then a getter replacing
Object.keys mid-enumeration. The single-entries-array form closes both, and a
fourth round showed it still is not a proof: the helper calls Object.fromEntries,
map and filter after the enumeration, and a getter can replace any of them.

That is not closable here. These cases share a closure with the guard, so an
author who can patch a global can patch the guard. The escalation is what was
wrong -- the previous round added a caller-supplied callback to the one spawn
site purely to make a mutation proof read better, and it had to be reverted.

So the claim is bounded rather than narrowed again:

- Freeze ALLOWED_CHILD_ENV, DECLARABLE_CASE_ENV_NAMES and SHELL_GENERATED_ENV.
  The name list was mutable; a case could push a forbidden name before
  declaring it. Verified: the push now throws and the spawn is still rejected.
- Say what the guard does. It rejects an ambient copy or a stray name passed
  as a declaration, which is the mistake a maintainer makes by accident. The
  comments claimed it closed the accessor gap; they no longer do.
- Correct the bootstrap() comment: main.ts constructs a logger before the
  secret check. The load-bearing claim -- the throw precedes any config read --
  is unchanged.

Document corrections, all found by review of claims written in earlier rounds:

- Withdraw "no second call to any replaceable global stands between the check
  and the use". False, and stated while correcting a different overclaim.
- Both exploits are rejected, but by different assertions. The ambient copy is
  named by the offending-keys message; the Proxy is stopped one assertion
  earlier by isProxy, whose message names no keys.
- The sandbox is created in spawnHermetic before the spawn call, not inside
  the spawn arguments. main.spec.ts:58 cleans up in a finally rather than by
  registration, outside the kbn-02a- prefix the residue count measures.
- The package.json RED does not execute the route yet: the script is absent, so
  the case stops on the missing manifest entry before any child is created.
- Three route cases, not two. Four XDG_*_HOME roots plus XDG_RUNTIME_DIR, not
  "five XDG home roots". Not every failure is behavioral -- the structural
  source guards are counted in the same 83.

RED unchanged: 83 failing / 17 passing (100 cases, 6 files), no Errors row,
zero /tmp/kbn-02a-* residue, fixture control green. Typecheck, lint, Prettier
clean.
2026-08-12 23:52:40 -05:00
..