Skip to content

fix(git-token-service): define the GitHub session capability request surface - #8767

Open
iscekic wants to merge 5 commits into
mainfrom
drain/tighten-github-capability-scope
Open

iscekic wants to merge 5 commits into
mainfrom
drain/tighten-github-capability-scope

Conversation

@iscekic

@iscekic iscekic commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — local end-to-end harness on this VM. One process composed the real capability issuance and redemption code from services/git-token-service/src/index.ts with the real containment outbound handler services/cloud-agent-next/src/sandbox-outbound.ts, stubbed the provider adapters, captured the upstream fetch, and served a TLS endpoint for api.github.com that the real gh 2.103.2 client trusted:

gh api graphql -f query='query { viewer { login } }'
    client request:        POST https://api.github.com/graphql  Authorization: token <capability>
    user-source result:    upstream Authorization: Bearer <user credential>
    installation-source:   upstream Authorization: Bearer <installation credential>

The same harness over the surrounding surface with an acme/repo capability:

POST api.github.com/graphql                                     -> HTTP 200 upstream=Bearer <user credential>
POST api.github.com/repos/acme/repo/issues/1/comments           -> HTTP 200 upstream=Bearer <user credential>
GET  api.github.com/user                                        -> HTTP 200 upstream=Bearer <user credential>
GET  github.com/acme/repo.git/info/refs?service=git-upload-pack  -> HTTP 200 upstream=Basic x-access-token:<user credential>
GET  api.github.com/repos/acme/other/pulls/7                    -> HTTP 502, no upstream request
GET  github.com/acme/other.git/info/refs?service=git-upload-pack  -> HTTP 200 forwarded without Authorization
POST api.github.com/graphql, recorded user authorization unresolved -> HTTP 502, no upstream request

Change

services/git-token-service/src/index.ts

  • POST api.github.com/graphql is answered with the credential the capability was issued for: the session user's token for a user source, the repository-scoped installation token for an installation source. A user-source capability resolves its recorded authorization and identity before the request is answered.
  • One credential kind now covers GraphQL and the repository-addressed surfaces, so the redemption path has a single authorization branch instead of a GraphQL-specific one.
  • The request surface is unchanged otherwise: api.github.com paths under /repos/<owner>/<repo>, GET/HEAD /user, the repository's Git smart-HTTP and LFS endpoints on github.com with or without the .git suffix, release-asset paths under /repos/<owner>/<repo> on uploads.github.com, and registry reads inside @<owner> on npm.pkg.github.com. Any other host, path, or method still returns repository_mismatch, invalid_upstream_request, invalid_upstream_url, or upstream_host_not_allowed before an authorization value is produced.
  • A Git request for a different repository is still forwarded without an authorization header.

services/git-token-service/src/index.test.ts

  • GraphQL redemption covered for both credential sources (user source sends the user token and consults the recorded authorization; installation source sends the installation token), and GraphQL added to the user-source own-repository request list.

Verification

  • Real gh 2.103.2 through the local TLS endpoint and the real containment outbound handler: both credential sources observed at the upstream request.
  • Composed run above: 7 surface cases over the real redemption and forwarding code.
  • pnpm --filter cloudflare-git-token-service run test: 24 files, 704 tests passed.
  • pnpm --filter cloudflare-git-token-service run typecheck and run lint: passed.
  • pnpm --filter cloud-agent-next exec vitest run src/sandbox-outbound.test.ts: 128 tests passed.
  • pnpm --filter cloud-agent-next run typecheck: passed.

Boundary: the provider adapters (installation lookup, token minting, user-authorized token selection) and the upstream GitHub response are stubbed; no live GitHub call was made. The forge integration harness fakes the token service at the network boundary, so it cannot exercise this policy and was not used.

