Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
265923b
feat(iam): define service account identity contract
BeforeLights Aug 3, 2026
9c238e8
feat(iam): scope service account permissions
BeforeLights Aug 3, 2026
1db0186
feat(iam): add service account repository port
BeforeLights Aug 3, 2026
4a23c05
feat(iam): authorize service account lifecycle
BeforeLights Aug 3, 2026
9ebad8f
feat(iam): generate one-time service account secrets
BeforeLights Aug 3, 2026
87530e7
feat(iam): persist service account records
BeforeLights Aug 3, 2026
73b4d99
feat(iam): add service account prisma adapter
BeforeLights Aug 3, 2026
e32ea58
feat(iam): expose service account lifecycle api
BeforeLights Aug 3, 2026
7bf5ba0
feat(iam): resolve service accounts by secret digest
BeforeLights Aug 3, 2026
d02aa7e
feat(iam): authenticate service account credentials
BeforeLights Aug 3, 2026
03ef9db
feat(audit): register service account lifecycle actions
BeforeLights Aug 3, 2026
528f0c9
feat(iam): publish service account api contract
BeforeLights Aug 3, 2026
f015b11
feat(audit): add signed seal attestations
BeforeLights Aug 3, 2026
34b79bc
feat(bua): issue bounded entitlement leases
BeforeLights Aug 3, 2026
796a681
feat(bua): persist offline entitlement leases
BeforeLights Aug 3, 2026
52d1607
feat(bua): persist entitlement lease schema
BeforeLights Aug 3, 2026
4d057e4
feat(bua): add entitlement lease repository
BeforeLights Aug 3, 2026
5c25a81
feat(bua): issue and verify entitlement leases
BeforeLights Aug 3, 2026
ecfbb25
feat(bua): expose entitlement lease api
BeforeLights Aug 3, 2026
89f7bfb
feat(bua): add provider-neutral lease signing
BeforeLights Aug 3, 2026
82b0e22
fix(bua): bind lease acceptance to canonical payload
BeforeLights Aug 3, 2026
ea59eb1
feat(audit): define seal attestation repository port
BeforeLights Aug 3, 2026
3705546
feat(audit): add in-memory seal attestations
BeforeLights Aug 3, 2026
304eecf
feat(audit): persist seal attestation schema
BeforeLights Aug 3, 2026
2834320
feat(audit): add Prisma seal attestation adapter
BeforeLights Aug 3, 2026
fb9d5c2
feat(audit): issue and verify seal attestations
BeforeLights Aug 3, 2026
4b40c9b
feat(audit): compose attestation providers
BeforeLights Aug 3, 2026
a100a75
feat(audit): expose attestation api
BeforeLights Aug 3, 2026
cb0dd27
fix(bua): validate entitlement snapshot plans
BeforeLights Aug 3, 2026
e4ad354
feat(bua): compose HMAC lease signing
BeforeLights Aug 3, 2026
d9a8386
docs(traceability): record IAM AUD BUA security slice
BeforeLights Aug 3, 2026
c9ebd1f
fix(traceability): keep audit ownership precise
BeforeLights Aug 3, 2026
99141b9
chore: format IAM and entitlement security slice
BeforeLights Aug 3, 2026
c2442ad
fix(iam): satisfy service account lint guard
BeforeLights Aug 3, 2026
20ceddd
test(api): track security migration and route contracts
BeforeLights Aug 3, 2026
df4d0a2
fix(deps): pin patched fast-uri release
BeforeLights Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/operations/iam-bua-security-slice-2026-08-03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# IAM, audit, and entitlement security slice — 2026-08-03

## Scope

This evidence record covers the 30-commit `feat/iam-security-completion` batch based on
`origin/dev`. It is implementation evidence only. It does not claim that Plan 020 or any
P0/P1 release gate is complete.

## Delivered

- IAM service-account identities now use bounded permissions, digest-only secrets, one-time
secret issuance, rotation, permanent revocation, last-use monotonicity, tenant-scoped
repositories, Prisma persistence, and versioned lifecycle HTTP contracts.
- AUD action vocabulary includes service-account lifecycle actions. Audit seal attestations
are canonical, independently signed, immutable, tenant-scoped, transaction-aware, and
available through in-memory and Prisma adapters with API verification.
- BUA entitlement snapshots validate their complete provider-independent plan projection.
Signed offline leases are bounded to 24 hours and snapshot expiry, bind revision and
security epoch, persist immutably, verify canonical payloads, and use a replaceable HMAC
signer or injected HSM/KMS-compatible signer.
- BUA and AUD module composition defaults to unavailable signing when key material is absent;
no secret is generated, logged, or committed by the repository.

