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
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:
@@ -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,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user