LearnNewsExamplesServices
Frontmatter
id15598
titleAdd github-pat auth mode for MCP servers (mirror of gitlab-pat)
stateClosed
labels
enhancementaisecurity
assigneesneo-kimi-phoebe
createdAtJul 20, 2026, 1:48 PM
updatedAtJul 20, 2026, 3:08 PM
githubUrlhttps://github.com/neomjs/neo/issues/15598
authorneo-kimi-phoebe
commentsCount0
parentIssuenull
subIssues
15606 Document github-pat security posture: allowedUsers + auto-provision exclusion
subIssuesCompleted1
subIssuesTotal1
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 20, 2026, 3:08 PM

Add github-pat auth mode for MCP servers (mirror of gitlab-pat)

Closed Backlog/active-chunk-8 enhancementaisecurity
neo-kimi-phoebe
neo-kimi-phoebe commented on Jul 20, 2026, 1:48 PM

Context

Early standalone graduation from Discussion #15595 (Local Runtime Parity), declared in its Graduation Criteria as a do-now decoupler: independently valuable, low-blast, shrinks the parity epic's diff. The production cloud deployment authenticates MCP clients via gitlab-pat; GitHub-based deployments (and any future local-docker parity stack per D#15595) have no equivalent — the only bearer modes today are oidc (full OAuth 2.1 infrastructure), gitlab-pat, and local-bearer (possession-only, no identity). A github-pat mode is the missing mirror: validate a client's GitHub Personal Access Token against GitHub's /user endpoint, no OAuth app required.

Live latest-open sweep: checked latest 20 open issues at 2026-07-20T11:45Z — no equivalent. A2A in-flight claim sweep (last 30, all read-states): no competing claim. KB semantic sweep (github-pat auth mode, type=ticket): no existing ticket; sibling precedent gitlab-pat confirmed as #12377/#12378 (closed, shipped). Exact sweep: #11404 (GitLab workflow parity — unrelated), #15347 (credential redaction — unrelated).

The Problem

Operators deploying the Agent OS MCP servers (KB + MC) against a GitHub-centered identity plane must either stand up a full OIDC provider or fall back to possession-only local-bearer (which carries no identity — every caller shares one anonymous principal, breaking per-user memory scoping and AgentIdentity binding). The gitlab-pat mode proved the lightweight pattern: client mints a PAT, server validates it against the provider's user endpoint, the resolved username becomes the caller identity, and #14388's auto-provision path binds an AgentIdentity. GitHub is the more common identity plane for this repository's ecosystem; the asymmetry is accidental, not designed.

The Architectural Reality

  • ai/mcp/server/shared/services/AuthService.mjs:74 — the gitlab-pat branch in the auth-mode dispatch.
  • ai/mcp/server/shared/services/AuthService.mjs:354-404 — the gitlab-pat verifier: GET {gitlabApiBaseUrl}/api/v4/user with the incoming bearer; 200 resolves {userId: username, source: 'gitlab-pat', authProvider: 'gitlab', authSource: 'gitlab-pat'}; non-200 → InvalidTokenError; server-side validation cache.
  • ai/configBase.mjs:177-229 — the auth config section: mode selector leaf ('oidc' | 'gitlab-pat' | 'local-bearer'), per-mode readiness metadata, gitlabApiBaseUrl leaf (NEO_AUTH_GITLAB_API_BASE_URL), optional OAuth-app gate, optional username allowlist, and autoProvisionIdentitySources (default ['gitlab-pat'], NEO_AUTH_AUTO_PROVISION_IDENTITY_SOURCES).
  • The PRM/discovery contrast: gitlab-pat mode advertises a "naked 401" (no OAuth Protected-Resource Metadata) so PAT clients are not pushed into OAuth DCR — the github-pat mode must replicate this exact 401 shape.
  • Identity provisioning: #14388 (closed) — auth success without a bound AgentIdentity left graph-gated tools failing; the auto-provision sources list is the seam.

