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>
549 lines
21 KiB
YAML
549 lines
21 KiB
YAML
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'
|