feat(api): unblock cloud-tests mutations for API key + service token callers#2883
Merged
Conversation
…callers
A customer (Josh) hit a 401 calling
POST /v1/cloud-security/findings/:id/exception with an X-API-Key:
"Marking an exception requires session authentication." The same
inline check existed on revokeException and updateAwsScanMode. With
MCP integration coming soon we want the full cloud-tests mutation
surface to be API-callable.
The reason the wall existed: AuditLog.userId + FindingException.markedById
are both String/FK to User, and API keys are org-scoped (no per-user
identity). This change introduces a shared ActingUserResolver that
attributes API key / service-token mutations to the org's oldest
owner-role member — same pattern 19+ other services in the codebase
already use for owner lookups. The audit log description prepends
`[via API key "<name>"]` so reviewers see at a glance that it was
automation, not a UI action.
What's preserved:
- Authentication unchanged (HybridAuthGuard runs as before).
- RBAC unchanged (PermissionGuard runs as before — caller still
needs integration:update scope).
- Cross-tenant isolation preserved (owner lookup scoped to the
calling org).
- Session callers: zero behavior change — resolver short-circuits
to req.userId with NO DB query. Audit description unchanged
(no [via ...] prefix).
- Existing API key writes (vendor:create, risk:create, etc.):
untouched.
- Service tokens passing x-user-id: unchanged (HybridAuthGuard
already sets req.userId, resolver short-circuits).
What changes:
- Service tokens without x-user-id: now succeed with owner-fallback
attribution (previously hit the inline 401 too).
- API key callers: now succeed with owner-fallback attribution.
- Org with no owner-role member: 400 with actionable message
("ensure your organization has at least one user with the owner
role"). Soft failure, never a 500.
Tests:
- 10 acting-user.service tests (session short-circuit, owner
fallback, cross-tenant scoping, deterministic oldest-owner pick,
no-owner soft failure, caller-label formatting)
- 12 exception.service tests (+2 for callerLabel propagation —
audit description prefix + metadata field)
- 5 NEW aws-scan-mode.service tests (idempotent no-op, AWS-only
enforcement, owner-fallback callerLabel)
- 9 NEW controller-level tests covering all 3 endpoints × 3
scenarios (session, API-key+owner, no-owner → 400)
- 253 cloud-security + auth tests pass (3 pre-existing TLS-env
failures unrelated — same suites fail on main)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Summary
Customer Josh hit a 401 calling
POST /v1/cloud-security/findings/:id/exceptionwith an X-API-Key from his automation pipeline. The same restriction existed onrevokeExceptionandupdateAwsScanMode. With MCP integration coming next, we want the full cloud-tests mutation surface to be callable via API key + service token, not session only.This PR removes the inline session-only wall on all three endpoints by introducing a shared
ActingUserResolverthat attributes API key / service token mutations to the org's oldest owner — keeping the audit trail intact without forcing callers to manage user IDs themselves. The audit log description prepends[via API key "<name>"]so reviewers see at a glance that it was automation.What's preserved (regression safety)
integration:updatescope.Member.organizationIdfilter.req.userIdwith no DB query, and the audit description is exactly the same as today (no[via ...]prefix).What changes
x-user-idx-user-id[via service "X"][via API key "X"]Files
New (4):
apps/api/src/auth/acting-user.service.ts— shared resolver, single source of truth for "who do I attribute this mutation to?"apps/api/src/auth/acting-user.service.spec.ts— 10 testsapps/api/src/cloud-security/aws-scan-mode.service.spec.ts— 5 tests (none existed)apps/api/src/cloud-security/cloud-security.controller.spec.ts— 9 tests (none existed for these endpoints)Modified (8):
apps/api/src/auth/auth.module.ts— registerActingUserResolverapps/api/src/auth/api-key.service.ts—ApiKeyValidationResultnow includesapiKeyId, apiKeyNameapps/api/src/auth/hybrid-auth.guard.ts— populaterequest.apiKeyId+request.apiKeyNameapps/api/src/auth/types.ts— type additionsapps/api/src/cloud-security/cloud-security.controller.ts— replace 3 inline 401 walls with resolver calls; better OpenAPI descriptionsapps/api/src/cloud-security/exception.service.ts— acceptcallerLabel, use it in audit descriptionapps/api/src/cloud-security/aws-scan-mode.service.ts— acceptcallerLabel, use it in audit descriptionapps/api/src/cloud-security/exception.service.spec.ts— +2 tests for callerLabel propagationTests
253 cloud-security + auth tests pass. Three pre-existing failures unchanged (same suites fail on
main— Postgres TLS env issues, unrelated to this PR).Key regression guards added:
orderBy: { createdAt: 'asc' }) — stable attribution across owner add/removeReply to send the customer after merge
Test plan
POST /v1/cloud-security/findings/:id/exceptionwith X-API-Key + valid body → expect 201, exception created withmarkedById = <owner user>, audit log contains[via API key "<key name>"][via ...]prefix), behavior identical to today🤖 Generated with Claude Code
Summary by cubic
Unblocked cloud-security mutations for API keys and service tokens by attributing actions to the org’s oldest owner via a new
ActingUserResolver, with clear audit labels. Removes the session-only wall on exception create/revoke and AWS scan mode updates while keeping auth and RBAC intact.ActingUserResolverfor attribution: short-circuits for session orX-User-Id; otherwise falls back to the oldest org owner; prefixes audit text with[via API key "<name>"]or[via service "<name>"]; returns 400 if no owner.callerLabelso audit logs show automation; session behavior is unchanged and does not query the DB.apiKeyIdandapiKeyNameon the request viaapi-key.serviceandhybrid-auth.guardfor accurate audit context.Written for commit 26e53da. Summary will update on new commits. Review in cubic