# Band-2 cross-file spike — does escalation fire + catch a non-payments cross-file IDOR? (2026-06-17)

audience: AI coding agents first. Measured log. Tags [MEASURED] vs [INFERRED]. Do not prettify.

## BLUF

Ran the advisor-gated check: ONE genuinely cross-file canonical, siblings on disk, to see if band-2
escalation fires + connects + catches. Result, two parts — **but the cell I picked is SHAPE A, the easy
shape; the hard shape (Shape B) is still untested.** See "Shape A vs Shape B" below.

1. **Coverage is NOT the hole FOR SHAPE-A (missing-check) cross-file IDOR — band-1 flags it.** [MEASURED] The
   route-only review caught the canonical at [high] 2/3: *"IDOR: user-supplied resource IDs passed to query
   functions without IN-FILE ownership validation."* But this cell is NOT truly undecidable single-file:
   band-1 fired on the IDOR *smell* (raw user FK `project_id` passed onward, no guard visible) — the finding
   text itself says *"in-file."* In a cell with a *visible* (but buggy) guard call, that reasoning would not
   trigger. **So this measures Shape A only.**
2. **Band-2 confirmation does NOT generalize beyond payments — structural.** [MEASURED] Escalation did NOT
   fire (scope stayed = target). Both escalation paths gate on the imported symbol NAME matching the
   payments-flavored `CRITICAL` regex; zync's authz symbols match none of it. (Cell-independent — code-confirmed.)

3. **MEASURED — band-1 is SILENT on Shape B → a REAL coverage hole.** [MEASURED] Shape B = route CALLS an
   imported predicate/builder that LOOKS correct, bug lives INSIDE the imported symbol (fail-open / insecure
   default; the C02 founding shape). Ran the discriminating cell: zync `ead618d~1` `refresh.ts` calls
   `buildSessionPayload({...})` OMITTING `enforce2fa`/`twoFactorVerified`; the builder defaults
   `enforce_2fa: args.enforce2fa ?? false` (insecure default, in `@zync/auth`) → 2FA silently dropped on every
   refresh for a tenant that enforces it. band-1 k=3 union = ~16 findings (rotation TOCTOU, token-cap races,
   origin config, type casts) — **NONE is the canonical.** Oracle SILENT (payments-only), escalation cannot
   fire (`buildSessionPayload` not CRITICAL-named; no sibling on disk) → **silent TOTAL miss.**

**This is the founding failure mode.** band-1 trusts a visible-and-well-formed call and never pulls the imported
symbol to find the insecure default. The bar ("catch ALL in 1 round") **FAILS for Shape-B cross-file.** band-2
is therefore **existential for RECALL**, not just confirmation/precision — which CONFIRMS the band-2 > precision
ranking. The "coverage is not the hole" claim holds ONLY for Shape A (visible missing-check IDOR), NOT Shape B.

4. **MEASURED — the FIX gate: with RESOLUTION (both files in prompt), band-1 catches the canonical 3/3.** [MEASURED]
   Re-ran the SAME Shape-B cell with `refresh.ts` + `session.ts` concatenated into one prompt (manual escalation
   bypass, k=3): band-1 flagged `[critical] 2FA enforcement stripped on every token refresh` at **3/3** (the
   strongest possible signal) — vs **0/3 silent** single-file. It even emitted `[medium] cross-file dependency`
   reasoning unprompted. **Conclusion: RESOLUTION is the bottleneck; omission-DETECTION is NOT.** Once the imported
   builder's body (`enforce_2fa: args.enforce2fa ?? false`) is in scope, the LLM does the call-site-omission diff
   itself — **no separate "diff call-site args vs builder param-set" sub-check is needed.** This narrows band-2 to
   exactly two unbuilt pieces: (1) a RESOLVER that pulls the imported symbol's definition (barrel-following), and
   (2) a TRIGGER that decides WHICH imports to pull. Report: `/tmp/sg_shapeB/refresh_plus_session_report.md`.

