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-3xq8is 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_authof type
vcs_connectionminted 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_connectionis 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: falserestores the previous behaviour
if it blocks a workflow you rely on.
Web
nextraised to 16.3.8, clearing a critical RCE advisory
(GHSA-p498-v437-472g) andGHSA-vcvr-r3jv-pc5jin the published
terrapod-webimage.- 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.locationunvalidated. - 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_connectiongit 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
bespokeapi.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 mergeremoved, 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_boundtoken being able to mint a wider one; - a listener no longer re-joining across pools;
- Helm
| default truehonouring an explicitly configuredfalse, which would
change DB-pool and SMTP behaviour for an existingvalues.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