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 onlygit,ca-certificates,tiniand 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
opensslcommand are removed, and their CVEs with them. Terraform module sources therefore usegit::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,
RuntimeDefaultseccomp, 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-tokenChat 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_ALLOWLISTorrepos.yamllists only your repos -
TERIAC_PUBLIC_URLis your https URL - Secrets come from files or a secret store, not plain env vars
-
TERIAC_SESSION_SECRETis 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_destroyis limited -
require_apply_project: true, so applies are one project at a time - Policy checks run in
auditfirst, thenenforce - 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 -
/metricsis 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.