← 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 →