## Verification

- Domain build and 148 domain tests pass, including canonical lease acceptance, malformed plan
rejection, attestation binding, tenant ancestry, and signature tampering cases.
- Focused API TypeScript compilation, ESLint, Prisma validation, OpenAPI generation/check,
Redocly validation, and focused IAM/AUD/BUA tests pass.
- Prisma migrations are ordered and add only `bua.entitlement_leases` and
`aud.audit_seal_attestations`; no migration was applied to a live environment.
- Traceability entries for IAM-013, AUD-015, AUD-016, BUA-017, and BUA-018 remain `partial`
and `not-verified`. They point to the concrete code, tests, and this evidence record.

## Security and rollback notes

- Lease payloads are canonicalized before signature verification; malformed, stale, expired,
overlong, wrong-scope, and tampered leases fail closed.
- Attestation storage never broadens a caller scope and rejects immutable-identity changes.
- HMAC keys must be at least 32 bytes and should be supplied by a secret manager. HMAC is a
portable default, not a replacement for a production KMS/HSM policy.
- Every commit on the feature branch is independently reversible. The migration commits must
be reverted only with a reviewed down-migration/restore procedure; no destructive rollback
was executed here.

## Remaining gates

Full audit export/legal-hold/retention administration, atomic cross-module audit coordination,
offline authorization snapshots, entitlement reconciliation/usage exports, real PostgreSQL
integration, backup restoration, security assessment, and release evidence remain outstanding.
The feature PR targets `dev` without CodeRabbit; CodeRabbit remains reserved for the later
`dev` to `main` promotion PR and is invoked once there.
78 changes: 48 additions & 30 deletions docs/plans/requirement-traceability.json
Original file line number Diff line number Diff line change
Expand Up @@ -1085,15 +1085,20 @@
"codePaths": [
"packages/domain/src/audit/v1.ts",
"services/api/src/features/aud/",
"services/api/prisma/schema/aud.prisma"
"services/api/prisma/schema/aud.prisma",
"services/api/src/features/aud/application/audit-attestation.service.ts",
"services/api/src/features/aud/adapter/prisma-audit-attestation-repository.adapter.ts",
"services/api/prisma/migrations/20260803080000_aud_seal_attestations/migration.sql"
],
"testPaths": [
"packages/domain/test/audit-v1.test.mjs",
"services/api/test/features/aud/",
"services/api/test/http-contract.test.ts"
"services/api/test/http-contract.test.ts",
"services/api/test/features/aud/audit-attestation.service.test.ts",
"services/api/test/features/aud/prisma-audit-attestation-repository.test.ts"
],
"releaseEvidence": [
"docs/operations/identity-audit-entitlement-reconciliation-2026-08-03.md"
"docs/operations/iam-bua-security-slice-2026-08-03.md"
],
"status": "partial",
"coverage": "partial",
Expand All @@ -1110,20 +1115,24 @@
"codePaths": [
"packages/domain/src/audit/v1.ts",
"services/api/src/features/aud/",
"services/api/prisma/schema/aud.prisma"
"services/api/prisma/schema/aud.prisma",
"services/api/src/features/aud/application/audit-attestation.service.ts",
"services/api/src/features/aud/api/audit-attestation.controller.ts",
"services/api/src/features/aud/adapter/in-memory-audit-attestation-repository.adapter.ts",
"services/api/prisma/migrations/20260803080000_aud_seal_attestations/migration.sql"
],
"testPaths": [
"packages/domain/test/audit-v1.test.mjs",
"services/api/test/features/aud/",
"services/api/test/http-contract.test.ts"
"services/api/test/http-contract.test.ts",
"services/api/test/features/aud/audit-attestation.controller.test.ts",
"services/api/test/features/aud/audit-attestation-repository.test.ts"
],
"releaseEvidence": [
"requirement-linked-tests",
"security-and-tenant-gate",
"release-manager-approval"
"docs/operations/iam-bua-security-slice-2026-08-03.md"
],
"status": "planned",
"coverage": "planned",
"status": "partial",
"coverage": "partial",
"verificationStatus": "not-verified",
"verifiedPaths": [],
"releaseStatus": "p0-release-gate"
Expand Down Expand Up @@ -1763,20 +1772,23 @@
"codePaths": [
"packages/domain/src/entitlements/v1.ts",
"services/api/src/features/bua/",
"services/api/prisma/schema/bua.prisma"
"services/api/prisma/schema/bua.prisma",
"services/api/src/features/bua/application/entitlement-lease.service.ts",
"services/api/src/features/bua/adapter/prisma-entitlement-lease-repository.adapter.ts",
"services/api/prisma/migrations/20260803070000_bua_entitlement_leases/migration.sql"
],
"testPaths": [
"packages/domain/test/entitlements-v1.test.mjs",
"services/api/test/features/bua/",
"services/api/test/http-contract.test.ts"
"services/api/test/http-contract.test.ts",
"services/api/test/features/bua/entitlement-lease.service.test.ts",
"services/api/test/features/bua/prisma-entitlement-lease-repository.test.ts"
],
"releaseEvidence": [
"requirement-linked-tests",
"security-and-tenant-gate",
"release-manager-approval"
"docs/operations/iam-bua-security-slice-2026-08-03.md"
],
"status": "planned",
"coverage": "planned",
"status": "partial",
"coverage": "partial",
"verificationStatus": "not-verified",
"verifiedPaths": [],
"releaseStatus": "ga-completion"
Expand All @@ -1790,20 +1802,23 @@
"codePaths": [
"packages/domain/src/entitlements/v1.ts",
"services/api/src/features/bua/",
"services/api/prisma/schema/bua.prisma"
"services/api/prisma/schema/bua.prisma",
"services/api/src/features/bua/api/entitlement.controller.ts",
"services/api/src/features/bua/adapter/hmac-entitlement-lease-signer.adapter.ts",
"services/api/prisma/migrations/20260803070000_bua_entitlement_leases/migration.sql"
],
"testPaths": [
"packages/domain/test/entitlements-v1.test.mjs",
"services/api/test/features/bua/",
"services/api/test/http-contract.test.ts"
"services/api/test/http-contract.test.ts",
"services/api/test/features/bua/entitlement.controller.test.ts",
"services/api/test/features/bua/hmac-entitlement-lease-signer.test.ts"
],
"releaseEvidence": [
"requirement-linked-tests",
"security-and-tenant-gate",
"release-manager-approval"
"docs/operations/iam-bua-security-slice-2026-08-03.md"
],
"status": "planned",
"coverage": "planned",
"status": "partial",
"coverage": "partial",
"verificationStatus": "not-verified",
"verifiedPaths": [],
"releaseStatus": "ga-completion"
Expand Down Expand Up @@ -9379,23 +9394,26 @@
"packages/domain/src/authorization/v1.ts",
"packages/domain/src/mfa/v1.ts",
"packages/domain/src/csrf/v1.ts",
"packages/domain/src/service-account/v1.ts",
"packages/domain/src/audit/v1.ts",
"services/api/src/features/iam/",
"services/api/prisma/schema/iam.prisma"
"services/api/prisma/schema/iam.prisma",
"services/api/prisma/migrations/20260803060000_iam_service_accounts/migration.sql"
],
"testPaths": [
"packages/domain/test/identity-v1.test.mjs",
"packages/domain/test/permissions-v1.test.mjs",
"services/api/test/features/iam/",
"services/api/test/features/iam/service-account.service.test.ts",
"services/api/test/features/iam/prisma-service-account-repository.test.ts",
"services/api/test/platform/http/session-tenant-context.test.ts",
"services/api/test/platform/http/csrf-protection.test.ts"
],
"releaseEvidence": [
"requirement-linked-tests",
"security-and-tenant-gate",
"release-manager-approval"
"docs/operations/iam-bua-security-slice-2026-08-03.md"
],
"status": "planned",
"coverage": "planned",
"status": "partial",
"coverage": "partial",
"verificationStatus": "not-verified",
"verifiedPaths": [],
"releaseStatus": "p0-release-gate"
Expand Down
4 changes: 4 additions & 0 deletions packages/domain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
"types": "./src/identity/v1.ts",
"import": "./dist/identity/v1.js"
},
"./service-account/v1": {
"types": "./src/service-account/v1.ts",
"import": "./dist/service-account/v1.js"
},
"./entitlements/v1": {
"types": "./src/entitlements/v1.ts",
"import": "./dist/entitlements/v1.js"
Expand Down
90 changes: 90 additions & 0 deletions packages/domain/src/audit/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const AUDIT_ACTIONS_V1 = Object.freeze([
'device.enrolled',
'device.activated',
'device.revoked',
'service_account.created',
'service_account.rotated',
'service_account.revoked',
'entitlement.granted',
'entitlement.suspended',
'artifact.registered',
Expand Down Expand Up @@ -99,6 +102,28 @@ export interface AuditSealV1 {
readonly sealedAt: StrictUtcTimestampV1;
}

/** AUD-015/016: an independently stored signature over an immutable seal range. */
export const AUDIT_ATTESTATION_SCHEMA_VERSION_V1 = 1 as const;

export interface AuditSealAttestationV1 {
readonly schemaVersion: typeof AUDIT_ATTESTATION_SCHEMA_VERSION_V1;
readonly attestationId: StableIdentifierV1;
readonly tenantScope: TenantScopeV1;
readonly firstSequence: number;
readonly lastSequence: number;
readonly eventCount: number;
readonly rootDigest: string;
readonly sealedAt: StrictUtcTimestampV1;
readonly signerKeyId: string;
readonly payload: string;
readonly signature: string;
}

export interface AuditSealAttestationSignerV1 {
sign(payload: string): string;
verify(payload: string, signature: string): boolean;
}

export type AuditErrorCodeV1 =
| 'INVALID_IDENTIFIER'
| 'INVALID_TIMESTAMP'
Expand Down Expand Up @@ -185,6 +210,22 @@ function canonicalEvent(event: Omit<AuditEventV1, 'digest'>): string {
});
}

function canonicalAttestation(
input: Omit<AuditSealAttestationV1, 'payload' | 'signature'>,
): string {
return JSON.stringify({
schemaVersion: input.schemaVersion,
attestationId: input.attestationId,
tenantScope: input.tenantScope,
firstSequence: input.firstSequence,
lastSequence: input.lastSequence,
eventCount: input.eventCount,
rootDigest: input.rootDigest,
sealedAt: input.sealedAt,
signerKeyId: input.signerKeyId,
});
}

export function sanitizeAuditSummaryV1(input: unknown): AuditResultV1<AuditSummaryV1> {
if (input === undefined) return Object.freeze({ accepted: true, value: Object.freeze({}) });
if (typeof input !== 'object' || input === null || Array.isArray(input))
Expand Down Expand Up @@ -353,3 +394,52 @@ export function createAuditSealV1(
}),
});
}