Net: Shape-A cross-file IDOR is caught as a *flag* (recall OK); **Shape-B cross-file is a silent miss single-file
(recall FAILS) but a 3/3 catch once the symbol is resolved into scope.** The band-2 work item is therefore exactly
two unbuilt pieces — **(a) a PROACTIVE TRIGGER** (pull the imported symbol regardless of payments keywords, because
Shape B emits no band-1 flag for reactive escalation to react to) and **(b) a barrel-following RESOLVER**. The third
candidate — **(c) a special omission-detection sub-check — is NOT needed: the two-file test proved the LLM does the
call-site-vs-builder diff itself once resolution is provided** (item 4 above). Detection is solved; only delivery
(trigger + resolver) remains.

## The cell — SHAPE A (visible missing-check IDOR) [MEASURED]

| field | value |
|---|---|
| class | cross-tenant FK injection (IDOR family) — non-payments, tenant-isolation |
| fix commit | `16f510f` (zync) "guard task_statuses.project_id FK (cross-tenant injection — final residual)" |
| target (on disk) | `route__task-statuses.ts` = `16f510f~1:apps/zync-api/src/routes/task-statuses.ts` (132 lines, RAW git) |
| sibling (on disk) | `queries/task-statuses.ts` = `16f510f~1:packages/db/src/queries/task-statuses.ts` (294 lines) |
| canonical | route `POST /statuses` passes request `project_id` into imported `createStatus(db, session.tid, {projectId})`; pre-fix `createStatus` never verifies the project belongs to the tenant → a user attaches a task-status to ANOTHER tenant's project |
| why cross-file | the call site looks tenant-scoped (`session.tid` is passed); the missing check lives entirely inside the imported `createStatus`. Undecidable from the route WITHOUT assuming the query is safe. |

## What happened [MEASURED]

- **Escalation fired? NO.** `gate.py` scope log = `[scope] route__task-statuses.ts (depth 0; target)` and nothing
  else. No sibling pulled.
- **Why (root cause, code-confirmed `gate.py`):** both escalation paths require the imported symbol's NAME to match
  the `CRITICAL` regex —
  - proactive pull-in (`gate.py:206` → `critical_imports`): filters imported names by `CRITICAL`.
  - reactive escalation (`gate.py:218-222`): pulls a flagged xfile symbol only if `csym == sym` AND that symbol is a
    `CRITICAL`-named resolvable import (`xfile_symbols` also requires camelCase + `CRITICAL`).
  - The route's imports — `createStatus`, `updateStatus`, `deleteStatus`, `reorderStatuses`, `requirePermission`,
    (post-fix) `assertTenantOwnsProject` — ALL return `CRITICAL.search(name) == False`. The regex is
    payments-flavored (`claim|lock|settle|payout|refund|charge|transfer|release|mediat|reserve|consume|debit|
    credit|ledger|escrow|hold|webhook|idempoten` + generic `authoriz|verif|guard`). zync's tenant-isolation authz
    is named by ROLE (`assertTenantOwns*`) and by MIDDLEWARE (`requirePermission`/`requireAdminSession`), none of
    which contain a payments keyword → the trigger is dead for this whole class.
- **Band-1 catch (route-only k=3 union) [MEASURED]:**
  - `[high] IDOR: User-supplied resource IDs passed to query functions without in-file ownership validation` — 2/3
  - `[high] reassignToStatusId not tenant-scoped before reaching the DB layer` — 1/3
  - Both are the 16f510f canonical, flagged as an *unverified* (in-file) ownership gap.

## The cell — SHAPE B (imported-symbol insecure default) [MEASURED] — the discriminating check

