feat(phase-3): workstream 6 — SOC 2 Type II Preparation

Implements all 22 WS6 tasks completing Phase 3 Enterprise.

Column-level encryption (AES-256-CBC, Vault-backed key) via EncryptionService
applied to credentials.secret_hash, credentials.vault_path,
webhook_subscriptions.vault_secret_path, and agent_did_keys.vault_key_path.
Backward-compatible: isEncrypted() guard skips decryption for existing
plaintext rows until next read-write cycle.

Audit chain integrity (CC7.2): AuditRepository computes SHA-256 Merkle hash
on every INSERT (hash = SHA-256(eventId+timestamp+action+outcome+agentId+orgId+prevHash)).
AuditVerificationService walks the full chain verifying hash continuity.
AuditChainVerificationJob runs hourly; sets agentidp_audit_chain_integrity
Prometheus gauge to 1 (pass) or 0 (fail).

TLS enforcement (CC6.7): TLSEnforcementMiddleware registered as first
middleware in Express stack; 301 redirect on non-https X-Forwarded-Proto
in production.

SecretsRotationJob (CC9.2): hourly scan for credentials expiring within 7
days; increments agentidp_credentials_expiring_soon_total.

ComplianceController + routes: GET /audit/verify (auth+audit:read scope,
30/min rate-limit); GET /compliance/controls (public, Cache-Control 60s).
ComplianceStatusStore: module-level map updated by jobs, consumed by controller.

Prometheus: 2 new metrics (agentidp_credentials_expiring_soon_total,
agentidp_audit_chain_integrity); 6 alerting rules in alerts.yml.

Compliance docs: soc2-controls-matrix.md, encryption-runbook.md,
audit-log-runbook.md, incident-response.md, secrets-rotation.md.

Tests: 557 unit tests passing (35 suites); 26 new tests (EncryptionService,
AuditVerificationService); 19 compliance integration tests. TypeScript clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SentryAgent.ai Developer
2026-03-31 00:41:53 +00:00
parent 272b69f18d
commit fd90b2acd1
35 changed files with 3715 additions and 26 deletions

View File