export function createAuditSealAttestationV1(
seal: AuditSealV1,
input: { readonly attestationId: unknown; readonly signerKeyId: unknown },
signer: AuditSealAttestationSignerV1,
): AuditResultV1<AuditSealAttestationV1> {
const attestationId = stableId(input.attestationId);
const signerKeyId = text(input.signerKeyId, 200);
if (!attestationId || !signerKeyId) return rejected('INVALID_IDENTIFIER');
const unsigned: Omit<AuditSealAttestationV1, 'payload' | 'signature'> = {
schemaVersion: AUDIT_ATTESTATION_SCHEMA_VERSION_V1,
attestationId,
tenantScope: seal.tenantScope,
firstSequence: seal.firstSequence,
lastSequence: seal.lastSequence,
eventCount: seal.eventCount,
rootDigest: seal.rootDigest,
sealedAt: seal.sealedAt,
signerKeyId,
};
const payload = canonicalAttestation(unsigned);
const signature = text(signer.sign(payload), 2048);
if (!signature) return rejected('INVALID_TEXT');
return Object.freeze({
accepted: true,
value: Object.freeze({ ...unsigned, payload, signature }),
});
}

export function verifyAuditSealAttestationV1(
attestation: AuditSealAttestationV1,
seal: AuditSealV1,
signer: AuditSealAttestationSignerV1,
): AuditResultV1<true> {
if (
attestation.schemaVersion !== AUDIT_ATTESTATION_SCHEMA_VERSION_V1 ||
tenantScopeKeyV1(attestation.tenantScope) !== tenantScopeKeyV1(seal.tenantScope) ||
attestation.firstSequence !== seal.firstSequence ||
attestation.lastSequence !== seal.lastSequence ||
attestation.eventCount !== seal.eventCount ||
attestation.rootDigest !== seal.rootDigest ||
attestation.sealedAt !== seal.sealedAt
)
return rejected('CHAIN_INVALID');
const { payload, signature, ...unsigned } = attestation;
if (canonicalAttestation(unsigned) !== payload || !signer.verify(payload, signature))
return rejected('CHAIN_INVALID');
return Object.freeze({ accepted: true, value: true });
}
1 change: 1 addition & 0 deletions packages/domain/src/authorization/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ const resourceScopeTypes: Readonly<Record<ResourceTypeV1, readonly TenantScopeV1
artifact: Object.freeze(['workspace', 'project'] as const),
'billing-account': Object.freeze(['organization'] as const),
device: Object.freeze(['organization'] as const),
'service-account': Object.freeze(['organization', 'workspace'] as const),
job: Object.freeze(['workspace', 'project'] as const),
organization: Object.freeze(['organization'] as const),
project: Object.freeze(['project'] as const),
Expand Down
Loading
Loading