Proxy answers

  1. Question: How should bound GitHub capability redemption treat POST api.github.com/graphql?
    Answer: Use the installation token for GraphQL — superseded by answer 6 for which credential answers GraphQL
    Confidence: 0.55
  2. Question: Should GET api.github.com/user remain available to a bound capability?
    Answer: Allow GET and HEAD /user only
    Confidence: 0.75
  3. Question: What should happen to npm.pkg.github.com requests?
    Answer: Keep it, confined to the owner's package scope
    Confidence: 0.60
  4. Question: How should a Git request for a different repository resolve?
    Answer: Forward without authorization
    Confidence: 0.45
  5. Question: Should POST api.github.com/graphql answer with the repository-scoped installation credential only after the capability's recorded authorization resolves?
    Answer: Require the pin, keep the installation credential — the pin requirement stands; which credential answers is superseded by answer 6
    Confidence: 0.78
  6. Question: For a user-source capability, should GraphQL carry the session user's token while an installation-source capability keeps the installation token?
    Answer: User token for user-source, installation token for installation-source
    Confidence: 0.86

Production prerequisites

None.

…surface

- Bound GitHub capabilities carry their repository's request surface: REST under
  /repos/<owner>/<repo>, the repository's Git smart-HTTP and LFS endpoints,
  release assets under that repository, and registry reads in the owner's
  package scope.
- Share the path, endpoint, and decode checks between the legacy and bound
  validators.
- GraphQL requests are answered with the repository-scoped installation
  credential.
- Identity reads (GET/HEAD /user) remain available, and Git requests for other
  public repositories are forwarded anonymously.
- Clone URLs work with or without the .git suffix.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening labels Oct 23, 2026
Comment thread services/git-token-service/src/index.ts Outdated
Comment thread services/git-token-service/src/index.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit 5a1d2912 is a comment-only clarification of the GraphQL branch in validateGitHubCapabilityUpstream; the wording matches the redemption path, and no new defects were introduced.

Assumptions: [INFERENCE] The previously reported GraphQL repository-confinement limitation is the maintainer's explicitly accepted design (PR proxy answer 6 and the owner's "Accepted by design" reply), and the new validator comment records the endpoint property and the credential chosen per source, which was the suggested remedy. The comment is accurate against current HEAD: claims.source === 'user' routes to redeemPinnedUserAuthorization, otherwise getCloudAgentAuthForRepo supplies the repository-scoped installation token. No code execution or live GitHub/Workers run was performed in this read-only review.

Files Reviewed (1 file)
  • services/git-token-service/src/index.ts - 0 issues (comment-only change to the GraphQL validator branch; behavior at index.ts:551-558 unchanged and consistent with the redemption path at index.ts:1195-1219)
Previous Review Summaries (5 snapshots, latest commit ef39403)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit ef39403)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The GraphQL credential now follows the capability source as requested, but POST /graphql remains the one surface where a user-source capability's token is not repository-confined.

Assumptions: [INFERENCE] A GitHub App user-to-server token covers every repository and organization the user's authorization reaches, and the containment handler forwards the GraphQL body unmodified, so repository confinement cannot be enforced on /graphql; the user-token choice itself is the maintainer's explicit request in the PR thread. No live GitHub or Workers-runtime execution was performed in this read-only review and the PR's reported test runs were not re-executed here.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
services/git-token-service/src/index.ts 557 POST /graphql is not repository-confined and now receives the user's token for user-source capabilities, widening scope beyond the recorded repository; owner-requested, so record or constrain the boundary
Files Reviewed (2 files)
  • services/git-token-service/src/index.ts - 1 issue (credential-source branch verified; user-source pin re-resolution, identity match, and failure mapping unchanged for other surfaces)
  • services/git-token-service/src/index.test.ts - 0 issues (GraphQL coverage for both credential sources and the unresolved-pin refusal match the implementation)

Fix these issues in Kilo Cloud

Previous review (commit d9d8bb8)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental commit d9d8bb81d closes the previously reported GraphQL gap by requiring a user-pinned capability to re-resolve its user authorization and full session identity before the installation credential is used; no new defects found.

