Skip to content

feat(proxy): inject GitHub authorization at the MITM proxy so agents never hold usable tokens (#1861) - #4032

Closed
clubanderson wants to merge 1 commit into
v4from
feat/1861-proxy-gh-auth-injection
Closed

feat(proxy): inject GitHub authorization at the MITM proxy so agents never hold usable tokens (#1861)#4032
clubanderson wants to merge 1 commit into
v4from
feat/1861-proxy-gh-auth-injection

Conversation

@clubanderson

Copy link
Copy Markdown
Member

What this ships

The core of #1861, item 1 as re-scoped by triage: GitHub authorization is injected at the MITM proxy, per identified agent, so that with the flag on, nothing an agent holds authenticates anywhere. Opt-in via HIVE_PROXY_INJECT_GH_AUTH=true, default OFF — with the flag unset, token delivery and proxy behavior are byte-identical to today. The fleet keeps working while this soaks; do not flip the default here.

Both prerequisites the triage recorded are honored:

Mechanism

Hub side (pkg/github/proxy_token_source.go, WriteAgentToken): under the flag, the freshly-minted tier-scoped token is diverted to an in-memory registry only the hub process can read, and the agent-visible cache file — the single choke point that gh-wrapper.sh (GH_TOKEN), git-credential-hive.sh (password), and the manager's GITHUB_TOKEN env push for the MCP server all read — receives the visibly-fake placeholder hive-proxy-injected-<agent> instead. No bash/manager changes needed: every agent-side consumer is converted by that one divert, tooling keeps functioning (syntactically-valid credential in place), and any leak of the placeholder is inert and self-diagnosing. The registry is package-level, not on AppAuth, because appAuth is replaced at runtime on key rotation and a proxy closure over one instance would strand it on the stale map.

Proxy side (rewriteGitHubAuth, called after the ACMM/repo/canary gates and before both forwarding branches):

  • Strip: any agent-supplied Authorization header is deleted unconditionally under the flag — even a credential smuggled to an agent through a side channel cannot be spent through the proxy. (Survey note: Authorization is the only header GitHub honors for API/git auth today; the old ?access_token= query param was removed by GitHub in 2020 and no X-*-token request-auth variant exists, so header stripping covers the surface.)
  • Inject: the UID-identified agent's hub-held scoped token — token <t> for REST/GraphQL, Basic x-access-token:<t> for git smart HTTP paths (the exact shape git-credential-hive.sh produced before, so upstream sees nothing new).
  • Unknown agent = no credential, fail loud: when UID attribution fails, the request goes upstream with no Authorization and 401s at GitHub. Deliberate: any fallback (shared cache, hive token) would let an unattributable process ride a real credential, recreating the pre-fix(proxy): stop trusting self-asserted agent identity as a fallback (N7, #3841) #3888 identity hole. The token source is not even consulted (tested).
  • Internal caller passthrough: the hive's own control-plane requests (hive-internal, assigned only via UIDMap.IsInternalUID, unspoofable) pass untouched — they legitimately carry the hive's own App credentials for mint/heartbeat/relay fulfillment.
  • OAuth device-flow endpoints (/login/…) are strip-only — those authenticate the flow via the form body, and an App token there would be wrong.
  • Logging: agent name + injected yes/no only; never token bytes, not even prefixes.

Survey finding: MITM widens under the flag (and why)

Today the proxy MITMs only api.github.com; github.com and registered GHE hosts are opaque tunnels (NeedsMITM). That is fine without injection, but with it an opaque tunnel is a hole: the agent's git credential helper now serves the placeholder, so a git push through an un-decrypted tunnel would reach GitHub carrying the placeholder and 401 — and a smuggled real credential would transit un-stripped. So under the flag (only), hostNeedsMITM widens interception to every GitHub-family host. Injected git requests are additionally marked Connection: close, because the git branch switches to a raw relay after the first request's headers — a keep-alive follow-up on the same conn would stream past the rewrite; forcing per-request connections makes every git exchange re-enter the rewrite. Flag off: interception surface unchanged.

Soak watch-items (why the default must stay off for now)

  • Copilot CLI device flow: with github.com MITM'd, the Copilot CLI's /login traffic is intercepted; if that CLI does not trust the hive CA the flow breaks (the existing GIT_SSL_CAINFO-not-SSL_CERT_FILE comment suggests sensitivity here). First flag-on spoke should verify Copilot login.
  • ioscan fail-closed hives: MITM'd git-receive-pack hits the pre-existing "canary scan unavailable for git push" deny when ioscan.fail_closed is set — loud, but worth knowing before enabling both together.
  • Hub restart: the in-memory registry is empty until the next launch/relaunch/hourly mint; identified agents get fail-loud 401s (WARN names the mint lane) for at most one refresh interval.

Explicitly out of scope (the follow-up that closes #1861)

Test evidence (fail-first / mutation-checked)

New tests: pkg/proxy/auth_inject_test.go (7 tests, httptest-style fake upstream capturing raw bytes), pkg/github/proxy_token_source_test.go (2), pkg/config/proxy_inject_test.go (1). Each guard was mutation-checked — the guard removed, the suite re-run, the failure observed, the guard restored:

Mutation (guard removed) Detecting test Result
Authorization strip deleted …AgentSuppliedAuthorizationNeverReachesUpstream FAIL: agent-supplied credential reached upstream
Flag gate deleted (always rewrite) …FlagOffByteIdenticalPassthrough FAIL: header mutated + source consulted with flag off
Unknown-agent fallback added …UnknownAgentNoInjectionNoFallback FAIL: credential injected + source consulted for ""
WriteAgentToken divert deleted …InjectionDivertsRealTokenToRegistry FAIL: agent-readable cache contains the REAL token
MITM widening deleted TestHostNeedsMITM_InjectionWidensInterception FAIL: github.com/GHE not intercepted under flag
Injection deleted (strip kept) …IdentifiedAgentGetsScopedToken FAIL: upstream saw no Authorization

Suite runs (macOS, -count=1):

  • go test ./pkg/proxy/ -raceok (51s, full package)
  • go test ./pkg/github/ok (46s, full package)
  • go test ./pkg/config/ — new TestProxyInjectGHAuth passes; TestExportEmitsShellExportOnBothPaths + TestSharedCacheIsCreatedPrivate fail identically on clean origin/v4 @ 9f9a65f2 (verified in a pristine worktree) — pre-existing, unrelated (gh-app-token.sh script tests).

Part of #1861

…never hold usable tokens (#1861)

Opt-in via HIVE_PROXY_INJECT_GH_AUTH (default OFF, byte-identical behavior
when unset). When enabled:

- WriteAgentToken diverts the real tier-scoped token to an in-memory
  registry the proxy injects from, and writes the visibly-fake
  placeholder hive-proxy-injected-<agent> to the agent-readable cache
  (the single choke point gh-wrapper.sh, git-credential-hive.sh, and the
  manager's GITHUB_TOKEN env push all read).
- The MITM proxy strips any agent-supplied Authorization header and
  injects the UID-identified agent's hub-held scoped token: token scheme
  for REST/GraphQL, Basic x-access-token for git smart HTTP (with
  Connection: close so keep-alive reuse cannot bypass the rewrite via
  the raw git relay).
- MITM widens to every GitHub-family host (github.com, registered GHE)
  under the flag, because an opaque tunnel would carry the placeholder
  to GitHub un-replaced.
- Unknown agent = no injection, no fallback: the request proceeds
  unauthenticated and fails loud at GitHub. The hive's own control-plane
  calls (internalCallerName, UID-attributed) pass through untouched.
- OAuth device-flow endpoints get strip-only treatment.
- Logs carry agent name + injected yes/no, never token bytes.

Part of #1861

Signed-off-by: Andy Anderson <andy@clubanderson.com>
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Aug 18, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign clubanderson for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow kubestellar-prow Bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Aug 18, 2026
@clubanderson

Copy link
Copy Markdown
Member Author

On hold — do not merge. This changes the GitHub credential path for every agent (behind a default-OFF flag), and the operator wants it reviewed deliberately before it lands even dormant. Leaving the branch and CI as-is; no auto-merge watcher is armed on it.

@kubestellar-prow kubestellar-prow Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 23, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

clubanderson added a commit that referenced this pull request Aug 27, 2026
bin/agent-env-scrub.sh claimed same-uid /proc environ extraction was already closed by the proxy Authorization strip/inject lane from #1861/#4032. That mitigation is still parked in PR #4032, so document the residual as open until that work lands and note that smuggled credentials remain usable against GitHub today.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Aug 27, 2026
This was referenced Sep 1, 2026
@clubanderson

Copy link
Copy Markdown
Member Author

Superseded by #5876 — this feature targets the v5 line, and the port (with v5 adaptations: NeedsInspection composition, proxyHTTP capabilities param, atomic tunnelHalfCloseDrain, hivecommons module path) is now up there. Closing this v4-based PR.

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

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. hold needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inject GitHub auth at the MITM proxy so agents never hold tokens; tighten shared token cache perms

1 participant