SCP Security Overview¶
Version: 0.3.1 Owner: Ohana Consulting LLC Audience: Customer IT and security teams
Summary¶
The Supervisory Control Plane (SCP) governs AI agent behavior in regulated environments. Its security model is designed around one core requirement: if SCP cannot be trusted, the governance claim it enables collapses. Every security control in this document exists to protect that trust.
SCP's security posture covers seven domains, implemented in order of deployment criticality. This document describes what each domain protects against and how the protection works.
For the full technical reference — database schemas, algorithm details, file inventory — see security-architecture.md.
1. Audit Log Integrity¶
What it protects: Every agent context request is recorded. If that record can be altered after the fact, the compliance and governance story is worthless. The audit log must be tamper-evident and independently verifiable.
How it works:
- The audit table is append-only. PostgreSQL Row Level Security with
FORCEenforcement denies UPDATE and DELETE to all roles — including the application role. A trigger independently blocks any modification attempt. - Every record includes a SHA-256 hash chained to the record before it. Inserting, deleting, or modifying any record breaks the chain.
- The genesis hash is stored at deployment initialization and anchors the chain.
- A verification endpoint (
GET /api/audit/verify) walks the entire chain and reports pass/fail, record count, and the sequence number of any detected tampering. - Two separate database roles: the application writes via an insert-only role; audit queries use a read-only role.
2. Agent API Key Lifecycle¶
What it protects: Every agent authenticates with an API key. A compromised key with no revocation path is an unacceptable risk in a regulated deployment.
How it works:
- Keys carry an explicit status:
active,revoked, orexpired. Every authentication check queries status — not just whether the key exists. - Keys have a configurable expiry (default: 90 days). An hourly background job marks expired keys without manual intervention.
- Revocation is immediate:
DELETE /api/agents/{id}/api-keys/{key_id}marks the key revoked and records who revoked it and when. - When an agent is decommissioned, all of its active keys are revoked automatically.
3. Secrets Management¶
What it protects: Database credentials and JWT signing secrets must never be stored in source control, container images, or configuration files. Silent degradation to an insecure default is not acceptable.
How it works:
JWT_SECRETandDB_PASSWORDhave no defaults — the application refuses to start if either is absent from the environment.JWT_SECRETis validated at startup: known-weak values (e.g.,"secret","change-me-in-production") are explicitly rejected, and a minimum length of 32 characters is enforced.- Docker Compose uses
:?syntax —docker compose upfails immediately with a clear message if required secrets are not set. - In production: secrets are loaded from Azure Key Vault or AWS SSM Parameter Store, never from files on disk.
4. Transport Security¶
What it protects: All traffic between agents and SCP must be encrypted in transit. Plaintext connections to any external-facing endpoint are not acceptable in a regulated deployment.
How it works:
- Caddy reverse proxy terminates TLS on port 443 (API) and 8443 (context webhook). All agent traffic is HTTPS.
- In development: Caddy generates a self-signed certificate via
tls internal. In production: Caddy provisions a Let's Encrypt certificate automatically when pointed at a real hostname. - Application services bind to an internal Docker network only — ports 8000–8004 are not exposed directly to external networks.
- PostgreSQL connections enforce
sslmode=requirefor any network-exposed deployment.
5. Telemetry Beacon Security¶
What it protects: SCP sends daily usage metrics to Ohana for billing purposes. Customers must be able to verify exactly what data leaves their deployment, and the vendor must be able to verify that reports are authentic.
How it works:
- The beacon payload is a fixed, minimal set of six fields — no PII, no agent names, no SCD content, no request parameters:
| Field | Description |
|---|---|
deployment_id |
UUID assigned at installation |
license_key_hash |
SHA-256 of the license key — not the key itself |
agent_count |
Total registered agents |
active_agent_count |
Active agents |
reported_at |
ISO-8601 UTC timestamp |
scp_version |
SCP version string |
- When a signing secret is configured, each payload is signed with HMAC-SHA256 and transmitted as an
X-SCP-Signatureheader. - Every outbound report is stored locally. Customers can inspect the complete transmission log at any time:
GET /telemetry/logreturns the exact payload sent (or that would be sent) for each report.
6. License Key Enforcement¶
What it protects: Agent count limits are only meaningful if they can't be bypassed. The license key encodes customer entitlements and SCP enforces them at startup and on every agent registration.
How it works:
- License keys are RS256-signed JWTs issued by Ohana at customer activation. SCP holds the Ohana public key and verifies the signature on every load — the private key never leaves Ohana.
- The key encodes: customer identifier, deployment UUID, agent cap, tier (
standardorregulated), and expiry date. - At startup, SCP validates the license and logs a warning if expiry is within 30 days. An expired key aborts startup with a clear error message.
- On every
POST /api/agents, SCP checks the current count of non-revoked agents against the licensed cap. Requests that would exceed the cap are rejected with HTTP 402. - Revoked agents do not count toward the cap — decommissioned agents free up capacity immediately.
7. Admin API Authentication¶
What it protects: Bundle management, agent registration, API key issuance, and audit admin endpoints must not be accessible without authentication. An open admin API allows anyone with network access to register rogue agents, publish arbitrary bundles, or revoke legitimate credentials.
How it works:
- A static
ADMIN_API_KEYis configured at deployment time and stored in the environment (Azure Key Vault / AWS SSM in production). - All admin operations require an
X-Admin-Keyheader containing the admin key. Requests without it return HTTP 401. Requests with a wrong key return HTTP 401. Requests to a deployment whereADMIN_API_KEYis not set return HTTP 503 — the server fails loudly rather than silently open. - The comparison uses
secrets.compare_digestto prevent timing attacks. - Admin endpoints covered: all bundle registry operations, all agent registry operations (register, update, revoke, key management), and audit admin reads (
/audit/verify,/audit/requests,/audit/stream,/audit/chain/{id}). - Agent context operations (
POST /api/context) and agent output logging (POST /api/audit/output) continue to use per-agent API keys — they are not admin operations.
Deployment Checklist¶
Before any network-exposed deployment:
| Item | Requirement |
|---|---|
JWT_SECRET |
Cryptographically random, minimum 32 characters |
DB_PASSWORD |
Strong random value, minimum 24 characters recommended |
DEPLOYMENT_ID |
Stable UUID generated at installation |
DB_SSL_MODE |
Set to require |
CADDY_DOMAIN |
Set to real hostname (enables automatic TLS) |
| Ports 8000–8004 | Blocked from external networks; traffic enters via Caddy only |
ADMIN_API_KEY |
Cryptographically random; admin_ + 24 random hex bytes recommended |
LICENSE_KEY |
Provided by Ohana at activation |
Questions¶
Contact Ohana Consulting at tim@ohanaconsulting.com for security questions, penetration test coordination, or license key issues.