| field | value |
|---|---|
| class | auth / session-reissue 2FA-enforcement bypass (S1) — non-payments |
| fix commit | `ead618d` (zync) "S1-001/002/003 2FA flags on session re-issue" (+331-line regression test) |
| target (on disk) | `/tmp/sg_shapeB/cells/refresh.ts` = `ead618d~1:apps/zync-api/src/routes/auth/refresh.ts` (92 lines, RAW git) |
| imported symbol (deciding fact) | `buildSessionPayload` from `@zync/auth` (`packages/auth/src/session.ts`), pre-fix body: `enforce_2fa: args.enforce2fa ?? false` / `two_factor_verified: args.twoFactorVerified ?? false` — **insecure default** |
| canonical | pre-fix `refresh.ts` calls `buildSessionPayload({user,tenant,role,permissions,version,type})` OMITTING `enforce2fa`/`twoFactorVerified` → re-issued session has `enforce_2fa=false` even for a tenant that enforces 2FA → 2FA dropped for the rest of the session lifetime |
| why genuinely cross-file (Shape B, NOT A) | the call is visible AND well-formed; userId/tenantId are validated (from the refresh-token row, NOT user input) → **no IDOR smell, no visibly-absent check, zero mention of 2FA in the file.** To flag the omission a reviewer must already know the builder's param set + its insecure default — both live in `@zync/auth`. |

**Result [MEASURED]: band-1 SILENT on the canonical.** k=3 union = ~16 findings — all real-ish, NONE the 2FA
drop. Sample: `[critical] refresh rotation atomicity / concurrent reuse`, `[high] TOCTOU on MAX_ACTIVE_TOKENS`,
`[high] no error handling after rotateRefreshToken`, `[medium] staging URLs in ALLOWED_ORIGINS`, `[medium]
unsafe \`as\` casts baked into session JWT`. Report: `/tmp/sg_shapeB/refresh_report.md`. Oracle SILENT
(payments-only). Escalation did NOT fire (`buildSessionPayload` not CRITICAL-named + no sibling on disk).
→ **silent TOTAL miss.** This is the band-2-existential case: no flag for reactive escalation to react to.

## The two-file test — does RESOLUTION fix it, or is DETECTION the bottleneck? [MEASURED] — the fix-gate

The Shape-B miss proves the HOLE. It does NOT prove the proactive-pull REMEDY: pulling `session.ts` into scope
does not, on its own, guarantee the LLM flags the *omitted* field — it's an omission bug, and the LLM might
rationalize "maybe refresh intentionally skips 2FA." So before encoding "band-2 = proactive-pull" into a skill,
measure the remedy's detection half directly: hand band-1 BOTH files in one prompt (manual escalation bypass,
since the trigger won't pull a non-CRITICAL symbol — that bypass IS the trigger problem in miniature), same v2
prompt, k=3.

| field | value |
|---|---|
| cell (on disk) | `/tmp/sg_shapeB/cells/refresh_plus_session.ts` = `refresh.ts` + `session.ts` concatenated, FILE 1/2 delimited |
| what's added vs the single-file Shape-B cell | the imported builder's body — `enforce_2fa: args.enforce2fa ?? false` — is now in scope |
| run | `gate.py <cell> --k 3 --config-dir /tmp/sg_cfg` |

**Result [MEASURED]: band-1 CATCHES the canonical 3/3** — `[critical] 2FA enforcement stripped on every token
refresh` _(3/3 rolls)_ — vs **0/3 silent** on the single-file cell. The union also surfaced `[medium] Refresh
cookie security attributes are an unverifiable cross-file dependency` and `[medium] m.tier placed in JWT without
runtime enum validation`, i.e. the LLM is reasoning about cross-file dependencies *unprompted* once the scope is
broadened. Report: `/tmp/sg_shapeB/refresh_plus_session_report.md`.

**Decision [the gate the advisor pre-specified]: CATCHES → resolution is the bottleneck → proactive-pull is worth
building, encode it.** The DETECTION half is solved: the LLM does the call-site-omission diff itself once it can
see the builder. So the band-2 build does NOT need a bespoke "diff call-site args against builder param-set"
sub-check — it needs only (1) a barrel-following RESOLVER and (2) a TRIGGER deciding which imports to pull.

