Skip to content
Kodakodadocs
Operations

Security

Account lockout, session hardening, CSP, and audit events.

Koda is shipped to run agents in production. Its security posture is built around three ideas: fail closed on required infrastructure, log everything that could matter later, and don't allow accidental downgrades from hardened defaults. This page summarises the controls in place.

Authentication

  • Single-user owner account created on first boot. No self-service registration beyond that.
  • Password policy — 12+ characters, 3 of 4 character classes, no identifier substring, not in the top-500 common passwords list, Shannon entropy ≥ 2.0 bits/char.
  • Sessions — 32-byte URL-safe random token, SHA-256 hashed in storage. Default TTL 7 days. Transport is the encrypted koda_operator_session cookie with HttpOnly; Secure; SameSite=Strict.
  • Recovery codes — ten Argon2-hashed single-use codes, shown once on registration. All remaining codes invalidate when any one is used.

Session revocation

  • Logout — revokes the current session.
  • Password change — revokes every other session; keeps the initiating session active.
  • Password reset via recovery code — revokes all sessions.

Rate limits and lockout

Per-IP buckets back every auth-adjacent route. Response latency is floored at ~300 ms on failure paths to blunt timing-based enumeration.

  • POST /auth/login — 5 req / 5 min / IP.
  • POST /auth/password/recover — 5 req / hour / IP.
  • POST /auth/password/change — 10 req / hour / user.
  • POST /auth/register-owner — 3 req / hour / IP.
  • General operator bucket — 120 req / min (via CONTROL_PLANE_RATE_LIMIT).
  • Account lockout — after CONTROL_PLANE_OPERATOR_LOGIN_MAX_FAILURES (default 5) the account locks for CONTROL_PLANE_OPERATOR_LOGIN_LOCKOUT_SECONDS (default 15 min).

Production refusals

Several combinations are refused at boot when KODA_ENV=production. Koda exits before accepting traffic.

  • CONTROL_PLANE_AUTH_MODE=development or open.
  • ALLOW_LOOPBACK_BOOTSTRAP=true.
  • ALLOW_INSECURE_COOKIES=true.
  • ALLOW_INSECURE_WEB_OPERATOR_SESSION_SECRET=true.
These exist for a reason
The development-mode flags make local work friction-free. They are not security theatre — the process refuses to start with them on in production. You cannot accidentally deploy a debug-friendly config.

Content Security Policy

/login, /setup, and /forgot-password enforce a strict CSP with no 'unsafe-inline' or 'unsafe-eval'. Every script is served with a nonce. Patching 'unsafe-inline' back in to work around a third-party script would break the hardening that keeps credential-entry pages resistant to XSS.

Bootstrap flow

  • Code generation. Short-lived bootstrap codes are written to $${STATE_ROOT_DIR}/control_plane/bootstrap.txt with mode 0600, printed once to the container log, and deleted after successful registration.
  • Loopback trust. On development hosts with ALLOW_LOOPBACK_BOOTSTRAP=true, requests originating from 127.0.0.1 skip the code challenge. Refused in production.
  • Regeneration. koda auth issue-code prints a new code without restarting the stack. Rate limited to prevent flooding.

Secrets at rest

Every secret (provider credentials, bot tokens, integration passwords, per-agent secrets) is encrypted before it touches Postgres using the master key pointed at by CONTROL_PLANE_MASTER_KEY_FILE. The key file itself sits outside the database, owned by the service user with mode 0600, and is the single item you need to protect most carefully on the host.

Runtime validation

The security gRPC service (:50065) validates every risky runtime operation before it executes:

  • Shell commands — checked for injection patterns and blocked-command lists.
  • Environment variables — sanitised to remove sensitive keys before handoff to child processes.
  • Runtime paths — validated for traversal attempts and resolved to absolute paths.
  • S3 object keys — validated against the bucket's permitted prefix.
  • Log values — redacted before emission (credentials, tokens, long opaque strings).
  • Filesystem policy — reads and writes are checked against the agent's declared filesystem scope.

Audit trail

Every security-sensitive event writes a structured security.* record through the emit_security() helper. Events are queryable from the dashboard's Operations view and from /api/control-plane/audit (when audit export is enabled).

Representative event names:

  • security.auth.login_success / security.auth.login_failure
  • security.auth.password_changed / security.auth.password_reset
  • security.auth.recovery_code_used
  • security.session.created / security.session.revoked
  • security.telegram.rejected
  • security.runtime.shell_blocked

Hardening checklist

  • KODA_ENV=production, ALLOW_LOOPBACK_BOOTSTRAP=false.
  • HTTPS everywhere with Secure, HttpOnly, SameSite=Strict cookies.
  • Reverse proxy fronting 127.0.0.1 bindings; Postgres and SeaweedFS never exposed publicly.
  • .env, master key, and bootstrap files owned by the service user, mode 0600.
  • WEB_OPERATOR_SESSION_SECRET set to 32+ random bytes and rotated on a schedule.
  • CONTROL_PLANE_API_TOKEN blank unless needed; rotate when set.
  • Audit events exported to your SIEM of choice.
Reporting a vulnerability

Find something concerning? The repo's SECURITY.md is the authoritative disclosure path. Do not file public issues for vulnerabilities.

Next steps