Skip to content

fix(credentials): fail closed when GH_TOKEN vault fetch fails - #122

Open
twistedmelonman wants to merge 1 commit into
mainfrom
claude/fix-credentials-fail-closed-35329b18
Open

twistedmelonman wants to merge 1 commit into
mainfrom
claude/fix-credentials-fail-closed-35329b18

Conversation

@twistedmelonman

Copy link
Copy Markdown
Member

Problem

When the 1Password fetch failed, _load_gh_token left GH_TOKEN unset and
logged a warning saying gh would "use keyring fallback." That fallback is the
user's keyring OAuth token, which carries repo, workflow, admin:org,
delete_repo and admin:public_key — far wider than the restricted CCCLI PAT
the function exists to inject.

So the degraded path was strictly more privileged than the healthy one. Any
transient op failure — locked vault, missing service account token, network
blip, backoff exhausted — silently handed the agent full admin access,
announced only as a launch-time warning nobody reads.

Found while diagnosing #120.

Fix

Export a deliberately invalid sentinel instead of leaving GH_TOKEN unset, so
gh fails with a clear auth error rather than quietly succeeding with more
access than intended.

The "already set, skip lookup" guard now treats that sentinel as unset, so a
retry can still reach the vault instead of being short-circuited by a previous
failure.

Verification

Checked against the pre-change code with a stubbed failing op:

GH_TOKEN after failed fetch
before length 0 — unset, keyring fallback active
after length 38 — sentinel, gh fails closed

The failure path had no test coverage. Adds three: the sentinel is
exported, the warning is emitted, and a present sentinel does not suppress a
later vault lookup.

  • test-credentials.sh 14/14
  • test-launch-dir-check.sh 6/6
  • test-remote-session.sh 26/26
  • test-wrapper.sh pass
  • shellcheck -S info clean

test-gh-token-permissions.sh was deliberately not run: it mutates a live
sandbox repo and needs org write access the current PAT lacks (that being the
subject of #120).

Also

Corrects the file header, which claimed these credentials are "not present in
the interactive shell environment." OP_SERVICE_ACCOUNT_TOKEN is in fact
inherited by child processes of the wrapper, including the agent's own shell —
verified this session.

Note for reviewers

test-remote-session.sh has a pre-existing cwd dependency: it fails 25/26 when
run from outside the repo (it derives a session name from $PWD) and passes
26/26 from inside. Confirmed pre-existing by stashing this branch's changes and
re-running. Not addressed here.

Refs #120

https://claude.ai/code/session_015WZskZun8am3TWVS5pMYii

When `op read` failed, _load_gh_token left GH_TOKEN unset and logged a
warning saying gh would "use keyring fallback". That fallback is the user's
keyring OAuth token, which carries repo, workflow, admin:org, delete_repo
and admin:public_key — far wider than the restricted CCCLI PAT this function
exists to inject.

The effect was silent privilege escalation: any transient op failure (locked
vault, missing service account token, network blip, backoff exhausted) handed
the agent full admin access, announced only as a launch-time warning. The
degraded path was strictly more privileged than the healthy one, which
inverts the intent of injecting a restricted token at all.

Export a deliberately invalid sentinel instead, so gh fails with a clear auth
error rather than quietly succeeding with more access than intended. The
"already set, skip lookup" guard now treats that sentinel as unset, so a
retry can still reach the vault instead of being short-circuited by a
previous failure.

Verified against the pre-change code: on a failing `op read`, old behavior
left GH_TOKEN with length 0 (unset -> keyring fallback); new behavior exports
a 38-char sentinel (-> gh fails closed).

Adds three tests covering the failure path, which previously had none: the
sentinel is exported, the warning is emitted, and a present sentinel does not
suppress a later vault lookup.

Also corrects the file header, which claimed these credentials are "not
present in the interactive shell environment". OP_SERVICE_ACCOUNT_TOKEN is in
fact inherited by child processes of the wrapper, including the agent's shell.

Refs #120

Claude-Session: https://claude.ai/code/session_015WZskZun8am3TWVS5pMYii
@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown

The change is a targeted security improvement: when the vault fetch for GH_TOKEN fails, the code now exports a deliberately invalid sentinel instead of leaving the variable unset. This prevents gh from silently falling back to the broader keyring OAuth token on transient failures. The sentinel skip-logic in _load_gh_token() correctly allows a genuine retry while still skipping on a real pre-set token.

Three new tests cover the three cases (failure to sentinel, failure to warn, sentinel to retry still reaches vault). No reliability regressions or security issues introduced.

VERDICT: PASS

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