github mattrobinsonsre/terrapod v1.8.2

latest release: v1.7.7
3 hours ago

A security release for the 1.8 line, remediating an independent third-party
review of Terrapod that reported 49 findings.

The review was carried out by karl0r, who reported everything privately and
gave us time to fix it. Each finding has a GitHub security advisory crediting them as
reporter; those are published as one event once the last release in this
remediation programme ships, so that no advisory names a fix an operator cannot
yet take. The great majority of the findings are
fixed here; where a fix would have changed what a running 1.8 deployment does, it
is called out below rather than shipped quietly, and where it could not be made
drop-in it is named as deferred to 1.9.0.

Security

The fixes in this release, grouped by what an attacker could do before them.

Runs and state

  • A plan-only run could write a workspace's state. The upload endpoint had no
    phase, plan-only or lineage guard, so a run that was never allowed to change
    anything could replace the state it had only been given to read — and a foreign
    state at serial+1 was accepted outright. Uploads now carry the guard, and the
    lineage check no longer treats an absent lineage as a reason to skip itself.
  • Runner-reported OPA, scan and plan-JSON results were trusted as given. What
    a policy set is — mandatory or advisory, which policies it carries — is now
    read from the database rather than from the runner's own report, so a
    compromised runner cannot downgrade the gate that governs it.
  • A reduced plan now says it was reduced. GHSA-v677-9r29-3xq8 is that a
    mandatory AI policy gate can be bypassed by padding the plan past the size at
    which Terrapod reduces what it shows the model. Half of that is fixed here: the
    reduction is now declared rather than silent, and a destroy is never traded away
    for a create when the plan is trimmed. The gate refusing to rule on a plan it
    could not see in full is 1.9.0
    , because it changes when a run blocks. So on
    this line the padding is visible, not prevented — the advisory stays open.
  • The runner token outlived its phase. Called out as a limitation rather than
    fixed on this line: see Deferred to 1.9.0.

Credentials

  • A cloned-repo token is scoped to reading contents. A git_http_auth of type
    vcs_connection minted a token carrying every permission the GitHub App holds,
    across every repository in the installation, and handed it to a runner Job. It
    is now narrowed at the two call sites where a minted token leaves the API
    process, not merely at the default they share.
  • A Vault dynamic value source could reach Vault's own auth and sys
    self-service paths
    , including Terrapod's own token. Those paths are refused.
  • A vcs_connection is no longer attachable by anyone who can name one. A
    connection covers every repository its credential reaches and its id is
    serialised to anyone with read on a workspace using it, so the id is
    discoverable by design — naming one was therefore a grant. Creating or
    repointing a workspace, and the three registry-module paths that accept a
    connection, now require a claim to it. Platform admins are unaffected. If this
    blocks a workflow you rely on, vcs.require_connection_authorization: false
    restores the previous behaviour; it is on by default because the previous
    behaviour is the finding.

Web

  • next raised to 16.3.8, clearing a critical RCE advisory
    (GHSA-p498-v437-472g) and GHSA-vcvr-r3jv-pc5j in the published
    terrapod-web image.
  • Stored XSS in the 3D graph tooltips, which rendered untrusted node names as
    HTML. Node names come from state, so the payload needed only to be a resource
    name.
  • The login redirect parameter was written to window.location unvalidated.
  • AI output could fetch a remote image, which is an exfiltration channel for
    whatever the model was shown.
  • A Content-Security-Policy subset the application can actually satisfy is now
    sent. It is a subset deliberately: a full CSP is not attempted here, and this is
    defence in depth rather than the fix for any one finding.

SAML — assertion destination and recipient checks, request binding, replay
rejection and signature requirements. The five new checks default to permissive
on this line
, because switching them on can stop an existing IdP integration
working. One implementation, two sets of defaults: strict on main/2.0, opt-in
here. Turn them on deliberately after checking your IdP against
docs/sso.md.

Storage — a presigned upload could choose what the subsequent download
served, by setting a content type the download then echoed.

Audit — Slack approve and discard left no audit record. They do now.

Tooling that could not report anything. Two findings were not a missing
control but a check that passed while measuring nothing, and both had been green
for as long as they had existed: the CI secret scan ran gitleaks with zero
detection rules
, and the DAST templates asserted the healthy response, so
they fired on every correctly protected endpoint and were silent on a bypassable
one. Both are fixed and both now have guards, because each fix is one line that
reads as housekeeping.

terrapod-migrate — a TFE access tag no longer becomes a fleet-wide read
grant when migrating.

