fix(gateway): compliance attestations/aggregate IDOR — compare kernel.operatorAddress (twin of #221) - #222
Open
LamaSu wants to merge 1 commit into
Open
fix(gateway): compliance attestations/aggregate IDOR — compare kernel.operatorAddress (twin of #221)#222LamaSu wants to merge 1 commit into
LamaSu wants to merge 1 commit into
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes an IDOR (broken-ownership-check) on
POST /api/jobs/:jobId/attestations/aggregateinpackages/gateway/src/routes/compliance.ts.The kernel-operator authorization fallback compared
kernel.operatorId— a field that does not exist onshopKernels(packages/db/src/schema/kernels.tsdefines onlyoperatorAddress). So(kernel as any).operatorIdwas alwaysundefined, 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.tsIDOR fixed in #221 (commite62bd250), whose commit message explicitly flaggedcompliance.ts:126as the follow-up.The one-line fix
kernel.operatorAddressis the same email/wallet identity space as the caller'soperatorId:middleware/api-gate.tssetsreq.operatorId = apiKey.operatorId.paid-job-flow.tsresolveOperatorPayoutAddressalready resolves operators viarepos.apiKeys.findByOperator(kernel.operatorAddress)— treating the two as equivalent.The gate is not weakened: an unrelated caller still gets
403, an unauthenticated request still gets401.Regression test
New
packages/gateway/src/__tests__/compliance-attestations-ownership.test.ts(4 cases). The existingcompliance-routes.test.tsonly ever authorized the aggregate route via thesubmittedBypath (itsbeforeEachmakeskernels.findByIdreturnnull), so it never exercised the buggy kernel-operator line — which is how this slipped through. The new cases drive that path directly:submittedBydoes NOT match (so the kernel path is what authorizes).submittedBypath still authorizes (short-circuits, kernel lookup never called).403 forbidden; facade never called.401.Verification (DGX Spark, scoped)
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