@@ -0,0 +1,548 @@
openapi: 3.0.3
info:
title: SentryAgent.ai — Compliance & SOC 2 Type II Service
version: 1.0.0
description: |
The Compliance Service exposes endpoints supporting SentryAgent.ai's
**SOC 2 Type II** audit readiness programme.
Two categories of control are surfaced:
**Audit chain verification** (`GET /audit/verify`) — Confirms cryptographic
integrity of the immutable audit log chain across an optional date range.
This endpoint provides auditors and compliance tooling with a single call to
assert that no audit events have been tampered with, deleted, or reordered
after initial capture.
**SOC 2 control status** (`GET /compliance/controls`) — Returns a live status
snapshot for each of the five in-scope SOC 2 Trust Services Criteria controls
monitored by the platform. Designed as a lightweight, public health-style
endpoint so that monitoring infrastructure can poll without bearer credentials.
**In-scope SOC 2 controls:**
| Control ID | Name | Description |
|------------|------|-------------|
| `CC6.1` | Encryption at Rest | Verifies database and secrets store encryption is active |
| `CC6.7` | TLS Enforcement | Confirms TLS 1.2+ is enforced on all inbound connections |
| `CC7.2` | Audit Log Integrity | Validates audit chain hash continuity |
| `CC9.2` | Secrets Rotation | Checks that all managed secrets are within rotation policy |
| `CC7.1` | Webhook Dead-Letter Monitoring | Asserts dead-letter queue depth is within threshold |
**Required scope (audit chain verify only):** `audit:read`
servers:
- url: http://localhost:3000/api/v1
description: Local development server
- url: https://api.sentryagent.ai/v1
description: Production server
tags:
- name: Audit Chain
description: Cryptographic integrity verification of the immutable audit event chain
- name: Compliance Controls
description: SOC 2 Type II control status — public health-style monitoring endpoint
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
JWT access token with `audit:read` scope, obtained via `POST /token`.
Include as: `Authorization: Bearer <token>`
schemas:
ChainVerificationResult:
type: object
description: |
Result of an audit event chain integrity verification run.
The audit log is structured as a hash-linked chain. Each event stores a
reference to the hash of the preceding event. `verified: true` means every
event in the requested window was checked and no breaks in the chain were
detected.
When `verified` is `false`, `brokenAtEventId` identifies the first event
where the chain integrity check failed, enabling targeted forensic investigation.
required:
- verified
- checkedCount
- brokenAtEventId
properties:
verified:
type: boolean
description: >
`true` if every audit event in the checked range maintains an unbroken
cryptographic hash chain; `false` if at least one chain break was detected.
example: true
checkedCount:
type: integer
description: Total number of audit events examined during this verification run.
minimum: 0
example: 2847
brokenAtEventId:
type: string
format: uuid
nullable: true
description: >
UUID of the first audit event where chain continuity failed, or `null`
when `verified` is `true`. Only the first detected break is reported;
subsequent events are not checked after a break is found.
example: null
fromDate:
type: string
format: date-time
description: >
The ISO 8601 lower bound of the date range that was verified.
Present only when a `fromDate` query parameter was supplied.
example: "2026-03-01T00:00:00.000Z"
toDate:
type: string
format: date-time
description: >
The ISO 8601 upper bound of the date range that was verified.
Present only when a `toDate` query parameter was supplied.
example: "2026-03-31T23:59:59.999Z"
ControlStatus:
type: string
description: Operational status of a SOC 2 control at the time of the last check.
enum:
- passing
- failing
- unknown
example: passing
ComplianceControl:
type: object
description: Status record for a single SOC 2 Trust Services Criteria control.
required:
- id
- name
- status
- lastChecked
properties:
id:
type: string
description: SOC 2 Trust Services Criteria control identifier.
enum:
- CC6.1
- CC6.7
- CC7.2
- CC9.2
- CC7.1
example: "CC6.1"
name:
type: string
description: Human-readable name of the control.
example: "Encryption at Rest"
status:
$ref: '#/components/schemas/ControlStatus'
lastChecked:
type: string
format: date-time
description: ISO 8601 timestamp of the most recent automated check for this control.
example: "2026-03-31T06:00:00.000Z"
ComplianceControlsResponse:
type: object
description: SOC 2 compliance control status summary for all in-scope controls.
required:
- controls
properties:
controls:
type: array
description: Status record for each of the five in-scope SOC 2 controls.
minItems: 5
maxItems: 5
items:
$ref: '#/components/schemas/ComplianceControl'
example:
- id: "CC6.1"
name: "Encryption at Rest"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC6.7"
name: "TLS Enforcement"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.2"
name: "Audit Log Integrity"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC9.2"
name: "Secrets Rotation"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.1"
name: "Webhook Dead-Letter Monitoring"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
ErrorResponse:
type: object
description: Standard error response envelope used across all SentryAgent.ai APIs.
required:
- code
- message
properties:
code:
type: string
description: Machine-readable error code.
example: "UNAUTHORIZED"
message:
type: string
description: Human-readable description of the error.
example: "A valid Bearer token is required."
details:
type: object
description: Optional structured details providing additional context.
additionalProperties: true
example: {}
responses:
Unauthorized:
description: Missing or invalid Bearer token.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "UNAUTHORIZED"
message: "A valid Bearer token is required to access this resource."
Forbidden:
description: Valid token but insufficient permissions. Requires `audit:read` scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "INSUFFICIENT_SCOPE"
message: "The 'audit:read' scope is required to verify the audit chain."
TooManyRequests:
description: |
Rate limit exceeded. Retry after the reset time indicated in `X-RateLimit-Reset`.
headers:
X-RateLimit-Limit:
schema:
type: integer
description: Maximum requests allowed per minute.
example: 30
X-RateLimit-Remaining:
schema:
type: integer
description: Requests remaining in the current window.
example: 0
X-RateLimit-Reset:
schema:
type: integer
description: Unix timestamp when the rate limit window resets.
example: 1743155400
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "RATE_LIMIT_EXCEEDED"
message: "Too many requests. Please retry after the rate limit window resets."
InternalServerError:
description: Unexpected server error.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
code: "INTERNAL_SERVER_ERROR"
message: "An unexpected error occurred. Please try again later."
paths:
/audit/verify:
get:
operationId: verifyAuditChain
tags:
- Audit Chain
summary: Verify audit log chain integrity
description: |
Triggers a full integrity verification pass over the immutable audit event
chain. Each event in the log contains a cryptographic hash of the previous
event; this endpoint traverses the chain and confirms no breaks exist.
**Use cases:**
- Auditor evidence collection for SOC 2 Type II assessment
- Continuous compliance monitoring (cron-driven)
- Incident response — confirm audit log has not been tampered with
**Requires:** Bearer token with `audit:read` scope.
**Rate limit:** 30 requests/minute per `client_id`. Audit chain verification
is a computationally intensive operation and is rate-limited more aggressively
than standard read endpoints. For continuous monitoring, poll no more than
once per minute.
**Date range filtering:** Supply `fromDate` and/or `toDate` to restrict
verification to a specific window. When omitted, the entire retained audit
log is verified. `fromDate` must be before or equal to `toDate` when both
are provided.
**Result interpretation:**
- `verified: true` — chain is intact across all checked events
- `verified: false` — at least one chain break detected; `brokenAtEventId`
identifies the first affected event
security:
- BearerAuth: []
parameters:
- name: fromDate
in: query
description: |
ISO 8601 date-time lower bound for the verification window (inclusive).
When omitted, verification starts from the earliest available audit event.
Must be before or equal to `toDate` when both are supplied.
required: false
schema:
type: string
format: date-time
example: "2026-03-01T00:00:00.000Z"
- name: toDate
in: query
description: |
ISO 8601 date-time upper bound for the verification window (inclusive).
When omitted, verification runs up to and including the most recent
audit event. Must be after or equal to `fromDate` when both are supplied.
required: false
schema:
type: string
format: date-time
example: "2026-03-31T23:59:59.999Z"
responses:
'200':
description: |
Audit chain verification completed. Inspect `verified` to determine
whether chain integrity is intact. A `200` is returned regardless of
whether verification passed or failed — check the response body.
headers:
X-RateLimit-Limit:
schema:
type: integer
description: Maximum requests allowed per minute for this endpoint.
example: 30
X-RateLimit-Remaining:
schema:
type: integer
description: Requests remaining in the current rate limit window.
example: 29
X-RateLimit-Reset:
schema:
type: integer
description: Unix timestamp when the rate limit window resets.
example: 1743155400
content:
application/json:
schema:
$ref: '#/components/schemas/ChainVerificationResult'
examples:
chainIntact:
summary: Verification passed — chain is intact
value:
verified: true
checkedCount: 2847
brokenAtEventId: null
fromDate: "2026-03-01T00:00:00.000Z"
toDate: "2026-03-31T23:59:59.999Z"
chainBroken:
summary: Verification failed — chain break detected
value:
verified: false
checkedCount: 1203
brokenAtEventId: "c4d5e6f7-a8b9-0123-cdef-456789012345"
fromDate: "2026-03-01T00:00:00.000Z"
toDate: "2026-03-31T23:59:59.999Z"
noDateRange:
summary: Full log verified (no date range supplied)
value:
verified: true
checkedCount: 18504
brokenAtEventId: null
'400':
description: Invalid query parameter value or date range.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
invalidFromDate:
summary: fromDate is not a valid ISO 8601 date-time
value:
code: "VALIDATION_ERROR"
message: "Invalid query parameter value."
details:
field: "fromDate"
reason: "Must be a valid ISO 8601 date-time string (e.g. 2026-03-01T00:00:00.000Z)."
invalidToDate:
summary: toDate is not a valid ISO 8601 date-time
value:
code: "VALIDATION_ERROR"
message: "Invalid query parameter value."
details:
field: "toDate"
reason: "Must be a valid ISO 8601 date-time string (e.g. 2026-03-31T23:59:59.999Z)."
invalidDateRange:
summary: fromDate is after toDate
value:
code: "VALIDATION_ERROR"
message: "Invalid date range."
details:
reason: "fromDate must be before or equal to toDate."
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalServerError'
/compliance/controls:
get:
operationId: getComplianceControls
tags:
- Compliance Controls
summary: Get SOC 2 control status summary
description: |
Returns a live status snapshot for each of the five in-scope SOC 2 Type II
Trust Services Criteria controls monitored by the SentryAgent.ai platform.
**No authentication required.** This endpoint is intentionally public
(analogous to a health check) so that external monitoring infrastructure,
status pages, and audit tooling can poll it without bearer credentials.
**Controls monitored:**
| Control ID | Name | What is checked |
|------------|------|-----------------|
| `CC6.1` | Encryption at Rest | Database and secrets store encryption is active and configured |
| `CC6.7` | TLS Enforcement | TLS 1.2+ is enforced on all platform inbound connections |
| `CC7.2` | Audit Log Integrity | Audit chain hash continuity — shorthand of `/audit/verify` |
| `CC9.2` | Secrets Rotation | All managed secrets are within the rotation policy window |
| `CC7.1` | Webhook Dead-Letter Monitoring | Dead-letter queue depth is within the acceptable threshold |
**Status values:**
- `passing` — control is operating within policy
- `failing` — control has breached policy; immediate attention required
- `unknown` — automated check could not complete (e.g. dependency unavailable)
**Caching note:** Responses may be cached for up to 60 seconds by
intermediate proxies. The `lastChecked` field on each control indicates
the timestamp of the most recent automated evaluation.
**Rate limit:** 120 requests/minute per IP address.
security: []
responses:
'200':
description: SOC 2 control status summary returned successfully.
headers:
Cache-Control:
schema:
type: string
description: >
Downstream caches may serve this response for up to 60 seconds.
example: "public, max-age=60"
X-RateLimit-Limit:
schema:
type: integer
description: Maximum requests allowed per minute for this endpoint.
example: 120
X-RateLimit-Remaining:
schema:
type: integer
description: Requests remaining in the current rate limit window.
example: 119
X-RateLimit-Reset:
schema:
type: integer
description: Unix timestamp when the rate limit window resets.
example: 1743155400
content:
application/json:
schema:
$ref: '#/components/schemas/ComplianceControlsResponse'
examples:
allPassing:
summary: All controls passing
value:
controls:
- id: "CC6.1"
name: "Encryption at Rest"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC6.7"
name: "TLS Enforcement"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.2"
name: "Audit Log Integrity"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC9.2"
name: "Secrets Rotation"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.1"
name: "Webhook Dead-Letter Monitoring"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
oneControlFailing:
summary: One control failing (secrets rotation overdue)
value:
controls:
- id: "CC6.1"
name: "Encryption at Rest"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC6.7"
name: "TLS Enforcement"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.2"
name: "Audit Log Integrity"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC9.2"
name: "Secrets Rotation"
status: "failing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.1"
name: "Webhook Dead-Letter Monitoring"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
unknownControl:
summary: One control in unknown state (dependency unavailable)
value:
controls:
- id: "CC6.1"
name: "Encryption at Rest"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC6.7"
name: "TLS Enforcement"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.2"
name: "Audit Log Integrity"
status: "unknown"
lastChecked: "2026-03-31T05:00:00.000Z"
- id: "CC9.2"
name: "Secrets Rotation"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
- id: "CC7.1"
name: "Webhook Dead-Letter Monitoring"
status: "passing"
lastChecked: "2026-03-31T06:00:00.000Z"
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalServerError'