This document inventories every cryptographic primitive Optio uses, explains key management, and describes the post-quantum migration posture. It is the single reference for procurement security reviews, compliance audits, and engineering decision-making around cryptographic changes.
| Purpose | Algorithm | Key / output size | Implementation | PQ-safe? | Migration trigger |
|---|---|---|---|---|---|
| Secrets at rest | AES-256-GCM (NIST SP 800-38D) | 256-bit key, 96-bit IV, 128-bit auth tag | node:crypto via secret-service.ts |
Yes (Grover's reduces to 128-bit effective security) | NIST publishes AES successor |
| Session token storage | SHA-256 of random 32-byte token | 256-bit hash | session-service.ts |
Yes (256-bit pre-image resistance) | CNSA 2.0 compliance requirement |
| Internal API auth (pod ↔ API) | HMAC-SHA256 with replay protection | 256-bit secret | hmac-auth-service.ts |
Yes | None required |
| Webhook signing (outbound) | HMAC-SHA256 | Per-webhook secret | webhook-service.ts via crypto/signer.ts |
Yes | Optional ML-DSA-65 mode when customers demand it |
| Webhook verification (inbound GitHub) | HMAC-SHA256 + timingSafeEqual |
GitHub-configured secret | tickets.ts via crypto/signer.ts |
Yes | Follows GitHub's signing algorithm |
| GitHub App JWT | RS256 (RSA with SHA-256) | RSA private key (PEM) | github-app-service.ts via crypto/signer.ts |
No (low HNDL risk; JWT valid 10 min) | GitHub adds PQ-safe App auth |
| Credential secret derivation | SHA-256 of "{key}:credential-secret" |
256-bit derived key | credential-secret-service.ts |
Yes | None required |
| Envoy sidecar CA | Ed25519 self-signed X.509, 30-day validity | 256-bit key | envoy-sidecar.ts via openssl CLI |
No (ephemeral, intra-pod only) | Set OPTIO_ENVOY_CA_ALG=mldsa44 when OpenSSL 3.5+ is available |
| Postgres TLS (in-cluster) | Helm-generated self-signed CA, 10-year validity | RSA 2048 (Helm genCA default) |
helm/optio/templates/postgres-tls.yaml |
No (cluster-internal) | Helm adds PQ CA generation |
| Redis TLS (in-cluster) | Helm-generated self-signed CA, 10-year validity | RSA 2048 (Helm genCA default) |
helm/optio/templates/redis-tls.yaml |
No (cluster-internal) | Helm adds PQ CA generation |
| Outbound TLS (fetch to APIs) | TLS 1.3 via OpenSSL (X25519MLKEM768 hybrid when upstream supports) | Session keys | Node.js bundled OpenSSL | Yes when upstream supports hybrid | Upstream providers ship hybrid TLS |
| Kubernetes API TLS | Whatever the cluster negotiates | Session keys | @kubernetes/client-node |
Yes on K8s >= 1.33 (Go 1.24 default) | Cluster upgrade |
| WebSocket upgrade tokens | SHA-256 of random 32-byte token | 256-bit hash, 30-second TTL | session-service.ts |
Yes | Same as session tokens |
- Crypto abstraction layer:
apps/api/src/services/crypto/signer.tsdefinesSignerandVerifierinterfaces with implementations:Rs256Signer,HmacSha256Signer,HmacSha256Verifier, and a stubMlDsa65Signerfor future PQ support. - Timing-safe comparison: Used in session validation, WebSocket token lookup, webhook verification, and internal HMAC auth — all via
crypto.timingSafeEqual(). - Replay protection: Both inbound GitHub webhooks (
tickets.ts, 5-minute window) and internal pod auth (hmac-auth-service.ts, 5-minute window) validate timestamps to prevent replay attacks.
Every entry in the secrets table is encrypted with AES-256-GCM. The table below
matches each secret type to its expected shelf life and Mosca calculation.
The Mosca inequality determines when to begin migration:
If x + y > z, you must migrate now.
Where:
- x = security shelf life (how long the data must remain confidential)
- y = migration time (how long it takes to deploy new crypto)
- z = time until a cryptographically relevant quantum computer (CRQC) exists
Current industry estimates place z at 10–15 years (2035–2040). Optio's migration time (y) is estimated at 1–2 years for each primitive.
| Secret type | Example | Shelf life (x) | x + y | Migrate now? | Notes |
|---|---|---|---|---|---|
API keys (ANTHROPIC_API_KEY) |
Anthropic API key | Short (rotatable, < 1 year) | 2–3 years | No | Rotate regularly; HNDL risk is low |
OAuth tokens (CLAUDE_CODE_OAUTH_TOKEN) |
Claude subscription token | Short (< 90 days) | 1–2 years | No | Auto-expires; cannot be used retroactively |
GitHub tokens (GITHUB_TOKEN) |
PAT or installation token | Short (rotatable) | 2–3 years | No | Should be rotated periodically |
| Webhook secrets | HMAC signing keys | Medium (1–3 years typical) | 3–5 years | No | Only protects integrity, not confidentiality |
Encryption key (OPTIO_ENCRYPTION_KEY) |
Master AES-256 key | Long (life of deployment) | 10+ years | Monitor | AES-256 remains PQ-safe; risk is key exposure, not algorithm break |
| GitHub App private key | RSA PEM | Medium (1–3 years) | 3–5 years | No | JWTs expire in 10 minutes; HNDL value is minimal |
| Session tokens | User auth tokens | Short (30-day TTL) | 1–2 years | No | Only hash is stored; token is ephemeral |
Conclusion: No secret type currently triggers the Mosca inequality for immediate migration. AES-256-GCM (the only long-shelf-life primitive) is PQ-safe. The primary area to monitor is the GitHub App RSA key, which is not PQ-safe but has very low HNDL (harvest-now, decrypt-later) value due to 10-minute JWT expiry.
- Generation:
openssl rand -hex 32(256 bits of entropy). - Storage: Kubernetes Secret (
helm/optio/templates/secrets.yaml), set viaencryption.keyin Helm values. - Parsing (
secret-service.ts):- If the value is a 64-character hex string: decoded directly as 32 bytes.
- Otherwise: SHA-256 hashed to derive a 32-byte key. This fallback exists for convenience but hex input is strongly preferred.
- Weak key detection: At startup,
getEncryptionKey()rejects known-weak values ("change-me-in-production","changeme","test","secret","password","default") and throws an error. - Rotation: Currently manual. To rotate:
- Generate a new key:
openssl rand -hex 32 - Re-encrypt all secrets in the
secretstable with the new key - Update
encryption.keyin Helm values - Redeploy the API
- Automated rotation tooling is planned but not yet implemented.
- Generate a new key:
- Derivation (
credential-secret-service.ts):- If
OPTIO_CREDENTIAL_SECRETenv var is set: used directly. - Otherwise: derived as
SHA-256("{OPTIO_ENCRYPTION_KEY}:credential-secret").
- If
- Helm template (
secrets.yamlline 60):This ensures the Helm-deployed value matches the runtime derivation.OPTIO_CREDENTIAL_SECRET: { { printf "%s:credential-secret" .Values.encryption.key | sha256sum } }
- Purpose: Used by agent pods to authenticate to the API via HMAC signatures. The secret itself never crosses the wire — only HMAC signatures are transmitted.
- Per-webhook: Each webhook has its own secret, stored AES-256-GCM encrypted
in the database with AAD bound to
webhook:{url}:secret. - GitHub inbound:
GITHUB_WEBHOOK_SECRETis stored as a global secret, configured during setup.
- Storage:
GITHUB_APP_PRIVATE_KEYenv var, delivered via Kubernetes Secret. - Format: PEM-encoded RSA private key.
- Used for: Signing 10-minute JWTs to obtain GitHub App installation tokens.
| Primitive | Why it's safe |
|---|---|
| AES-256-GCM (secrets at rest) | Grover's algorithm reduces effective security to 128 bits, still well above the security margin. No known quantum speedup for GCM authentication. |
| SHA-256 (session tokens, credential derivation) | Grover's reduces pre-image resistance to 128 bits. Collision resistance reduced to 2^128 (still sufficient). |
| HMAC-SHA256 (webhooks, internal auth) | Same SHA-256 reasoning; HMAC construction adds no quantum vulnerability. |
| Kubernetes API TLS on K8s >= 1.33 | Go 1.24 defaults to X25519MLKEM768 hybrid key exchange. See docs/pq-readiness.md. |
| Outbound TLS to PQ-capable servers | Node.js / OpenSSL negotiate X25519MLKEM768 when the server supports it. |
| Primitive | Risk | Blocker | Tracking |
|---|---|---|---|
| GitHub App JWT (RS256) | Low — JWTs expire in 10 min, minimal HNDL value | GitHub must add PQ-safe App authentication | #324 |
| Envoy sidecar CA (Ed25519) | Very low — ephemeral, intra-pod, 30-day validity | OPTIO_ENVOY_CA_ALG=mldsa44 ready when OpenSSL 3.5+ ships ML-DSA |
#326 |
| Helm-generated Postgres/Redis TLS CAs | Low — cluster-internal only | Helm genCA must support PQ algorithms |
#327 |
| Outbound TLS to non-PQ servers (GitHub, Anthropic APIs) | Medium — depends on upstream | Upstream providers must enable hybrid TLS | #325 |
All post-quantum work is tracked under the post-quantum label:
- #324 — GitHub App PQ JWT migration
- #325 — Outbound TLS PQ hybrid enforcement
- #326 — Envoy sidecar ML-DSA CA migration
- #327 — Helm TLS CA PQ migration
- #328 — ML-DSA-65 webhook signing option
- #329 — AES-256-GCM V2 with mandatory AAD binding
- #330 — KMS-wrapped encryption key support
- #331 — PQ TLS metrics and observability
- #333 — This document (cryptography inventory)
The audit identified two categories of crypto-agility improvements:
-
Algorithm selector pattern: Already implemented for the Envoy CA (
OPTIO_ENVOY_CA_ALGenv var) and GitHub App JWT (GITHUB_APP_JWT_ALG). Thecrypto/signer.tsabstraction layer supports pluggable algorithms —MlDsa65Signeris stubbed and ready for implementation when Node.js crypto adds ML-DSA support. -
Ciphertext versioning:
secret-service.tsalready includes algorithm version bytes (ALG_AES_256_GCM_V1 = 0x01,ALG_AES_256_GCM_V2_AAD = 0x02). This allows transparent migration to new encryption schemes without re-encrypting all existing data at once.
| CNSA 2.0 requirement | Deadline | Optio status |
|---|---|---|
| Prefer PQ algorithms for new software | 2025 | Partial — symmetric crypto (AES-256, SHA-256, HMAC) is compliant; asymmetric (RSA, Ed25519) pending upstream support |
| Software/firmware signing: ML-DSA | 2025 | N/A — Optio does not sign software releases |
| Web browsers/servers: ML-KEM + ML-DSA | 2025 | Partial — TLS 1.3 with ML-KEM hybrid available; ML-DSA TLS not yet standard |
| Traditional public-key exclusive disuse | 2033 | On track — all non-PQ primitives have identified migration paths with external blockers (GitHub, Helm) |
| Symmetric algorithms | No change required | Compliant — AES-256 and SHA-256/384 are CNSA 2.0 approved |
Optio does not currently claim FIPS 140-3 compliance. Relevant considerations:
- Node.js OpenSSL: Node.js can be built with OpenSSL in FIPS mode
(
--openssl-is-fips), but the standard Node.js distribution is not FIPS-validated. - AES-256-GCM: FIPS-approved algorithm (SP 800-38D).
- SHA-256, HMAC-SHA256: FIPS-approved (FIPS 180-4, FIPS 198-1).
- Ed25519: Included in FIPS 186-5 (2023), but the OpenSSL implementation used by the Envoy sidecar may not be FIPS-validated.
- RS256: FIPS-approved (FIPS 186-4).
To achieve FIPS 140-3 compliance, Optio would need to:
- Use a FIPS-validated Node.js build or OpenSSL provider
- Ensure all crypto operations go through the validated module boundary
- Restrict key sizes and algorithms to FIPS-approved values
- Undergo formal validation (or use a validated crypto module)
Use tls.connect() from inside the API pod to probe what key exchange group is
negotiated with a given upstream:
kubectl exec deploy/optio-api -n optio -- node -e '
const tls = require("tls");
const targets = [
{ host: "api.anthropic.com", port: 443 },
{ host: "api.github.com", port: 443 },
{ host: "kubernetes.default.svc", port: 443 },
];
for (const t of targets) {
const sock = tls.connect(t.port, t.host, { servername: t.host }, () => {
const info = sock.getEphemeralKeyInfo?.();
console.log(`${t.host}: group=${info?.name ?? "unknown"} proto=${sock.getProtocol()}`);
sock.end();
});
sock.on("error", (e) => console.log(`${t.host}: error=${e.code}`));
}
'If the upstream supports hybrid PQ, you will see group=x25519_mlkem768.
kubectl exec deploy/optio-api -n optio -- \
openssl s_client -connect api.github.com:443 \
-groups X25519MLKEM768:X25519 \
-brief 2>&1 | grep -E 'Protocol|Server Temp Key|Groups'If the server supports ML-KEM hybrid, the output will show
Server Temp Key: X25519MLKEM768.
See docs/pq-readiness.md for the full verification procedure.
On K8s >= 1.33, the expected key exchange group is x25519_mlkem768.
# From inside an agent pod:
openssl x509 -in /etc/envoy/ca/ca.crt -noout -text | grep 'Public Key Algorithm'
# Expected: ED25519 (current) or id-ML-DSA-44 (after PQ migration)The optio_tls_handshake_total Prometheus metric will expose:
optio_tls_handshake_total{target="api.github.com", group="x25519", version="TLSv1.3"} 42
optio_tls_handshake_total{target="kubernetes.default.svc", group="x25519_mlkem768", version="TLSv1.3"} 108
Use this to monitor PQ adoption across all outbound connections without manual probing.