The Fix

  1. Config leaves (ai/configBase.mjs auth section, mirroring the gitlab-pat leaves):
    • Extend the auth.mode selector + readiness metadata with 'github-pat'.
    • githubApiBaseUrl leaf, default https://api.github.com, env NEO_AUTH_GITHUB_API_BASE_URL (configurable so GHES / self-managed GitHub works — the same self-managed rationale as gitlabApiBaseUrl).
    • Optional username allowlist leaf (env-driven), empty = any resolved GitHub user.
  2. Verifier (AuthService.mjs, sibling method to the gitlab-pat verifier): GET {githubApiBaseUrl}/user with Authorization: Bearer <token>, Accept: application/vnd.github+json, a User-Agent header (GitHub rejects UA-less requests), and the X-GitHub-Api-Version header. 200 → resolve {userId: login, source: 'github-pat', authProvider: 'github', authSource: 'github-pat'} in the identical AuthInfo shape; 401/403 → InvalidTokenError; ride the same server-side validation cache as gitlab-pat. Naked-401 PRM shape identical to gitlab-pat.
  3. Auto-provision: make 'github-pat' an accepted value of autoProvisionIdentitySources (default list unchanged — deployers opt in explicitly).
  4. Tests: unit specs mirroring the gitlab-pat verifier specs (200→identity, 401→InvalidTokenError, allowlist accept/reject, GHES base-URL override, cache behavior); an integration AuthRejection-class case if the pattern extends cheaply.
  5. Docs: update the auth-mode references in learn/agentos/cloud-deployment/Security.md + Configuration.md (mode matrix + one connect recipe paragraph).

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
AiConfig.auth.mode ai/configBase.mjs:196 Accept 'github-pat' Missing config leaves → readiness error at boot, never silent Configuration.md unit: config readiness spec
AiConfig.auth.githubApiBaseUrl new leaf, NEO_AUTH_GITHUB_API_BASE_URL Default https://api.github.com; GHES override Trailing-slash normalization like gitlab Configuration.md unit: base-url resolution spec
Bearer validation AuthService.mjs new verifier GET /user → GitHub login as userId 401/403/network → InvalidTokenError Security.md unit: verifier specs
401 response shape gitlab-pat PRM behavior Naked 401, no OAuth DCR advertisement n/a Security.md unit: 401-shape spec
autoProvisionIdentitySources ai/configBase.mjs:229 'github-pat' accepted as a source Default list unchanged Security.md unit: provisioning-source spec

Decision Record impact

aligned-with ADR 0014 (cloud deployment topology) — additive auth mode behind config; no ADR conflict. Discussion-origin classification: Not needed (low-blast early graduation per D#15595 Graduation Criteria).

Discussion Criteria Mapping

  • D#15595 Graduation Criteria: "Early standalone graduations: … the github-pat auth mode may graduate as [GRADUATED_TO_TICKET] items ahead of the main matrix" → this ticket.
  • D#15595 divergence #5 (Public surface: "A github-pat auth mode (mirror of gitlab-pat) does not exist yet and is needed regardless") → AC 1–5 below.

Acceptance Criteria

  • NEO_AUTH_MODE=github-pat boots kb-server and mc-server with bearer validation against GET {githubApiBaseUrl}/user
  • Valid GitHub PAT (classic and fine-grained) resolves userId = GitHub login, authSource: 'github-pat', in the same AuthInfo shape as gitlab-pat
  • Invalid/revoked token → 401 naked (no PRM), identical shape to gitlab-pat rejection
  • NEO_AUTH_GITHUB_API_BASE_URL override works (GHES-style base)
  • Optional username allowlist enforced when configured; empty = any resolved user
  • 'github-pat' accepted in autoProvisionIdentitySources; default list unchanged
  • Unit specs green (verifier, config readiness, 401 shape, allowlist, cache)
  • Docs: Security.md + Configuration.md auth-mode matrices include github-pat

Out of Scope

  • OAuth 2.1 / OIDC changes (the oidc mode is untouched)
  • GitHub App installation tokens or JWT app auth (PAT-class tokens only)
  • Fine-grained PAT resource scoping enforcement beyond what /user returns (the server validates identity, not repo permissions)
  • Local-docker parity wiring (D#15595's epic owns that; this ticket only unblocks it)

Avoided Traps / Gold Standards Rejected

  • Full OAuth app + web flow — the entire point of the PAT modes is zero OAuth infrastructure; rejected as deployment-weight creep.
  • Hardcoding api.github.com — rejected: GHES/self-managed parity with gitlabApiBaseUrl costs one config leaf.
  • Token introspection via GraphQL — rejected: REST /user is the minimal, cache-friendly identity call; GraphQL adds a schema dependency for zero gain.

Related

  • Discussion: #15595 (source; this is an early standalone graduation)
  • Sibling precedent: #12377 / #12378 (gitlab-pat mode, shipped)
  • Identity provisioning: #14388 (auto-provision AgentIdentity for PAT users)
  • Forward consumer: D#15595's parity epic (local-docker stack auth)

Origin Session ID: 8d4ce1c3-0bf2-4bb0-bad9-e49836248afe

Retrieval Hint: "gitlab-pat AuthService verifier github-pat mirror auth mode"