Assumptions: [INFERENCE] POST /graphql answering with the repository-scoped installation credential remains the owner-approved policy recorded in the PR's "Proxy answers" section. The prior WARNING on index.ts is resolved for the reported failure modes: redemption now calls selectUserAuthorization via redeemPinnedUserAuthorization, returns source_unavailable/integration_mismatch/identity_mismatch when the pin no longer resolves, and the installation credential stays repository-scoped by getTokenForRepo. The residual case (user with push access receiving the installation credential for GraphQL) is limited to a repository the user can already push to and is the owner's stated design choice. The GraphQL path now performs an extra managed-installation lookup and user-authorization probe per request; this is a cost characteristic, not a defect. No live GitHub or Workers-runtime execution was performed in this read-only review, and the PR's reported test runs were not re-executed here.

Files Reviewed (2 files)
  • services/git-token-service/src/index.ts - 0 issues (user-source GraphQL pin re-resolution, identity match, and failure mapping verified on changed lines)
  • services/git-token-service/src/index.test.ts - 0 issues (new refusal tests and updated selectUserAuthorization expectation match the implementation)

Previous review (commit 929dcc6)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit makes the Git endpoint matcher suffix-based and closes the previously reported LFS lock gap; the only remaining item is the GraphQL installation-credential behavior, which the PR records as an intentional owner decision and which is already tracked by the active comment on index.ts:1220.

Assumptions: [INFERENCE] POST /graphql answering with the repository-scoped installation credential is the owner-approved policy from the PR's "Proxy answers" section, and the token is repository-scoped because getTokenForRepo mints it with [repoName]. The anonymous github.com forwarding path grants no credential, so it is a design choice rather than a boundary crossing. No new defects were found in 929dcc67. No live GitHub or Workers-runtime execution was performed in this read-only review; the service test suite reported by the PR was not re-run here.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
services/git-token-service/src/index.ts 1220 POST /graphql still mints the installation credential for source: 'user' capabilities and skips the pinned user authorization; identity is checked only on installation id and app type, so a user-pinned session acts as the installation bot. Owner-declined as intentional; residual auth-boundary risk remains.
Files Reviewed (5 files)
  • services/git-token-service/src/index.ts - 1 issue (carried forward; LFS lock endpoints resolved by patch 2, path/decode checks verified)
  • services/git-token-service/src/index.test.ts - 0 issues (LFS lock and repository-scope coverage verified against the new matcher)
  • services/cloud-agent-next/src/sandbox-outbound.ts - 0 issues (authorization ?? undefined still strips the capability header via forwardRedeemedRequest)
  • services/cloud-agent-next/src/sandbox-outbound.test.ts - 0 issues (tests the anonymous forward with the capability header removed)
  • services/cloud-agent-next/src/types.ts - 0 issues (nullable redemption result matches the only consumer)

Fix these issues in Kilo Cloud

Previous review

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit closes the Git LFS lock gap and consolidates the Git endpoint matcher with no new defects; the only open item is the previously reported GraphQL installation-credential choice, which the PR records as an intentional owner decision.

Assumptions: [INFERENCE] The GraphQL installation-token behavior is the owner-approved policy from the PR's "Proxy answers" section (confidence 0.55), so it is retained as a design risk rather than a defect introduced by this commit. The LFS lock endpoints were verified against the new matcher and tests.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
services/git-token-service/src/index.ts 1220 POST /graphql still mints the installation credential for source: 'user' capabilities, skipping the pinned user authorization; owner-declined as intentional, residual auth-boundary risk remains
Files Reviewed (2 files)
  • services/git-token-service/src/index.ts - 0 new issues (LFS lock endpoints fixed; GraphQL finding retained)
  • services/git-token-service/src/index.test.ts - 0 issues (LFS lock coverage added)

Previous review (commit 85db3b2)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

Two gaps in the new GitHub capability request surface: POST /graphql returns the installation credential for user-sourced capabilities, and Git LFS lock endpoints are missing from the bound allowlist.

Assumptions: [INFERENCE] LFS locking is expected to work for GitHub sessions (git-lfs ships in the sandbox runtime) and the GraphQL credential choice was intentional, but its interaction with the recorded credential source is not addressed.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
services/git-token-service/src/index.ts 1223 POST /graphql mints the installation credential for source: 'user' capabilities, bypassing the pinned-user no-fallback contract
services/git-token-service/src/index.ts 457 Git LFS lock endpoints (POST/GET .../info/lfs/locks, POST .../locks/{id}/unlock) are missing from the bound allowlist and now return invalid_upstream_request
Files Reviewed (5 files)
  • services/git-token-service/src/index.ts - 2 issues
  • services/git-token-service/src/index.test.ts - 0 issues
  • services/cloud-agent-next/src/sandbox-outbound.ts - 0 issues
  • services/cloud-agent-next/src/sandbox-outbound.test.ts - 0 issues
  • services/cloud-agent-next/src/types.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 37.4K · Output: 3.8K · Cached: 278.1K

Review guidance: REVIEW.md from base branch main

…ository root

`git lfs lock`, `git lfs locks`, and `git lfs unlock` use the repository's
/info/lfs/locks endpoints. Matching the endpoint by the path after the
repository root keeps every Git endpoint handled consistently.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. intent:ok Janitor change confirmed as a real bug fix, not intended behavior labels Oct 23, 2026
…mption

A bound capability recorded with a user source now resolves that pin before
GraphQL redemption answers with the repository-scoped installation
credential. A pin that no longer resolves, or whose attribution changed,
returns source_unavailable or identity_mismatch and no upstream request is
made.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Changes requested: keep the user's identity on GraphQL for user-authorized sessions.

  • At the current head, POST /graphql on a user-source v2 capability is answered with the repo-scoped app installation token.
  • GraphQL mutations such as gh pr create, gh pr comment and gh pr merge would then be authored by the Kilo app instead of the user, with the app's permissions.
  • For user-source capabilities, send GraphQL with the user's token after the existing user-pin and identity checks. Keep the installation token for installation-source capabilities.
  • Keep the rest of the request surface unchanged, including the refusal of other repositories.
  • Add tests for both credential sources on POST /graphql, and update the PR body's proxy answer 1.

…ential

A user-source bound capability now sends the recorded user's token on
GraphQL after its pin and identity checks, so GraphQL mutations are
authored by the session user instead of the app. Installation-source
capabilities keep the repository-scoped installation token. The
repository confinement of the other paths is unchanged.
@iscekic iscekic removed the merge:human Substantive functional risk requires human review. label Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Done at ef39403.

POST api.github.com/graphql now carries the credential the capability was issued for: a user-source capability resolves its recorded authorization and identity first and sends the user's token; an installation-source capability sends the repository-scoped installation token. The rest of the request surface is unchanged, and the redemption path has a single authorization branch instead of a GraphQL-specific one.

Tests cover GraphQL for both credential sources, and GraphQL is part of the user-source own-repository request list. Verification on this VM: real gh 2.103.2 through the containment outbound handler against a local TLS endpoint for api.github.com, both sources observed at the upstream request, plus the surrounding surface and the unresolved-authorization branch; service suite, typecheck and lint pass. The PR body's proxy answers section records the policy.

Comment thread services/git-token-service/src/index.ts
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change merge:human Substantive functional risk requires human review. labels Oct 24, 2026
The comment records that GraphQL is not addressed by repository path and
that the capability's own credential answers it for each source.
@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 24, 2026
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 24, 2026

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

Labels

drain Work item from the findings drain intent:ok Janitor change confirmed as a real bug fix, not intended behavior janitor:trust Janitor finding: trust boundary hardening merge:human Substantive functional risk requires human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant