teriac server Security model

Security model

How teriac server keeps pull requests from changing their own rules, protects secrets and the apply path, and what to check before running it in production.

teriac server runs your Terraform with real cloud credentials, triggered by pull requests. This page explains what protects that path.

Threats and answers

Risk What teriac does
A webhook from someone who isn't GitHub Every webhook is checked against the App's webhook secret
Code from a repo you don't own Only repos on the allowlist are handled. With no allowlist, the server refuses to start
A PR that loosens its own rules Rules live in repos.yaml on the server. A repo's teriac.yaml can only add stricter rules, and it's read from the base branch
Applying something nobody reviewed Apply runs the saved plan, of the current commit, after approvals of that commit
Self-approval The author's approval never counts; the two-person rule stops authors applying
A plan built on wrong Terragrunt outputs Plans on mock_outputs can't be applied; outdated plans are refused
Running arbitrary commands from a comment Commands are parsed and arguments checked. Extra Terraform arguments are off by default
Editing teriac's comments to mislead reviewers Comments are never read back; edits trigger a warning and an audit entry
Destroying production Deletes and replaces need apply_destroy (maintain by default); policy rules can block them
Secrets in logs Known secret values are replaced with *** in logs, comments and the dashboard
teriac's own secrets reaching Terraform TERIAC_* variables are removed from the environment of every run
Cookie theft Sessions are signed, HttpOnly cookies, Secure on https; login attempts are rate limited
Third-party scripts in the dashboard The dashboard loads nothing from third parties

New in 0.2.0:

Risk What teriac does
A tampered Terraform download or mirror Checksum files are checked against the vendor's PGP signature
A plan file changed after review Its SHA-256 is checked before apply (verify_plan)
An approval carried over to new code A code change needs a fresh approval (reapprove_on_change); a pure rebase can keep it
Applying an out-of-date branch stale_base warns or blocks when main changed the project's files
Changes during launches or holidays Change freezes
Destroying a critical database or key Protected resources
Secrets printed in plan output The leaked-secret scan redacts them, or fails the plan
Urgent access with nobody to approve Break-glass grants: time-limited, audited and announced

New in 0.2.1:

Risk What teriac does
Losing the volume, and with it the audit log Scheduled backups to S3, GCS or Azure, restored on start
Known CVEs in unused image packages The image keeps only what the server runs; no critical CVEs left
An App with more access than it needs The permissions table lists exactly what each permission is for

The audit log

Every decision is recorded with who, what, which project and why: plans, applies (allowed or refused), unlocks, state edits, overrides, policy findings, and edits to teriac's comments. See it in the dashboard's Audit page or at GET /api/audit.

The container

  • Based on node:26-trixie-slim, with Debian security updates applied at build time, and only git, ca-certificates, tini and rclone (pinned and checksum-verified) added.
  • Only what the server runs is kept. npm, corepack, perl, the SSH client, apt, login and account tools, mount and disk utilities and the openssl command are removed, and their CVEs with them. Terraform module sources therefore use git::https:// (see private modules).
  • Runs as uid 10001, never root.
  • Tool binaries are downloaded on demand and checksum-verified, into /data.
  • With the Helm chart: read-only root filesystem, all capabilities dropped, RuntimeDefault seccomp, and an optional NetworkPolicy.

Secrets

Every secret setting can be read from a file instead of an env var: add _FILE to its name.

TERIAC_GITHUB_PRIVATE_KEY_FILE=/run/secrets/app.pem
TERIAC_GITHUB_WEBHOOK_SECRET_FILE=/run/secrets/webhook
TERIAC_SESSION_SECRET_FILE=/run/secrets/session
TERIAC_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc
TERIAC_API_TOKEN_FILE=/run/secrets/api-token

Chat tokens and webhook URLs are never written in repos.yaml: it names the env var that holds them (token_env, webhook_url_env).

Production checklist

  • TERIAC_REPO_ALLOWLIST or repos.yaml lists only your repos
  • TERIAC_PUBLIC_URL is your https URL
  • Secrets come from files or a secret store, not plain env vars
  • TERIAC_SESSION_SECRET is set (the Helm chart does this for you)
  • Dashboard sign-in is set up (OIDC recommended), or the dashboard is internal only
  • Only /events/ is public, if the dashboard is internal
  • Prod projects need approvals, and apply_destroy is limited
  • require_apply_project: true, so applies are one project at a time
  • Policy checks run in audit first, then enforce
  • Cloud access uses workload identity, scoped to what Terraform needs
  • Backups are on, with a lifecycle rule and an alert on teriac_backup_last_success_timestamp_seconds (the volume holds locks, history and the audit log)
  • podDisruptionBudget.enabled: true, so node drains don't interrupt an apply
  • /metrics is scraped and failed applies alert someone

Reporting a vulnerability

Please report security issues privately through GitHub's security advisories for the teriac repo, not in a public issue.