Skip to content

fix(sandbox): keep the GitHub token and registry password out of VM scripts - #410

Open
earakely-scale wants to merge 2 commits into
mainfrom
edgararakelyan/github-token-off-argv
Open

earakely-scale wants to merge 2 commits into
mainfrom
edgararakelyan/github-token-off-argv

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Building an image from GitHub with a token, and logging a VM's Docker in to a registry, put the secret in the text of a bash -c script. That text is a process argument on the VM host, and on the local provider that host is the operator's machine:

  • any other account there can read it from ps or /proc/<pid>/cmdline;
  • agent-env -v logs every local command in full, so it lands in the debug log too.

The GitHub token also sat in /tmp/git-askpass.sh: a fixed name, created with the default mode, and left behind when the build stopped before its cleanup. Another local account could create that file first and keep what was written into it.

Now a secret goes into a file in a folder made for it, and only the file's path appears in any command:

  • VmSandbox.secret_dir(): a mktemp -d folder (mode 0700, a name nobody can guess), removed on the way out, whether the step worked or not.
  • VmSandbox.write_secret_file(data, path): writes over the exec's stdin when the sandbox takes stdin, and through write_host_file otherwise.
  • VmSandbox.docker_login(auth): docker login --password-stdin < <file>. Used by pull_published and by put_from_github's push.
  • put_from_github's clone: git -c credential.helper= -c 'credential.helper=store --file=<file>' clone .... The first -c turns off the operator's own credential helpers, which otherwise keep a copy of a token git used, in the macOS keychain for example. The askpass helper had that side effect too. GIT_TERMINAL_PROMPT=0 now also applies to a tokenised clone, so a bad token fails instead of prompting.
  • LocalSandbox._exec_with_stdin: the one provider change. The local sandbox had no way to take stdin, so a file write on it went through the command line. exec and the new method share _spawn, so both rewrite /app and log the same way.

How each provider writes the secret file:

Provider How the secret reaches the file
local, modal_vm, tenki the exec's stdin
sail_vm, daytona, tensorlake, vercel, smol_vm the provider's own file API
e2b, boxd, boat (small files) write_host_file's base64 over exec, as before

On the last row the secret still travels inside an exec command. That happens only on a VM made for the run, which holds the credential anyway, and the provider receives the secret however it's sent. Covering those three would need per-provider code and is left out on purpose.

Testing

  • test_docker_image_github_clone.py runs the real git credential fill and approve against the clone's options. The token comes back from the file, and a credential helper in the user's git config is never handed it. Without -c credential.helper=, that helper gets the token.
  • test_docker_image_github_push.py: the token and the registry password reach the build VM only over stdin, into files under the secret folder. No script holds either. The folder is removed after the clone and after the login, and also when the clone fails.
  • vm_sandbox_test.py: pull_published logs in from a password file inside a removed folder, and a sandbox with a file API and no stdin writes the secret through that API.
  • local_sandbox_test.py: on a real LocalSandbox with real subprocesses, a secret written into secret_dir() reaches its file. The folder is mode 0700 and gone afterwards. The secret appears in no spawned process's arguments and nowhere in the DEBUG log.
  • The four secret tests in the push and sandbox files fail on main.
  • End to end on the local provider: put_from_github with a real GitHub token against docker-library/hello-world, then a real docker login and push to a local registry:2.
    • The token and the password appeared in none of 45 samples of ps -axww taken every 50 ms through the build.
    • Neither appeared in the 25-line DEBUG log.
    • /tmp/git-askpass.sh never existed, and both secret folders were gone afterwards.
    • The harness skipped only the apt-get install git line: macOS has no apt-get, so put_from_github on a Mac's local provider can't run as it is.
  • Full unit suite on macOS: 11,927 passed and 66 skipped. The one failure is test_hermes_bridge.py::test_the_event_pipe_is_not_left_open_in_the_server, which reads /proc/self/fd (Linux only). ruff check is clean.

Not in this PR

  • An agent container's environment, the model key included, still goes into docker run -e KEY=value script text (_container_args, A2AAgent._run_container). On the local provider that's the same process-table and -v log exposure. It's a separate change.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; the previously reported quoting issue is fixed.

Summary

Moves GitHub tokens and registry passwords out of VM script text into temporary files.

  • Private GitHub clones read their token from a temporary credentials file.
  • Registry logins read passwords from temporary files.

No new actionable issues were found in the changes since the previous review.

Reviews (2) · Last reviewed commit: "fix(sandbox): quote the credentials path..." · Reviewed by Greptile

…cripts

put_from_github wrote the token into a fixed /tmp askpass helper through
script text, and every docker login echoed the registry password in its
script. Script text is a process argument on the VM host, the operator's
machine on the local provider, and `agent-env -v` logs it there.

A secret now goes into a file in a mktemp -d folder that is removed on
the way out, written over the exec's stdin where the sandbox takes it and
through write_host_file otherwise; commands name only the file. The clone
reads the token through git's credential store with the operator's own
helpers off, and pull_published and the build's push share
VmSandbox.docker_login. LocalSandbox gains the stdin exec it lacked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
git runs a credential helper through the shell, so a credentials path
with a space (a TMPDIR that has one, on the local provider) split into
separate arguments and the clone could not read the token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant