Skip to content

fix(tasks): stop the sandbox retaining a prior actor's GitHub token - #72507

Merged
VojtechBartos merged 1 commit into
vojtab/github-identity-transitionsfrom
vojtab/github-identity-runtime-hardening
Jul 21, 2026
Merged

fix(tasks): stop the sandbox retaining a prior actor's GitHub token#72507
VojtechBartos merged 1 commit into
vojtab/github-identity-transitionsfrom
vojtab/github-identity-runtime-hardening

Conversation

@VojtechBartos

@VojtechBartos VojtechBartos commented Jul 21, 2026

Copy link
Copy Markdown
Member

🎯 Summary

Follow-up to #71941. During multiplayer testing, a follow-up actor without repo access pushed as the previous actor: the exact leak the GitHub identity gate is meant to prevent. The logout stripped the git remote and the /tmp/agent-env file, but the prior actor's token survived in two runtime places the file-clear can't reach. This closes both.

When does a transition happen?

A "transition" is a follow-up in the same thread from a different Slack user than the one the sandbox is currently bound to. The gate fires only then (same-actor follow-ups and bot runs are skipped).

sequenceDiagram
    actor A as User A (has access)
    actor B as User B (no access)
    participant S as Sandbox (one shared box)
    participant G as GitHub

    A->>S: "@bot clone repo X and work on it"
    Note over S: provisioned with A's token<br/>identity marker = A
    B->>S: same thread: "commit it and open a PR"
    rect rgb(255,235,235)
    Note over S: ACTOR TRANSITION  A to B
    S->>S: gate: B has no access, so LOG OUT<br/>clears git remote + /tmp/agent-env
    S-->>S: but A's token survives elsewhere
    S->>G: git push (recovered A's token)
    G-->>B: PR opened as User A  (LEAK)
    end
Loading

Why the logout didn't stick: the token lives in 3 places

One running sandbox holds A's token in three spots. Logout only reached two of them:

  git remote URL     x-access-token:<A>@github.com/...     cleared on logout   OK
  /tmp/agent-env     GH_TOKEN=<A>   (live, per command)    cleared on logout   OK
  agent-server env   process.env.GH_TOKEN=<A>              frozen at launch    LEAK  <- this PR
       + periodic refresh loop re-writes A's token back into the file          LEAK  <- this PR

Changes

  • constants.py: add GITHUB_TOKEN / GH_TOKEN to SANDBOX_AGENT_LAUNCH_UNSET_ENV_VARS, so the agent-server process holds no static token. The token is still delivered per command via the live file (re-sourced by BASH_ENV, seeded by env -0 > ENV_FILE before the unset), so git/gh still authenticate on turn 1: only the un-revocable copy is removed. Audited every agent-server token consumer first: all resolve via the file-first resolveGithubToken, so the unset is safe.
  • sandbox_credentials.py: the periodic refresh loop now skips a run whose sandbox-github-identity marker is bound to a different actor than the run owner (a transition / logout has occurred), so it can't re-inject the owner's token.
flowchart LR
    T([user token refreshed]) --> L{ run's sandbox marker<br/>== run owner? }
    L -- "yes / unset" --> A[re-apply owner token]
    L -- "no: transitioned away" --> S[skip, leave the current<br/>actor's state intact]
Loading

Companion PR (required together)

Needs PostHog/code#3611, which makes the agent-server treat an emptied env file as an explicit logout (return "") instead of falling back to process.env, and runs the gh attribution/whoami calls as the current actor. This backend PR removes the process-env token; the code PR stops the resolver and gh paths from resurrecting it. Both are needed for a complete fix.

How did you test this code?

  • New resolveGithubToken tests in fix(agent): treat an emptied sandbox env file as GitHub logout code#3611 (11 pass), including the assertion that an emptied file does not resurrect the process-env token.
  • New parameterized backend test test_actor_transition_gates_owner_token_propagation (marker unset / owner / other -> include / include / exclude). Could not run the DB-backed suite locally (ClickHouse held by the local dev stack); CI validates.

Two runtime gaps let a follow-up actor keep the previous actor's GitHub identity
after a per-message logout:

- The agent-server was launched with GITHUB_TOKEN/GH_TOKEN in its process env,
  frozen for the process lifetime. Clearing the live /tmp/agent-env file could
  not revoke that copy, so in-process tools resurrected it. Add both vars to
  SANDBOX_AGENT_LAUNCH_UNSET_ENV_VARS; the token is still delivered per command
  via the file (re-sourced by BASH_ENV, seeded before the unset).
- The periodic user-token refresh loop re-applied the run owner's token to every
  live sandbox keyed on the owner's integration, overwriting a transition. Skip
  runs whose sandbox-github-identity marker is bound to a different actor.

Pairs with the agent-server fix (PostHog/code) that treats an emptied env file
as an explicit logout instead of falling back to the process env.
@trunk-io

trunk-io Bot commented Jul 21, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@VojtechBartos
VojtechBartos merged commit c9b507c into vojtab/github-identity-transitions Jul 21, 2026
178 checks passed
@VojtechBartos

Copy link
Copy Markdown
Member Author

Folded into #71941 so there's a single posthog PR for the GitHub identity work. The two hardening commits (unset the launch env token + actor-aware refresh loop) are now part of that PR.

@VojtechBartos
VojtechBartos deleted the vojtab/github-identity-runtime-hardening branch July 21, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant