feat(api): deterministic scan worker + AUD0 wire + tenant binding
CI (SIAX Cloud) / security (pull_request) Successful in 14s
CI (SIAX Cloud) / security (push) Successful in 14s
CI (SIAX Cloud) / contracts (pull_request) Successful in 15s
CI (SIAX Cloud) / contracts (push) Successful in 17s
CI (SIAX Cloud) / quality (push) Successful in 1m9s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m10s

- scan worker: atomic claim (FOR UPDATE SKIP LOCKED), Playwright-core + system
  chromium capture → typed EvidenceRecords (runtime-html/dom/computed-style/
  stylesheet/asset/network-request/screenshot), confidence=measured
- AUD0 emitter (fire-and-forget, advisory): c0py.registry_created/.scan_created/
  .scan_completed/.scan_failed, tenant_id = Zitadel resourceowner (org)
- tenant binding: resourceowner claim from introspection (deny-by-default 403),
  migration 002 tenant_id on registries+scans, all queries tenant+owner scoped
- evidence gaps stay explicit (screenshot miss → no screenshot record)
- 30/30 tests, canonical validator OK
This commit is contained in:
2026-09-16 22:53:55 +02:00
parent 7fb6043e34
commit 12f6a65b80
13 changed files with 757 additions and 52 deletions
+16 -1
View File
@@ -5,6 +5,7 @@ export interface IntrospectionResult {
sub?: string;
aud?: string[] | string;
scope?: string;
resourceOwnerId?: string;
reason?: string;
}
@@ -75,6 +76,20 @@ export function createIntrospector(deps: IntrospectionDeps) {
return { active: false, reason: "audience_mismatch" };
}
return { active: true, sub: body.sub, aud: body.aud, scope: body.scope };
// Tenant binding: the resource owner (org) claim is asserted by Zitadel,
// never taken from any client input. Missing claim is surfaced so the
// caller can deny-by-default.
const raw = body as unknown as Record<string, unknown>;
const resourceOwnerId = typeof raw["urn:zitadel:iam:user:resourceowner:id"] === "string"
? String(raw["urn:zitadel:iam:user:resourceowner:id"])
: undefined;
return {
active: true,
sub: body.sub,
aud: body.aud,
scope: body.scope,
resourceOwnerId,
};
};
}