**Honest scope of this result:** n=1 cell — a point estimate ("given resolution, band-1 CAN catch THIS Shape-B
canonical at 3/3"), not a recall RATE. A rate needs n≥3 with-resolution Shape-B cells (bench.py). And it does
NOT solve the TRIGGER: "pull every *security-relevant* import" either collapses to "~all imports" (cost) or
re-introduces the same classification problem the payments `CRITICAL` regex had (moved from "which names" to
"which symbols"). The trigger is the remaining hard, unbuilt problem; detection is no longer in doubt.

## Interpretation [MEASURED facts → INFERRED redirect]

- The escalation TRIGGER (`CRITICAL` symbol-name regex) is co-extensive with the ORACLE's domain (payments).
  Where it fires, the oracle already covers; where the oracle is silent (the rest of the S1–S11 taxonomy), the
  trigger is silent too. So **band-2-via-escalation, as salvaged, adds little beyond the oracle** and is dead for
  tenant-isolation / IDOR / generic-auth cross-file bugs. [INFERRED from the two MEASURED facts above + the oracle's
  payments-only scope.]
- The LLM does NOT need teaching to *see* cross-file risk — band-1 already emits the flag. What's missing is
  **CONFIRMATION**: pull the imported symbol's definition, decide TP vs FP, upgrade or drop. Without it, every
  cross-file IDOR stays a `[high]` suspicion in the union — directly worsening the precision/triage problem
  (see recall-spike doc §2b: triage/ranking, NOT dedup).

## Redirect — what this changes for the build

1. **Band-2 escalation TRIGGER must generalize beyond payments-keyword symbol names.** Replace/augment the
   payments `CRITICAL` regex with role/taxonomy detection: any imported predicate that a security review must
   resolve to decide a finding — ownership guards (`assertTenantOwns*`, `*Owns*`, `can*`, `is*Allowed`), authz
   middleware, and any query/mutation that RECEIVES an untrusted FK/id. Trigger candidate = "the LLM flagged an
   in-file-unverifiable ownership/authz dependency on symbol X" → pull X regardless of payments keywords.
2. **Resolver must follow the barrel.** zync imports via `@zync/db/queries` (barrel `index.ts` re-exporting from
   `./task-statuses`) — a 2-hop resolve the current `resolve()` does not follow. Needed before any real zync
   cross-file pull-in works. [MEASURED: monorepo barrel structure; resolver is single-hop.]
3. **Encode what the two-file test PROVED, gate what it didn't.** The create-tests/corpus skill may state:
   band-1 single-file covers single-file logic + Shape-A cross-file IDOR (as a flag); **Shape-B cross-file is a
   measured single-file MISS whose DETECTION half is solved once the symbol is resolved (3/3, item 4)**; the
   remaining band-2 GAP is purely DELIVERY — the TRIGGER (1) + the barrel RESOLVER (2). Do NOT encode the trigger
   as solved (it is the open problem); DO encode that no bespoke omission-detection sub-check is needed.
4. **Positive-path mechanics (escalation pulls sibling + a leg catches) are already acceptance-tested on C02**
   (payments, oracle leg). What remains UNPROVEN: the LLM band-2 *confirmation* catch on a connected sibling,
   independent of the oracle — blocked until the trigger generalizes to a non-payments cell.

## Scope — honest

- DOES [MEASURED]: (Shape A) band-1 flags a real non-payments cross-file IDOR; (Shape B) band-1 is SILENT on a
  real non-payments imported-insecure-default vuln → a measured coverage hole; proves the escalation trigger
  cannot fire on tenant-isolation OR auth symbol names (code-confirmed + observed); both cells code-verified
  against the fix diff.
- DOES [MEASURED, two-file test]: with the imported symbol resolved into scope, band-1 catches the Shape-B
  canonical 3/3 → the DETECTION half of the proactive-pull remedy works; no bespoke omission-detection sub-check
  needed.
- DOES NOT: measure a band-2 recall RATE (n=1 cell per shape; a rate needs n≥3 with-resolution Shape-B cells →
  bench.py); does not build/test the TRIGGER (which imports to pull — the open problem, manually bypassed here);
  does not build/test the barrel RESOLVER (resolution was supplied by hand-concatenation, not by `resolve()`).
  Detection: PROVEN. Delivery (trigger + resolver): UNBUILT.
