Upgrade notes
Notes for operators of a deployment that ran earlier code. Each entry describes an operator action required by an upgrade, including credential exposure and database migrations. An entry is keyed by the condition that makes your deployment affected and by the commit that fixed it, not by a version number: the packages are unversioned, so a commit is the only identifier a deployment can compare against.
Read an entry when its Affected line matches your deployment. An entry that does not match needs nothing from you.
Act first, read second. Kubernetes, or Docker with command-line or event capture, and a worker built before
3200071c: rotate PATs, per-host PATs and user tokens now (UN-1 steps 1 and 2), and any providerapiBasetoken that has a path or query (UN-3), then read the entries. A Docker worker with no model proxy hands the provider API key to code the agent runs at every commit (UN-5).Standing exposure, whatever your build: host- or node-level process auditing records the git credential from git’s own argument list inside each workspace on every authenticated call (details). Upgrading does not end it.
1. Is your worker running a fix?
Section titled “1. Is your worker running a fix?”Every SHA below is a commit on main. The worker image carries the commit it was built from:
# in the running worker container (or: docker inspect <image> --format '{{.Config.Env}}')printenv COMMIT_HASHThe shipped image workflow also ends each image tag with the short commit
(<timestamp>-<commit>). COMMIT_HASH reads unknown on an image built without that build
argument; for such an image, compare the source it was built from instead. With a clone of the
repository:
git merge-base --is-ancestor <fix-sha> <your-build-sha> && echo "contains the fix"“Contains” means the build commit descends from the fix commit. An image built by a pull-request
run takes COMMIT_HASH from the pull request’s merge ref, which is not a commit on main, so the
command above can fail for it; use an image built from a main commit, or compare the source it
was built from. Only the worker image matters for every entry here: the gateway and web images
never run a workspace command.
Standing exposure: the credential in a workspace’s git argument list
Section titled “Standing exposure: the credential in a workspace’s git argument list”This is current behaviour on every build, including the fixed ones, not a defect with a fix commit.
Each authenticated git call runs inside the workspace, and the shell there expands
$ASW_GIT_AUTH into git’s own argument list as -c http.extraheader=AUTHORIZATION: basic <base64>
(base64 of x-access-token:<token>). On a runc runtime a container process is a host process, so
anything that records process arguments on the Docker host or on a Kubernetes node (auditd
EXECVE, EDR agents, Falco proc.cmdline) records that header on every authenticated call, at
every commit, on both backends. A sandboxed runtime (gVisor, Kata Containers, the VM behind Docker
Desktop) may keep those processes out of the host’s view; verify that in your environment before
relying on it. The fixes moved the value off the docker and kubectl command lines; they did not
remove it from git’s. architecture.md lists the in-container visibility and
the shell-step helper’s host ps visibility under its Limitations; it does not list host- or
node-level capture, which this section is the record of.
- The header in a workspace
gitprocess is by design and does not show a vulnerable build. A clear-textx-access-token:URL in one is not by design: see Recognising a hit. - Exclude workspace and container processes from that capture, or treat those logs as credential-bearing: restrict who reads them and apply your retention policy to them.
- A GitHub App installation token is good for one hour; a PAT is good until revoked. Prefer the App where you can. This repository implements no other mitigation for this path.
2. Quick reference
Section titled “2. Quick reference”| Secret | Where it was exposed | How long the secret is good | Action |
|---|---|---|---|
| GitHub PAT (instance, or a per-host PAT for a GitHub Enterprise host) | Kubernetes API-server audit logs, kubectl and docker process arguments, Docker exec events, and for a while a lingering git process environment, and for a while the clone’s origin URL (entries 1, 4, 6 and 7). On every build, git’s own argument list inside the workspace (standing exposure) |
Until you revoke it or its own expiry on GitHub | Rotate |
| GitHub App installation token | The same places | One hour (GitHub sets it; the worker reads expires_at from GitHub’s answer and caches the token until a minute before it) |
Nothing to rotate. The token carries the installation’s full permissions and audit logs often reach a SIEM within seconds: if anyone outside your trust boundary could read the logs in near real time, review the installation’s activity in GitHub’s audit log for the exposure window. The App id and private key were never exposed |
A user’s own GitHub token (user-github-credentials.md) |
The same places, on runs that user launched | Until the user revokes it or its own expiry on GitHub | The user rotates it |
| Registry-proxy token | Kubernetes Pod objects, etcd and kubectl get pod; docker run arguments and docker inspect (entries 2 and 4) |
One workspace’s life, then revoked; every token is also lost when the worker restarts | Usually none; clean up leftovers (entry 2) |
Provider apiBase that carries a credential in its path or query |
kubectl exec arguments and environment pairs; docker exec arguments (entry 3) |
Until the gateway or provider rotates it | Rotate that gateway token |
| Provider API key | Never in an argument list or environment pair. Inside the workspace, as ANTHROPIC_API_KEY, for Claude Code agents with no model proxy: on Docker at every commit, on Kubernetes before 7cccdd43 (entry 5) |
Until you rotate it | Configure the model proxy; rotate if the workspace ran code you do not trust |
Recognising a hit
Section titled “Recognising a hit”A command line reaches your logs encoded, so a plain-text search for the readable form never
matches. The Kubernetes API server records the exec command as repeated command= query
parameters, encoded like Go’s url.Values.Encode: space is +, = is %3D, : is %3A,
' is %27, " is %22, / is %2F, @ is %40, $ is %24, { is %7B. Any one of these
in a kubectl exec request, or in a docker or kubectl process, confirms a token was logged
by a build without the fix. Inside a workspace, the two forms differ. The header
(AUTHORIZATION: basic, base64 prefix eC1hY2Nlc3MtdG9rZW46) in a git process is by design on
every build (standing exposure).
A clear-text x-access-token: URL in a git process’s arguments is not: current code clones only
the credential-free URL, so it marks a build older than the fix (the main clone before 3397bff3,
dependency checkouts before 8e372963, a shell step’s clone before 3200071c):
| Search for | Meaning |
|---|---|
AUTHORIZATION%3A+basic+ (decoded: AUTHORIZATION: basic ) |
The git header with a value after it |
eC1hY2Nlc3MtdG9rZW46 |
Base64 of x-access-token:, the start of every header value (check: node -e "console.log(Buffer.from('x-access-token:').toString('base64'))") |
x-access-token%3A (decoded: x-access-token:) |
A clone URL with the token in clear text, https%3A%2F%2Fx-access-token%3A<token>%40<host> |
To read a hit, URL-decode it (+ is a space), then base64-decode what follows basic . Do not
search for the bare word extraheader: the fixed commands carry it too. A fixed command names
ASW_GIT_AUTH and has no basic <base64> after it. From d9365e26 the request carries
%22http.extraheader%3D%24ASW_GIT_AUTH%22; from 8e372963 it carries
%22http.extraheader%3D%24%7BASW_GIT_AUTH%3A%3Fgit+credential+not+delivered%7D%22.
Host process records are not URL-encoded but have their own traps. In ps, use
grep -F 'AUTHORIZATION: basic' or grep -F 'x-access-token:'. In auditd, an EXECVE argument
that holds a space or a double quote is hex-encoded, and the sh -c script always does, so grep of
audit.log finds nothing: read the records with ausearch -i, or search for the hex of the
string. Verify the method on your own system against a known string before you rely on a
negative result.
3. Entries
Section titled “3. Entries”UN-1. GitHub credential in Kubernetes exec command lines
Section titled “UN-1. GitHub credential in Kubernetes exec command lines”Affected. All of these:
WORKSPACE_BACKEND=kubernetes.- A worker image built from a commit that does not contain
8e372963.d9365e26fixes the target-repository clone, base fetch,gitAuthedfetch and push. It does not fix dependency checkouts (cloneDependencyRepos, used when a repository has dependency edges), which still logged the clear-text token URL until8e372963. A build that containsd9365e26but not8e372963is partly fixed.
The Kubernetes backend first exists at 68b1dd23, so no earlier build can be affected.
What was exposed and where. kubectl exec sends the command to the API server as repeated
?command= query parameters on the pods/exec request. The request URI is recorded in
API-server audit logs even at the Metadata level, and by anything between the worker and the API
server that logs request URLs. Three kinds of value were in the command:
- The git header,
-c http.extraheader=AUTHORIZATION: basic <base64>, inside thesh -cscript text. The base64 decodes tox-access-token:<token>. It is encoding, not protection. - For dependency checkouts, the clone URL
https://x-access-token:<token>@host/..., with the token in clear text, in thegit clonecommand. - The same two values in the argument list of the
kubectlclient process on the worker host or pod, for as long as each command ran.
The credential is whichever one the run used to reach GitHub, in this order: the token of the user
who launched the run, if per-user credentials are enabled and that user saved one for the
repository; otherwise the platform credential of the repository’s host (the instance’s PAT or App
installation, or the PAT or App you recorded for that GitHub Enterprise host under
GitHubHostCredential). See user-github-credentials.md.
What was not exposed. The GitHub App id and private key, and the signed JWT used to mint an
installation token, which are handled in the worker process and never sent into a workspace. Your
provider API keys (they travel as secretEnv, in a stdin preamble).
Exposure window and lifetime. From the first run on the Kubernetes backend until your worker contained the fix. The audit entry lives as long as your retention (often a year or more), so the exposure lasts as long as the credential does:
| Credential | Still valid? |
|---|---|
| App installation token | No, after one hour. It matters only to someone who read the log inside that hour. An installation token carries the installation’s full permissions on every repository it covers |
| Instance PAT, per-host PAT | Yes, until revoked |
| User’s own token | Yes, until revoked |
Actions.
- Rotate every PAT that could have been used since the first Kubernetes run: create the new token
on GitHub, save it at
/admin/integrationsunder GitHub (for a GitHub Enterprise host, under Per-host credentials), then revoke the old one on GitHub. Revoking is the rotation; saving alone leaves the old token live. - If you enabled per-user credentials, tell every user who launched runs against a Kubernetes worker to save a new token for each repository on the Connections page and revoke the old one.
- Find the runs, to learn the window and who could have read it. Search your API-server audit
logs, and any sink that receives them, for exec requests (
objectRef.subresourceisexec) whoserequestURImatches a search in Recognising a hit. Do not paste hits into tickets or chat. - Purge or redact the matching audit entries per your retention policy (see If you cannot tell). Check the places that copy those logs: SIEM, object storage, backups.
- Check any load balancer, managed control-plane log or API gateway in front of your API server for request-URL logging. This note cannot tell you what your infrastructure records.
Verify the fix is running. The worker contains 8e372963. For a running cluster: run a
workspace, then look at its kubectl exec requests in the audit log. The clone and fetch
commands contain ASW_GIT_AUTH with no basic <base64> after it, and none of the searches in
Recognising a hit matches a kubectl exec request from a new run. A node-level
process record of the header in git inside the pod is the standing exposure, not a failure of this check. A search that finds
nothing proves the fix only if the same search finds a known string in your log pipeline.
UN-2. Registry-proxy token in Kubernetes Pod objects
Section titled “UN-2. Registry-proxy token in Kubernetes Pod objects”Affected. All of these:
WORKSPACE_BACKEND=kubernetes.- The registry proxy is configured (
REGISTRY_PROXY_PORTset; seepackage-registries.md), so workspaces receive a token. - A worker image built from a commit that does not contain
c4d1a285.
What was exposed and where. The per-workspace proxy token, as COREPACK_NPM_TOKEN and
YARN_NPM_AUTH_TOKEN in the env of the workspace Pod. It was readable by anyone with get pods
in the workspace namespace (kubectl get pod -o yaml), stored in etcd and in etcd snapshots, and
present in the body of the pods create request wherever your audit policy records request bodies
(not at Metadata level). The npmrc that also carries the token is written through stdin and was
not on any command line.
What was not exposed. The upstream registry credentials: a workspace holds only the proxy’s token, never an upstream token. Any GitHub credential (that is UN-1).
Exposure window and lifetime. The token is 32 random bytes minted per workspace. The worker keeps a digest of it in memory only and revokes it when the workspace is released; a worker restart drops all of them. It works only at the worker’s registry proxy and, in lockfile mode, only for the package versions committed on the base ref. A token read from a finished run is dead. A token of a run still in progress when you read it is live until that run ends.
Actions.
- Upgrade the worker to a build containing
c4d1a285, and apply the matching RBAC frominfra/kubernetes/workspace-namespace.yamlbefore it starts: the worker now needscreateanddeleteonsecretsandlistandwatchonpods, or workspace creation fails its preflight. - No rotation is needed for completed runs. Delete leftover workspace Pods from older runs (the
worker does not reap finished Pod objects), and expire etcd snapshots that predate the fix if
your policy treats even dead tokens as sensitive. After the fix the token is still held in a
Kubernetes Secret while the workspace lives, so it sits in etcd and in the
secrets createrequest body wherever your audit policy records bodies (not atMetadatalevel), and in snapshots taken meanwhile; it is dead once the workspace is released, and the same advice applies to those snapshots. - Restart the worker once after the upgrade: that revokes any registration still alive from an old process.
- If the registry proxy’s address is reachable from outside the cluster or from other tenants, treat a token read during a live run as usable until that run ends. Whether it is reachable is a property of your network: verify in your environment.
Verify the fix is running. The worker contains c4d1a285. Create a workspace and run
kubectl get pod <workspace> -o yaml: the two token variables appear as valueFrom.secretKeyRef,
not as value, and a Secret named <pod>-env exists, owned by the Pod.
UN-3. Claude Code provider base URL in exec arguments and environment
Section titled “UN-3. Claude Code provider base URL in exec arguments and environment”Affected. All of these:
- A provider credential whose
apiBasehas a path, query or fragment that carries a secret (some LLM gateways put a token in the path). A bare origin carries none. - Claude Code turns that ran without the worker’s model proxy, so the harness talked to your
apiBasedirectly. With the proxy (HARNESS_MODEL_PROXY_PORTset), the harness receives the proxy’s per-turn URL and token, andapiBasenever reaches the workspace. - A worker image built from a commit that does not contain
3200071c.
On Docker the proxy is optional. On Kubernetes a worker built from 7cccdd43 or later refuses to
start without it, whatever its configuration, so the Kubernetes case is a worker older than that
commit.
What was exposed and where. The apiBase value was passed as an ANTHROPIC_BASE_URL=...
environment pair on the exec command, and as inline JSON in the harness’s --settings argument.
Under Kubernetes both are part of the ?command= query of the exec request and land in the audit
log (see UN-1). Under Docker both are docker exec arguments, visible in ps on the worker host.
What was not exposed. The provider API key itself: it travels as secretEnv (a stdin preamble
under Kubernetes, -e NAME without a value under Docker). A gateway that authenticates by API key
only, with an origin-only apiBase, exposed nothing.
Exposure window and lifetime. The harness first passed apiBase as ANTHROPIC_BASE_URL at
2bcc49fd (2026-10-03), so no earlier build is affected. The window runs from the first Claude
Code turn without the model proxy until your worker contained 3200071c. A path token is valid
until its issuer rotates it.
Actions.
- List your provider credentials at
/modelsunder Credentials, and open each one that is used by a Claude Code agent. Note everyapiBasethat has anything after the host. - For each, rotate that token at the gateway or provider, and save the new
apiBase. - On Kubernetes, search the audit logs as in UN-1 for the token text of the old
apiBase, in its URL-encoded form (see Recognising a hit): search the host and the start of the path with:,/and@written as%3A,%2Fand%40. - On Docker, treat it like UN-4.
The gateway refuses an apiBase that carries a username or password (credentialInputProblem);
since 1d7b03b7 (2026-10-03). It checks the URL’s userinfo only (and the API key for whitespace and
control characters). It does not look at the path or query, which is why they needed this fix, and it
does not re-check a credential saved before it existed. A stored credential with userinfo in its apiBase was a credential in a
URL: rotate it.
Verify the fix is running. The worker contains 3200071c. A turn with a path-bearing
apiBase then shows no ANTHROPIC_BASE_URL= pair and no inline --settings JSON on its exec
command, and the pinned settings layer is a file under the harness home.
3200071c also adds a run-time guard against credential-named environment pairs, which needs no
operator action; the current rule and what it misses are in the Source invariants table of
AGENTS.md.
UN-4. Credentials in process arguments on the Docker backend
Section titled “UN-4. Credentials in process arguments on the Docker backend”Affected. All of these:
WORKSPACE_BACKEND=docker(the default).- A worker image built from a commit that does not contain the fix for the value in question (the list below).
- A host where something other than the worker can read process arguments or exec events: other
local users, command-line auditing (
auditdexecverecords, EDR agents, process accounting), a runtime monitor that records container processes, or anything that subscribes to the Docker event stream (see below).
Do not treat a host as unaffected until you have checked every consumer on it. Which consumers you run is not something this repository can show: verify in your environment.
What was exposed and where. The Docker CLI runs on the worker host with the command in its
argument list, so the value is in ps for as long as that command runs, and in anything that
records it:
| Value | First in a Docker command line | In the argument list until | Commands |
|---|---|---|---|
| Clone URL with the token in clear text | Main workspace clone: 5cdb253d (2026-02-23), docker exec … git clone '<url>'. Shell-step clone: 89573d41 (2026-05-13), the same URL via docker run. Dependency checkouts: 8f1f4a82 (2026-08-21) |
Main clone: 3397bff3 (2026-09-02), after which it carried the header until d9365e26. Dependency checkouts: 8e372963. Shell-step clone: 3200071c (its base fetch runs in the same script, using the origin the clone configured) |
docker exec, docker run |
Git header AUTHORIZATION: basic <base64> |
4763a611 (2026-07-21), as -c http.extraheader='…' on fetch and push; the main clone from 3397bff3 |
d9365e26 (workspace clone, fetch and push; the shell-step push) |
docker exec, docker run |
| Registry-proxy token | e0187a3f (2026-10-20) emitted -e YARN_NPM_AUTH_TOKEN=<token>; b248b650 (2026-10-20) added -e COREPACK_NPM_TOKEN=<token> |
c4d1a285 for the workspace container; 83dc2ff5 for the shell-step npmrc helper; 3200071c for the shell-step sandbox |
docker run -e NAME=value |
Provider apiBase with a credential in its path |
2bcc49fd (2026-10-03) |
3200071c |
docker exec |
| Container-step node inputs | 0e1d9b5b (2026-06-18), -e CONTAINER_STEP_INPUT=<json>; a credential only if a workflow author routed one into a node input (below) |
Not tied to a cited commit (below) | docker run |
The Kubernetes backend exists from 68b1dd23 (2026-10-21), so the Kubernetes entries cannot
affect an earlier build. A Docker worker built before the fix for a value is affected from that
value’s first commit, not only recent builds. This repository cannot tell how long your own
deployment ran each build.
Container-step inputs. A build whose containerStep.ts passes CONTAINER_STEP_INPUT in
secretEnv keeps the node inputs out of docker run arguments; an older build put them there.
That change removes the value from the argv only: it is still in the container’s configured
environment, so docker inspect shows it in Config.Env for as long as the container exists,
which matters most for a sidecar. The inputs are workflow data, so this exposes a credential only
if an author routed one into a node input. The top box’s “built before 3200071c” cutoff does not
cover this item: check the file in your build’s source. Rotate any credential an input carried,
and search host command-line records for the input’s text. Shell and container steps run on the
Docker backend only; the Kubernetes driver refuses them.
What was not exposed. Docker daemon API request bodies are not normally logged. The Docker
socket proxy shipped with this project (infra/docker-socket-proxy/haproxy.cfg) logs the HTTP
request line, not bodies. If you enabled daemon debug logging, an authorization plugin, or put a
body-logging proxy in front of the socket, check those logs too. The provider API key and the
platform’s own secrets were never in an argument list.
The Docker event stream and exec records. The Docker daemon records the command of each
docker exec in its event stream (docker events, actions exec_create and exec_start, with
the command line in the action text), and GET /exec/{id}/json returns the process arguments
while the exec record exists. This is Docker behaviour this repository cannot test, so confirm it
on your version. Anything that subscribes to the stream and persists it (monitoring and security
agents, Falco, Portainer, cAdvisor, SIEM shippers) holds the same data as a command-line audit:
before d9365e26 the header, and before 8e372963 and 3200071c the clear-text token URL of the
commands those fixed. Treat that data like command-line audit data in the actions below.
What still shows after the fixes. The fixes name a variable in the Docker command
(-e NAME) and pass its value through the client’s environment, which ps does not show. Three
things remain by design, listed in the Limitations of architecture.md:
docker inspect of a container shows the Config.Env it was created with, which includes the
registry-proxy token of a workspace; inside the container the git header is in the environment and
in git’s argument list for the length of each call, which a host process audit also sees
(standing exposure); and a
shell step’s clone helper shows the header to a host ps for the length of the clone.
Exposure window and lifetime. The seconds to minutes each command ran, per command. Lifetimes are those in the quick reference.
Actions.
- Upgrade the worker to a build containing
3200071c(which contains the fixes in the table above, but not the container-step item). - Decide whether anyone or anything that could read process arguments on a worker host existed in the window, including a consumer of the Docker event stream. If so, rotate the PAT, per-host PATs and per-user tokens as in UN-1 steps 1 and 2. Process auditing of git inside the workspaces continues to record the header after the fix. An App installation token needs no rotation (see the quick reference for its caveat).
- Search host command-line audit data and stored Docker events with the searches in
Recognising a hit (
AUTHORIZATION: basic,eC1hY2Nlc3MtdG9rZW46,x-access-token:; the URL-encoded spellings apply only where a log is URL-encoded); purge or redact per your retention policy. - If you ran shell steps with a registry, the shell-step sandbox token was also in
docker inspectof that container while it existed. It is dead once the step ends.
Verify the fix is running. The worker contains 3200071c. While a workspace command runs,
ps -eo args | grep -F 'AUTHORIZATION: basic' and ... | grep -F 'x-access-token:' on the worker
host find no docker process that carries a value. A git process inside a workspace that carries the
header is the standing exposure; a docker process that carries a value, or a git process with a
clear-text token URL, is a failure. To check an event consumer, look for ASW_GIT_AUTH in the
stored exec event with no basic <base64> after it.
UN-5. Provider API key inside the workspace
Section titled “UN-5. Provider API key inside the workspace”Affected. Claude Code agents (see agents.md) running with no model proxy
(HARNESS_MODEL_PROXY_PORT unset), on either backend:
- Docker, at any commit. The model proxy is optional on this backend and the key stays inside the workspace when it is off. This is current behaviour, not a fixed defect: no commit changes it. (The shipped compose files turn the proxy on; a deployment that does not use them may not.)
- Kubernetes, with a worker built before
7cccdd43. From that commit the worker refuses to start without the proxy.
What was exposed and where. Without the proxy the harness gets the raw provider API key as
ANTHROPIC_API_KEY in its environment inside the workspace. On Kubernetes it reached the pod
through a stdin preamble, so it was not in any audit log or Pod object. Anything the agent ran in
that workspace could read it.
Actions. Configure the model proxy: set HARNESS_MODEL_PROXY_PORT and, on Kubernetes, a
HARNESS_MODEL_PROXY_URL that workspace pods resolve (a worker containing 7cccdd43 refuses to
start until both are set). See configuration.md,
deployment.md and agents.md. If the agents in those
workspaces ran code you do not trust (a target repository you do not control, a prompt-injected
run), rotate the provider key; configuring the proxy does not retire a key already read.
Verify. A Claude Code turn’s harness receives the proxy’s per-turn URL and token instead of
the provider key (agents.md).
UN-6. GitHub credential kept alive by a background process
Section titled “UN-6. GitHub credential kept alive by a background process”Affected. Both backends, with a worker image built from a commit that does not contain
8e372963. It matters when code you do not trust ran in the workspace after a credential-bearing
call.
What was exposed and where. The hardened configuration for the version-control client lacked
gc.auto=0 and maintenance.auto=false, so a fetch or commit could spawn maintenance run --auto, which detaches. The header is in that client’s environment (GIT_CONFIG_PARAMETERS) for
the length of a call; the detached process kept it past the call, readable by anything the agent
ran afterwards through /proc/<pid>/environ. The commit that fixed it records the behaviour as
confirmed with GIT_TRACE. This repository cannot show whether it happened in your runs. The
credential is the one in UN-1: the platform credential or the token of the user who launched the
run.
Actions. If agents ran code you do not trust in a build before 8e372963, rotate as in UN-1
steps 1 and 2. If not, nothing is needed beyond upgrading.
Verify the fix is running. The worker contains 8e372963.
UN-7. GitHub credential in the workspace’s .git/config
Section titled “UN-7. GitHub credential in the workspace’s .git/config”Affected. WORKSPACE_BACKEND=docker (the Kubernetes backend exists only from 68b1dd23),
with a worker image built from a commit that does not contain 4763a611 (the agent workspace) or
6b4ee97e (shell-step volumes). It matters when code you do not trust ran in such a workspace, or
when people outside your trust boundary can read stored traces or transcripts.
What was exposed and where. The clone left https://x-access-token:<token>@host/... as
origin in the workspace’s .git/config, in clear text, for the workspace’s whole life: from
5cdb253d (2026-02-23) until 4763a611 (2026-07-21) for the agent workspace, and from 89573d41
(2026-05-13) until 6b4ee97e (2026-08-21) for a shell step’s volume. The agent, and any code it
ran, could read it. So could any command whose output printed the remote (git remote -v,
git config --get remote.origin.url, cat .git/config), and that output went into the model’s
prompt and into the run’s stored AgentTrace rows and agent transcripts
(transcripts.md). Both stores mask a URL’s userinfo at write time from
9f59da0c (2026-08-27); a row written before that commit has no such guarantee, the prompt was
never masked, and a mask is not a guarantee for any other spelling. Treat the stored traces and
transcripts of runs from an affected build as credential-bearing: restrict who reads them and
apply your retention policy to them. The credential is the one in UN-1.
Actions. If code you do not trust ran in a workspace from an affected build, or the traces and transcripts are readable beyond your trust boundary, rotate as in UN-1 steps 1 and 2 (an App installation token is dead after an hour). Purge or redact those traces and transcripts, and their backups, per your retention policy.
Verify the fix is running. The worker contains 4763a611 and 6b4ee97e. In a new workspace,
git remote get-url origin prints a URL with no x-access-token:.
UN-8. Pull-request evaluation: migration and the steps that make it enforce
Section titled “UN-8. Pull-request evaluation: migration and the steps that make it enforce”This entry is not a credential defect. It lists what an operator must do, on a deployment that ran
earlier code, for pull-request evaluation (pr-evaluation.md) to take effect.
Affected. Every deployment whose gateway and worker images are built from a commit that contains
7ddfd551. A build before it has none of this.
What changes. A migration adds the table pr_evaluations and the column
connections.pr_evaluation_mode. The normal migrate step applies it (prisma migrate deploy, which the
gateway image runs before it starts); nothing is run by hand. The feature is off: nothing is evaluated and
no status is posted until an admin sets prEvaluation.enabled. No repository is in auto mode, and
prEvaluation.autoMergeAllowed is off.
Actions.
- Apply the migration with the normal migrate step.
- To use the feature, set
prEvaluation.enabledat/admin/platform-settings, and make sure the GitHub App deliverspull_requestandpull_request_reviewevents. - To make approver counts bind, a repository admin marks the status context
auto-swe/pr-evaluation(or the value ofprEvaluation.requiredStatusContext) as a required check in branch protection or a ruleset. This binds only for a repository inautomode;advisoryalways reports success. - For
automode, additionally pin that required check to the platform’s GitHub App (a personal access token cannot be pinned, soautoneeds App credentials), and allow auto-merge on the repository. Until both are trueautois refused.
Verify. auto-swe pr-policy status <repoId> shows the mode and the last evaluation, and a
pull request the platform opened shows the status auto-swe/pr-evaluation on its head commit.
Device authorization codes require the new migration
Section titled “Device authorization codes require the new migration”Affected. A deployment that enables mcp.deviceFlowEnabled on a database that has not applied
20261029132410_mcp_device_codes.
Action. Apply the migrations before enabling the setting. The gateway expects Better Auth’s
device_codes table, including its OAuth resource and client fields; do not enable the device flow
against an older schema. The normal deployment migration step applies this migration.
Verify the fix is running. The gateway and shared package contain c4253ec2, migration
20261029132410_mcp_device_codes, and the generated DeviceCode Prisma model.
Reporting attribution backfill (20261029130735_reporting_attribution)
Section titled “Reporting attribution backfill (20261029130735_reporting_attribution)”Affected. Deployments applying commit 265c2a938f0ca6df969a481643efcf00fae302b3 or a descendant
that has not yet applied 20261029130735_reporting_attribution. The migration adds nullable
WorkflowRun team/organization attribution, PullRequest template/run attribution, and the HITL
resolution-source columns, then backfills existing workflow runs and repository-matched pull
requests.
Expected cost. Runtime scales with the number of stored workflow runs and pull requests. The
migration blocks writes to workflow_runs and pull_requests while the backfills run. Large
deployments should schedule a maintenance window or raise the migration session’s statement timeout.
Actions. Nothing manual is required beyond the normal migrate deploy; the migration performs
both backfills. No historical HITL source is inferred, so existing resolution-source values remain
null.
4. If you cannot tell
Section titled “4. If you cannot tell”If you cannot establish which commit your workers ran, whether per-user credentials were on, or whether anything logged command lines: rotate first, then clean up.
- Rotate the PAT, every per-host PAT, and ask users to rotate their own tokens (UN-1 steps 1 and 2); treat stored traces and transcripts as credential-bearing (UN-7). An App needs no rotation (see the quick reference for its caveat).
- Rotate any provider
apiBasetoken (UN-3). - Upgrade the worker to a build that contains
3200071cand, on Kubernetes, apply the RBAC ininfra/kubernetes/workspace-namespace.yaml. - Purge or redact audit entries and command-line records that match the patterns above, per your retention policy. If the logs are immutable or held for compliance, record that they contain credentials that have been revoked, and restrict who can read them.
5. How these notes are maintained
Section titled “5. How these notes are maintained”A change that needs an operator to act on a deployment that ran earlier code adds an entry here in the same change. That covers rotating a credential, purging logs, running a migration by hand, and changing configuration. An entry is keyed by the condition that makes a deployment affected and by the SHA of the fixing commit. An entry states conditions and facts in the present tense, not a history of the change; it is corrected by adding a note or a new entry, not by rewriting what an operator may already have acted on.
Limitations
Section titled “Limitations”- A SHA identifies code, not your deployed image. The comparison in §1 holds only if your
image really was built from the commit you think. An image built without the build arguments
reports
unknown. - The notes cannot tell you whether anyone read your logs. They tell you where a value was recorded and how long it is good; whether it was used is a question for your own audit trail, on GitHub as well as in your cluster.
- They cannot enumerate your log sinks. Load balancers, managed control planes, SIEM forwarders, backups and host agents are yours to check.
- How long your deployment ran each build is unknown. The start commits in UN-4 date the code, not your rollout; only your own deployment records say which build ran when.
- Host-level capture of git inside a workspace is standing. No fix removes the header from git’s argument list there; whether your audit pipeline records it is yours to check.
- The container-step item has no commit cited. It is identified by the code marker in UN-4, which you check in your build’s source.
- The fixes narrow where a value travels; they do not remove every copy. What remains is
listed in the Limitations of
architecture.mdandpackage-registries.md. - Some host behaviour is not verified here. Whether your Docker version records exec commands in its event stream and which consumers you run, whether your network exposes the registry proxy, what your log pipeline does to an encoded command line, and whether the lingering process of UN-6 occurred, are properties of your environment.
- They cannot tell you what a leaked token did. For an App installation token, which has the installation’s full permissions, the only record is GitHub’s audit log.
© 2026 Jorge Barnaby
Source-available under FSL-1.1-MIT