Skip to content

Post-Security-Hardening Operational Runbook

Date: 2026-04-01 Context: After applying 14 security fixes (C-1 through L-3) to the seat manager, the following operational items require attention before running in production with writes enabled.

1. Signing Key Isolation

The API currently loads SEAT_MANAGER_OWNER_PRIVATE_KEY into the same process that serves HTTP. This means a remote code execution vulnerability in the API process would expose the treasury signing key.

Current state: API runs with writes=disabled. Signing key is not loaded.

Before enabling writes:

  • Migrate to KMS-backed signing via SEAT_MANAGER_KMS_KEY_ID (AWS KMS). The raw private key never leaves the HSM.
  • If KMS is not available, ensure the API process runs in a restricted network namespace with no inbound access from the public internet. The admin frontend should proxy through a separate gateway.
  • Never set SEAT_MANAGER_OWNER_PRIVATE_KEY and expose the API on a public interface simultaneously.

2. HMAC Secrets Must Be Persisted

The C-1 fix made SESSION_HMAC_SECRET and STEP_UP_HMAC_SECRET mandatory. These are currently set only in the shell environment of the running process. They will be lost on reboot.

Action required:

  • Store both secrets in a .env file at the project root with chmod 600 owned by the service user, or use a secrets manager.
  • If the secrets are lost, all active sessions become invalid (users must re-login). No data loss, but operational disruption.
  • Never commit these secrets to git.

Current values were generated on 2026-04-01:

SESSION_HMAC_SECRET=34c0c421cc509d876d9a7bded5139fb29118bceadf29d0231d42fa20aa8c7073
STEP_UP_HMAC_SECRET=efce4c68b40a6329962f522f3208f961966161df384d7ccfd8a4f1005d1c2a9f

These should be rotated periodically. Rotation invalidates all existing sessions.

3. Admin Password Rotation

The current admin password (Admin!Temp2026#) appears to be a temporary credential. For a system that authorizes 32 CTN validator deposits, this must be rotated.

Action required:

  • Log in to the admin console and change the password via the user management page, or use the API:
    POST /v1/users/<user-id>/password
    
  • Use a password manager to generate a high-entropy password (20+ characters, mixed case, symbols, digits).
  • After rotation, verify login works before closing the old session.

4. Required Environment Variables (Post-Hardening)

The following env vars are now mandatory in non-test environments:

Variable Purpose Notes
SESSION_HMAC_SECRET HMAC key for session token hashing 256-bit hex, must be high-entropy
STEP_UP_HMAC_SECRET HMAC key for step-up token signing 256-bit hex, must be high-entropy
DATABASE_URL PostgreSQL connection string Required by Prisma

The following are required when writes are enabled:

Variable Purpose Notes
SEAT_MANAGER_OWNER_PRIVATE_KEY Treasury signing key (local mode) Prefer KMS instead
SEAT_MANAGER_KMS_KEY_ID AWS KMS key ID (preferred over local key) Requires AWS credentials

The following are optional but affect security posture:

Variable Default Purpose
SEAT_MANAGER_STATIC_ADMIN_TOKEN_SUNSET none ISO-8601 date after which static token auth is rejected
SEAT_MANAGER_SECURE_COOKIES true in production Set Secure flag on session cookies
SEAT_MANAGER_VERIFY_DEPOSIT_SIGNATURE true BLS signature verification on deposit-data submission
SEAT_MANAGER_REQUIRE_STAKER_AUTH true Require auth on staker-facing read routes (set to false only for isolated local development)

5. Database Migration Checklist

After any code update that modifies prisma/schema.prisma, run:

npx prisma migrate status          # Check for pending migrations
npx prisma migrate deploy          # Apply pending migrations (production)
npx prisma generate                # Regenerate Prisma client
npm run build                      # Rebuild dist/

Never start the API if prisma migrate status shows unapplied migrations. The Prisma client will reference columns that don't exist and requests will fail with 500 errors.

6. Restart Procedure

# 1. Ensure migrations are current
npx prisma migrate status

# 2. Build
npm run build

# 3. Stop existing process
fuser -k 8080/tcp

# 4. Start with required env vars
SESSION_HMAC_SECRET=<secret> \
STEP_UP_HMAC_SECRET=<secret> \
SEAT_MANAGER_OWNER_PRIVATE_KEY=<key>  \
node dist/index.js api --port 8080 --host 127.0.0.1

# 5. Verify
curl -s http://127.0.0.1:8080/health

7. Pro Audit Addendum (2026-04-10): CSM Controls

The 2026-04-10 professional audit introduced additional controls (CSM-01 through CSM-04). Use this matrix to verify they remain enforced after every security-sensitive change.

Finding Control (must remain true) Primary routes
CSM-01 API rejects caller-supplied treasury; trusted treasury is server-controlled and enforced before irreversible seat ops POST /v1/seats/generate, POST /v1/seats/with-vault, POST /v1/seats/:id/(approve|deposit|settle|force-exit)
CSM-02 Session admins must pass step-up for irreversible operations; static admin bearer is blocked on step-up routes POST /v1/seats/generate, POST /v1/seats/:id/(settle|force-exit)
CSM-03 Global audit and dashboard reads are ADMIN-only GET /v1/audit-logs*, GET /v1/dashboard/(health|alerts)
CSM-04 Staker API key reads on pubkey routes must be address-bound via X-Staker-Address GET /v1/validator/:pubkey*

8. CSM Regression Test Map

Run these tests on every PR touching authz, schemas, or seat lifecycle handlers:

npx vitest run test/security.test.ts test/schemas.test.ts -t "CSM-01 regression"
npx vitest run test/security.test.ts -t "CSM-02 regression"
npx vitest run test/security.test.ts -t "CSM-03 regression"
npx vitest run test/security.test.ts -t "CSM-04 regression"

Exact regression test names:

Finding Test name
CSM-01 CSM-01 regression: POST /v1/seats/generate rejects caller treasury override
CSM-02 CSM-02 regression: POST /v1/seats/generate requires step-up for session admins
CSM-03 CSM-03 regression: GET /v1/audit-logs rejects VIEWER
CSM-04 CSM-04 regression: /v1/validator/:pubkey requires X-Staker-Address