Skip to content

Operator Tokens

The operator token is a pre-shared secret that gates destructive-but-routine admin endpoints across services. Each service holds its own independent token; tokens are never shared across service boundaries.

Auth model

Requests carry the token in:

  • X-Operator-Token header — executor, risk, and manager bot-lifecycle endpoints.
  • Authorization: Bearer <token> header — POST /bff/emergency-flatten on gordon-manager.
ConditionHTTP statusCode
Token not configured at startup503<SERVICE>_UNAUTHORIZED
Token missing or wrong401<SERVICE>_UNAUTHORIZED
Body missing or invalid400
Success200

Fail-closed: if the GORDON_<SERVICE>_OPERATOR_TOKEN env var is absent or empty at startup, guarded endpoints return 503. There is no insecure fallback.

No oracle: missing-token and wrong-token responses are identical — the caller cannot distinguish which check failed.

Constant-time comparison: all implementations use constant-time equality to prevent timing side-channel attacks.

Token surfaces

TokenEnv varHolderPurpose
Manager operator tokenGORDON_MANAGER_OPERATOR_TOKENgordon-console (server-side), gordon-managerPrimary browser-to-manager token. Console injects as X-Operator-Token on every BFF mutation.
Risk operator tokenGORDON_RISK_OPERATOR_TOKENgordon-riskGates POST /emergency-flatten, POST /risk/resume, POST /bots/:id/pause on gordon-risk directly.
Executor operator tokenGORDON_EXECUTOR_OPERATOR_TOKENgordon-executorGates POST /clear-quarantine, POST /flatten on gordon-executor. Manager-only forward token.
Manager→Risk forward tokenGORDON_MANAGER__GORDON_RISK_OPERATOR_TOKENgordon-managerManager's token for proxying operator commands to gordon-risk. Set only on gordon-manager.

BFF boundary note

The canonical v7 design has gordon-manager as the sole browser-facing token surface for write actions. Gordon-console holds only GORDON_MANAGER_OPERATOR_TOKEN. Manager re-authenticates to gordon-risk and gordon-executor using its own forward tokens.

Current drift (DP-12, open): gordon-console still holds GORDON_RISK_OPERATOR_TOKEN and calls POST /risk/resume directly on gordon-risk. The manager /bff/risk/resume proxy is designed but not yet wired. When DP-12 closes, GORDON_RISK_OPERATOR_TOKEN will be removed from the console's env.

Service isolation table

A compromised token for one service grants no access to any other service.

ServiceEnv varVault keyEndpoints guarded
gordon-executorGORDON_EXECUTOR_OPERATOR_TOKENvault_gordon_executor_operator_tokenPOST /clear-quarantine, POST /flatten
gordon-riskGORDON_RISK_OPERATOR_TOKENvault_gordon_risk_operator_tokenPOST /emergency-flatten, POST /risk/resume, POST /bots/:id/pause
gordon-managerGORDON_MANAGER_OPERATOR_TOKENvault_gordon_manager_operator_tokenPOST /bff/emergency-flatten, POST /bots/:id/clear-quarantine

Production provisioning

Production values live in the Ansible vault:

homelab/group_vars/all/vault.yml
  → vault_gordon_executor_operator_token
  → vault_gordon_risk_operator_token
  → vault_gordon_manager_operator_token

Rotation procedure (repeat per service)

  1. Generate a new 256-bit random hex secret:
    bash
    openssl rand -hex 32
  2. Update the relevant vault key.
  3. Run ansible-playbook deploy.yml --tags gordon-<service>.
  4. The service restarts with the new token — no DB changes required.
  5. Verify (example for executor):
    bash
    curl -H "X-Operator-Token: <new>" http://srv-apps:8085/clear-quarantine
    # Should return 400 (missing body), not 401/503
  6. Repeat verification for each guarded endpoint on the rotated service.

Local development

Each service's docker-compose.yml sets a safe default:

ServiceDefault dev value
gordon-executore2e-operator-token
gordon-riske2e-operator-token
gordon-managere2e-operator-token

Override by setting the env var before docker compose up. E2E tests use these defaults; no secrets needed locally.

Error codes

ServiceError codeLog level
gordon-executorEXECUTOR_UNAUTHORIZEDwarn
gordon-riskRISK_UNAUTHORIZEDwarn
gordon-managerMANAGER_UNAUTHORIZEDwarn

Gordon — keep compounding without blowing up