Skip to content

fix(gateway): compliance attestations/aggregate IDOR — compare kernel.operatorAddress (twin of #221) - #222

Open
LamaSu wants to merge 1 commit into
masterfrom
fix/compliance-operator-ownership
Open

fix(gateway): compliance attestations/aggregate IDOR — compare kernel.operatorAddress (twin of #221)#222
LamaSu wants to merge 1 commit into
masterfrom
fix/compliance-operator-ownership

Conversation

@LamaSu

@LamaSu LamaSu commented Jul 9, 2026

Copy link
Copy Markdown
Owner

What

Fixes an IDOR (broken-ownership-check) on POST /api/jobs/:jobId/attestations/aggregate in packages/gateway/src/routes/compliance.ts.

The kernel-operator authorization fallback compared kernel.operatorId — a field that does not exist on shopKernels (packages/db/src/schema/kernels.ts defines only operatorAddress). So (kernel as any).operatorId was always undefined, the comparison always failed, and a legitimate kernel operator was wrongly 403'd when aggregating attestations for their own job.

This is the exact twin of the jobs.ts IDOR fixed in #221 (commit e62bd250), whose commit message explicitly flagged compliance.ts:126 as the follow-up.

The one-line fix

-        if (kernel && (kernel as any).operatorId === operatorId) isAuthorized = true;
+        if (kernel && kernel.operatorAddress === operatorId) isAuthorized = true;

kernel.operatorAddress is the same email/wallet identity space as the caller's operatorId:

  • middleware/api-gate.ts sets req.operatorId = apiKey.operatorId.
  • paid-job-flow.ts resolveOperatorPayoutAddress already resolves operators via repos.apiKeys.findByOperator(kernel.operatorAddress) — treating the two as equivalent.

The gate is not weakened: an unrelated caller still gets 403, an unauthenticated request still gets 401.

Regression test

New packages/gateway/src/__tests__/compliance-attestations-ownership.test.ts (4 cases). The existing compliance-routes.test.ts only ever authorized the aggregate route via the submittedBy path (its beforeEach makes kernels.findById return null), so it never exercised the buggy kernel-operator line — which is how this slipped through. The new cases drive that path directly:

  • POSITIVE (kernel operator): rightful operator aggregates with no 403 when submittedBy does NOT match (so the kernel path is what authorizes).
  • POSITIVE (submitter): the submittedBy path still authorizes (short-circuits, kernel lookup never called).
  • NEGATIVE (unrelated): still 403 forbidden; facade never called.
  • NEGATIVE (unauthenticated): 401.

Verification (DGX Spark, scoped)

✓ src/__tests__/compliance-attestations-ownership.test.ts (4 tests) — 4 passed

Against the pre-fix code the kernel-operator positive case FAILS 403 (true regression guard); with the fix all 4 pass.

Scope

compliance.ts + the new test only. jobs.ts, paid-job-flow.ts, /complete, kernels.ts, and the settlement crank are untouched. Closes the twin of #221 flagged by the accept-403 fix.

🤖 Generated with Claude Code

…atorAddress not nonexistent operatorId

POST /api/jobs/:jobId/attestations/aggregate authorized the kernel-operator
fallback by comparing `kernel.operatorId` — a field that does not exist on
shopKernels (packages/db/src/schema/kernels.ts defines only `operatorAddress`).
The branch was dead code: `(kernel as any).operatorId` was always undefined, the
comparison always failed, and a legitimate kernel operator was wrongly 403'd when
aggregating attestations for their own job (an IDOR-class broken-ownership bug).

Fix: compare `kernel.operatorAddress` — the same email/wallet identity space as
the API-key operatorId (middleware/api-gate.ts sets req.operatorId =
apiKey.operatorId; paid-job-flow.ts resolveOperatorPayoutAddress already resolves
operators via repos.apiKeys.findByOperator(kernel.operatorAddress)). The gate is
not weakened: an unrelated caller still receives 403, unauthenticated still 401.

Twin of the jobs.ts IDOR fixed in PR #221 (commit e62bd25), whose message
flagged compliance.ts:126 as the follow-up. Same bug class, same fix shape.

Adds compliance-attestations-ownership.test.ts (regression, 4 cases): the
kernel's rightful operator aggregates with no 403 when submittedBy does NOT match
(so the kernel path is what authorizes — the previously-broken line); the
submittedBy path still authorizes; an unrelated caller is still 403'd; an
unauthenticated request is 401. Verified on DGX Spark: 4/4 pass with the fix, and
the kernel-operator case FAILS 403 against the pre-fix code (true regression guard).

Scope: compliance.ts + the new test only — jobs.ts, paid-job-flow.ts, and the
settlement crank are untouched.

implementer-compliance-ownership

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vhf19Lf2GJYw3wBa5kyuNP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant