Skip to content

fix: close the credential leaks two security reviews found - #93

Merged
jferrl merged 1 commit into
mainfrom
fix/security-review
Oct 31, 2026
Merged

jferrl merged 1 commit into
mainfrom
fix/security-review

Conversation

@jferrl

@jferrl jferrl commented Oct 31, 2026

Copy link
Copy Markdown
Owner

Summary

Two independent full-repository security reviews, Codex (gpt-6.1-sol, reasoning ultra) and Claude Fable, found ten issues, none overlapping beyond two. The serious one: githubauth token --exec passed the App's private key, which mints tokens for every installation, to the command it runs. This PR fixes all ten. Three further adversarial Codex passes over the fixes found thirteen more gaps in them, also fixed. Stacked on #92, whose clone default two of the fixes touch.

What this does

Before After
The --exec command, and git for the clone default, saw $GITHUB_APP_PRIVATE_KEY Neither does, in any letter case on Windows
A base64 --key was echoed in full in the error, into logs and agent transcripts A value longer than a path is not repeated
--enterprise-url https://ghe:8443 set GH_HOST=ghe, so gh sent the token to 443 GH_HOST=ghe:8443
An exported GH_HOST for another instance made gh send the token there token --exec exits 2 before minting
A clone's https origin on another port than the API's scoped the token to it Ignored: git push would hand the token to that port
kill -INT on githubauth left the command running with the token Passed on, unless the command is the terminal's foreground job
Actions referenced by movable tags, in the job holding the publishing PAT Pinned to commits

Changes

  • childEnviron() drops the key and exported tokens for every process githubauth starts.
  • ghHost keeps any port except https's 443, never rewrites a host with one, and keeps IPv6 brackets. originRepo compares an http(s) origin's host and port, an ssh origin's host only.
  • checkGHHost refuses a mismatched GH_HOST for token --exec. The JWT is not checked: it is signed locally.
  • SIGINT forwarding asks whether the child's process group is the terminal's foreground (darwin, linux).
  • Successful responses are read through a 16 MiB limit. Every uses: is pinned to a SHA with its version.
  • Docs: pass the PEM via the environment or stdin, not argv; revoking does not clear a source's cache. CHANGELOG gains a Security section.

Rationale

The GH_HOST mismatch exits rather than warns, since a warning would still send the token. A --base-url proxy in front of github.com, for which v1.11.0 suggested exporting GH_HOST, sets it inside the command instead: --exec -- env GH_HOST=github.com gh …. That is under Changed in the CHANGELOG.

The ports and foreground rules each fail closed: an unusual setup gets exit 2 or a gh connection error, never a token on another port.

Migration Notes

None beyond the GH_HOST change above. Every new test fails with its fix undone. Verified against a real App: from an https clone of gopherguru/test, the child got a token scoped to it and no GITHUB_APP_PRIVATE_KEY. Foreground detection was checked under a real pseudo-terminal and in a session without one.

Known and left: on macOS, Go documents a narrow window in which a signal can reach a reused PID after the child exits, inherited from the runtime and already true of SIGTERM.

🤖 Generated with Claude Code

@jferrl
jferrl added this pull request to stack #94 October 31, 2026 09:47
Base automatically changed from feat/scope-to-origin to main October 31, 2026 09:48
Independent reviews by Codex and Claude Fable, then three adversarial
passes over the fixes, found ways a credential reached somewhere it
should not:

- --exec passed $GITHUB_APP_PRIVATE_KEY to the command, and the clone
  default to git. Both now get an environment without it or any
  exported token, matched case-insensitively on Windows.
- A --key that is neither PEM nor a file, such as a base64 CI secret,
  was repeated in full in the error. A value longer than a path no
  longer is.
- GH_HOST dropped the API's port, so gh sent the token to 443. Ports
  are kept, a host with one is never rewritten, and IPv6 keeps its
  brackets. A clone's http(s) origin must name the API's port too,
  443 included, since git push hands the token to it.
- An exported GH_HOST naming another instance made gh send the token
  there. token --exec now exits 2 before minting; the JWT is left
  alone, since only the caller knows its App's instance.
- A SIGINT sent to githubauth alone left the command running. On Linux
  and macOS it is passed on unless the command is in the terminal's
  foreground job, which got the Ctrl-C itself.
- Successful responses are read up to 16 MiB, and every workflow action
  is pinned to a commit, since the release job holds the publishing PAT.

Docs say to pass the PEM through the environment or stdin, not argv,
and that revoking a token does not clear a source's cache.
@jferrl
jferrl force-pushed the fix/security-review branch from 6b9d37e to 2212e46 Compare October 31, 2026 09:48
@codecov

codecov Bot commented Oct 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.47826% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.68%. Comparing base (0e33a98) to head (2212e46).

Files with missing lines Patch % Lines
cmd/githubauth/foreground_unix.go 72.72% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #93      +/-   ##
==========================================
- Coverage   97.93%   97.68%   -0.25%     
==========================================
  Files           8        9       +1     
  Lines         871      908      +37     
==========================================
+ Hits          853      887      +34     
- Misses         18       21       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jferrl
jferrl merged commit 0bf620a into main Oct 31, 2026
8 of 10 checks passed
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