Three documentation commits correct claims the code does not support, and two
record behaviour that is deliberate and bounded rather than changed (the bootstrap
join token's missing expiry, and how an IdP group becomes a role).

Still true from 1.8.0, and worth repeating in a security release

A lagging runner silently evaluates a shared-evaluation policy set without its
data files.
shared_evaluation and support_files are additive, so a runner
image older than 1.8.0 ignores both and evaluates one policy at a time. A rule
that calls a helper from a support file then fails to compile and the set reports
errored, which is visible. But a rule that reads data.<key> compiles fine,
the data is absent, the rule never matches — and a mandatory gate reports a
clean pass it has not earned. Upgrade runners before enabling shared_evaluation
on any set. Sets that leave it off are unaffected on any runner version.

Breaking Changes

  • A run that pushed to a repository using a VCS-connection credential now
    fails with 403.
    The credential handed to a runner Job is narrowed to
    contents: read, which is the least that can still clone, so anything the run
    did beyond cloning — pushing a commit, opening a pull request — no longer works
    with it. That is the finding rather than a side effect: the old token carried
    every permission the GitHub App held across every repository in the
    installation. If a run genuinely needs to push, give it a static credential
    scoped to what it should reach.

  • A GitLab vcs_connection git credential now errors the run until a key is
    set.
    A GitLab access token cannot be attenuated — there is no narrower form to
    mint — so delivering one to a runner hands the runner everything that token can
    do. There is no safe default for a credential that cannot be narrowed, so it is
    off, and a run that relied on it fails loudly rather than silently continuing to
    over-share. Set the credential explicitly to opt back in.

Fork pull requests — read this before assuming you are protected

GHSA-gp5w-76rw-c452 (critical): a pull request from a fork, or from an
untrusted author, triggered a run with the workspace's credentials.

This release adds the control — a per-workspace allow_fork_pr_plans setting,
across the API, go-terrapod, the provider, MCP and the UI — and defaults it
ON
, which means a 1.8.2 deployment is not protected until you turn it off.
That is deliberate: defaulting it off would stop fork plans that work today on a
supported line, and a patch release does not remove working behaviour. 2.0
defaults it off.

So the action is yours: for any workspace whose repository takes pull requests
from people who should not be able to reach its credentials, set
allow_fork_pr_plans to false. docs/runbooks.md
has the curl and the provider attribute.

Correction to the docs inside this release. Two reference pages shipped here
state the opposite default and were found after the tag was cut:
docs/api-reference.md's workspace attribute table gives allow-fork-pr-plans as
false, and docs/vcs-integration.md opens its fork section with "defaults to
false". Both are wrong for 1.8.2 — it defaults true — and
docs/vcs-integration.md contradicts itself, saying "on by default on this release
line" further down. llms.txt and the UI are correct. The fix is on
release/v1.8 and ships with the next patch; this note is authoritative in the
meantime.

The UI now says so in every language. Writing these notes turned up that 26 of
the 33 locales stated 2.0's default — "off by default" — in the primary text beside
the toggle, and that every locale's autodiscovery hint contradicted itself ("on by
default: a fork pull request gets no speculative plan"). Both are corrected here.
An operator told the setting is already off would reasonably have concluded there
was nothing to do.

Deferred to 1.9.0

Named here so the gaps are not discovered by reading a diff. Each would change
what an existing deployment does, which is why it is not in a patch:

  • the engine-environment allowlist (GHSA-658f-j48w-w8m9), which could break a
    bespoke api.extraEnv;
  • a mandatory AI policy gate refusing to rule on a plan it could not see in full
    (GHSA-v677-9r29-3xq8), which changes when a run blocks;
  • terrapod merge removed, and PR-comment commands requiring push permission
    (GHSA-x4jp-5g4j-f8rr, critical — comment commands do not check the commenter's
    permission on this line);
  • a scoped service_bound token being able to mint a wider one;
  • a listener no longer re-joining across pools (a name collision 409s instead of
    silently moving the listener);
  • Helm | default true honouring an explicitly configured false — which would
    change DB-pool and SMTP behaviour for an existing values.yaml, including
    possibly stopping SMTP using TLS;
  • label-based authorization for VCS connections, which needs a schema change the
    current one does not.

Four of the 49 findings are not fixed because, on investigation, they did not
hold: an SSRF guard that resolves a host before judging it (so the unicode
spellings reported were already refused — there is a regression test for it now), a
status endpoint that does not return the peer address its report named, an
unauthenticated schema route on a repository that is public and ships its own route
contract, and a metadata address already denied on the port it uses. Those four
advisories stay unpublished rather than being published and then argued with.

Upgrading

Drop-in except for the GitLab credential above. One schema migration adds the
fork-PR column; it is additive, has a server default, and is sequenced so that a
1.8 deployment upgrading to 1.9.0 or 2.0 meets it exactly once.

1.7 reaches end of life with 1.9.0. v1.7.7 ships alongside this release with
the same fixes except the fork-PR control, which could not be sequenced onto both
lines. If you are on 1.7 and your workspaces serve repositories that take
outside pull requests, upgrading to 1.8.2 is the remedy.

Status

Stable.

Verification

All five images are published multi-arch at :v1.8.2, the Helm chart is 1.8.2
with appVersion: v1.8.2, and 44 assets are attached. Every pipeline gate passed,
including Eval Boot on both kind and k3d — those two were cancelled by an
operator error while the pipeline was finishing (after every build, manifest and
release job had already succeeded) and were re-run to completion, so the tag's run
is green end to end.

Full Changelog: v1.8.1...v1.8.2

Don't miss a new terrapod release

NewReleases is sending notifications on new releases.