[object Object]

← back to Ticket System

TK-10971: prove the REFRE pre-gate is load-bearing (canary TK-00001 + mixed-array + hermeticity self-assert) — contrarian fix

13270debd2564fe931a6d959daf2a9a47ee9da0c · 2026-08-30 10:06:53 -0700 · Steve Abrams

Files touched

Diff

commit 13270debd2564fe931a6d959daf2a9a47ee9da0c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 30 10:06:53 2026 -0700

    TK-10971: prove the REFRE pre-gate is load-bearing (canary TK-00001 + mixed-array + hermeticity self-assert) — contrarian fix
---
 lib.test.js | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/lib.test.js b/lib.test.js
index 57d6d07b..3e50ee38 100644
--- a/lib.test.js
+++ b/lib.test.js
@@ -17,6 +17,11 @@ const {
   nextMid, messages, isBroadcast, withLock, EVENTS,
 } = lib;
 
+// Fail LOUD if a future require-order regression lets EVENTS bind to the real
+// store (require before setting TICKET_DATA_DIR) — otherwise the suite would
+// silently write ~/.claude/tickets/events.jsonl instead of the tmpdir.
+assert.ok(EVENTS.startsWith(os.tmpdir()), 'hermetic isolation broken — EVENTS points outside tmpdir');
+
 const iso = (offsetMs = 0) => new Date(Date.now() + offsetMs).toISOString();
 const readEventLines = () => fs.readFileSync(EVENTS, 'utf8').split('\n').filter(Boolean);
 
@@ -97,17 +102,49 @@ test('resolveId resolves numeric, TK-prefixed, and full-slug refs; null on miss'
 });
 
 // ── 5. SECURITY: resolveList rejects the injection class, resolves clean refs ──
-test('resolveList REJECTS the shell-injection class', () => {
-  const id = 'TK-10007-security';
-  append({ ts: iso(), type: 'create', id, title: 'Security', agent: 'a' });
+// The whole point of REFRE is that it must gate the RAW ref BEFORE resolveId —
+// because resolveId ALONE is foolable: parseInt stops at the first non-digit, so
+// "TK-1; rm -rf /" numeric-resolves to a real low-numbered ticket. The real store
+// has legacy TK-1, so this is a live attack surface, not a hypothetical. These
+// tests plant a real idNum===1 ticket to PROVE the pre-gate is load-bearing.
+test('resolveList REJECTS the shell-injection class (with an idNum===1 ticket present)', () => {
+  const id = 'TK-00001-canary'; // idNum === 1 — what "TK-1; rm -rf /" numeric-resolves to
+  append({ ts: iso(), type: 'create', id, title: 'Canary', agent: 'a' });
   const map = tickets();
   const evil = ['TK-1; rm -rf /', 'TK-1 && curl evil', 'TK-1|cat', '../TK-1', 'TK-1\n rm', '1; ls', ''];
   const out = resolveList(evil, map);
-  // None of the malicious refs survive, and whatever DOES survive is IDRE-clean.
-  for (const r of out) assert.ok(IDRE.test(r), `survivor not IDRE-clean: ${r}`);
-  for (const r of out) assert.ok(!/[;&|/\s\\]/.test(r) || r.startsWith('TK-'), `unsafe survivor: ${r}`);
-  // Specifically: the "1; ls" numeric-injection must NOT resolve to a real ticket.
+  // Every injection is dropped even though a real idNum===1 ticket now exists.
   assert.equal(out.length, 0);
+  // And any survivor (there are none here, but see the mixed-array test) is IDRE-clean.
+  for (const r of out) assert.ok(IDRE.test(r), `survivor not IDRE-clean: ${r}`);
+});
+
+test('resolveId ALONE is foolable — the injection numeric-resolves to a real ticket', () => {
+  const id = 'TK-00001-canary';
+  append({ ts: iso(), type: 'create', id, title: 'Canary A1', agent: 'a' });
+  // parseInt("1; rm -rf /") === 1 → resolveId resolves the injection to the real
+  // idNum===1 ticket. This is exactly why a raw resolveId call cannot be trusted
+  // and REFRE must gate FIRST — proven by the pair below.
+  assert.equal(resolveId('TK-1; rm -rf /', tickets()), id);
+});
+
+test('REFRE pre-gate blocks the injection BEFORE resolveId can resolve it', () => {
+  const id = 'TK-00001-canary';
+  append({ ts: iso(), type: 'create', id, title: 'Canary A2', agent: 'a' });
+  // Same store where resolveId WOULD have resolved the injection (test above) —
+  // resolveList drops it, so the load-bearing gate is REFRE, not resolveId.
+  assert.deepEqual(resolveList(['TK-1; rm -rf /'], tickets()), []);
+});
+
+test('mixed array — the clean ref survives, the injection is dropped', () => {
+  const id = 'TK-00001-canary';
+  append({ ts: iso(), type: 'create', id, title: 'Canary A3', agent: 'a' });
+  const map = tickets();
+  // The real attack surface: a valid ref alongside an injection. The clean id
+  // resolves through, the injection is gated out — and the survivor is IDRE-clean.
+  const out = resolveList(['TK-00001', 'TK-1; rm -rf /'], map);
+  assert.deepEqual(out, [id]);
+  for (const r of out) assert.ok(IDRE.test(r), `survivor not IDRE-clean: ${r}`);
 });
 
 test('resolveList rejects a non-array input and returns []', () => {

← 6f55ede9 TK-10971: hermetic test suite for ticket-system core (lib.js  ·  back to Ticket System  ·  auto-data-snapshot: 2026-08-30T17:12:20 (1 data files) — tmp cad49df1 →