github mattrobinsonsre/terrapod v1.7.7

2 hours ago

A security release for the 1.7 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.

Read the fork pull request section first. This release does not carry the
fix for the review's one critical finding that applies to this line, and the
remedy is v1.8.2.

Fork pull requests — this line does not get the fix

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

The control for it is a per-workspace setting, which needs a schema migration. A
migration can only be sequenced onto one release line without a deployment
meeting it twice on the way to 2.0, and that line is 1.8. So 1.7.7 does not
carry it
, and no amount of configuration on 1.7 closes it.

If any workspace on a 1.7 deployment serves a repository that accepts pull
requests from people who should not be able to reach that workspace's
credentials, upgrade to v1.8.2 and set allow_fork_pr_plans to false on
those workspaces. Note that 1.8.2 defaults the setting on, so the upgrade
alone is not the fix — the setting is.

1.7 reaches end of life with 1.9.0. This is the second-to-last release of the
line; plan the move to 1.8.

Security

Everything else in the review that applies to this line and could be made
drop-in is fixed here, identically to v1.8.2.

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 never allowed to change anything
    could replace the state it was only given to read, and a foreign state at
    serial+1 was accepted. The lineage check also 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, and which policies it carries — is
    now read from the database, 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. (This half was initially recorded as not applying
    to 1.7; measured, that was wrong — the defect is equally present here.)

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.
  • A Vault dynamic value source could reach Vault's own auth and sys
    self-service paths
    , including Terrapod's own token.
  • 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 naming one was a
    grant. Platform admins are unaffected;
    vcs.require_connection_authorization: false restores the previous behaviour
    if it blocks a workflow you rely on.

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 — and node names come from state, so the payload need only be a resource
    name.
  • The login redirect parameter was written to window.location unvalidated.
  • AI output could fetch a remote image, an exfiltration channel for whatever
    the model was shown.
  • A Content-Security-Policy subset the application can actually satisfy, as
    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. Strict on 2.0, opt-in here; enable them deliberately after checking your
IdP.

Storage — a presigned upload could choose what the subsequent download served.

Audit — Slack approve and discard left no audit record.

Tooling that could not report anything. Two findings were not a missing
control but a check that passed while measuring nothing, both 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 correctly
protected endpoints and were silent on bypassable ones. Both fixed, both now
guarded.

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

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, so delivering one to a runner
    hands the runner everything that token can do. A credential that cannot be
    narrowed has no safe default, so it is off and the run fails loudly rather than
    continuing to over-share. Set the credential explicitly to opt back in.

Deferred to 1.9.0

Named so the gaps are not discovered by reading a diff — each would change what an
existing deployment does:

  • 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;
  • Helm | default true honouring an explicitly configured false, which would
    change DB-pool and SMTP behaviour for an existing values.yaml;
  • label-based authorization for VCS connections, which needs a schema change.

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. No schema migration.

Status

Stable. Security fixes only; 1.7 reaches end of life with 1.9.0.

Full Changelog: v1.7.6...v1.7.7

Don't miss a new terrapod release

NewReleases is sending notifications on new releases.