GitHub
Proxy for the GitHub REST API. Each managed resource exposes seven MCP
tools mapped onto specific api.github.com routes, with per-route OAuth
scopes (gh:repo:read, gh:repo:write, gh:issues:read, gh:issues:write,
gh:workflows:write).
Overview
Pick an auth mode at install time:
idp_passthrough(recommended) — each caller's own GitHub OAuth token is forwarded upstream. The agent acts as the user; their GitHub permissions apply on top of PBAC policy. Requires a GitHub IdP registered on the AS (one OAuth App per instance).static— a single Personal Access Token (PAT) is forwarded for every caller. Use for unattended automations where the operator owns the GitHub identity and there is no human in the loop. The PAT is stored as a secret reference; it is not typed into a form field directly.
A managed resource is bound to one auth mode. To serve both shapes of
caller, create two managed resources (e.g. github-user and
github-bot). Policy can key off input.resource.name to apply
different rules.
Setting up GitHub as an IdP
The admin portal's "Connect a resource" wizard inlines this step the
first time you install GitHub with idp_passthrough. The flow is:
- Open GitHub Developer Settings → OAuth Apps → New OAuth App.
- Set Homepage URL to your AS issuer (e.g.
http://localhost:8080). - Set Authorization callback URL to
<issuer>/oauth2/callback— the wizard shows the exact value with a copy button. - Generate a client secret. Copy both the Client ID and Client secret into the wizard.
GitHub OAuth Apps don't have you pre-pick scopes at registration —
scopes are requested at authorize time and approved by the user. The
connector requests read:user, repo, and read:org so callers can
read user metadata, work with both public and private repos they own,
and resolve org memberships for ABAC decisions. If your tenants only
need read access, you can drop repo to its narrower equivalent
(public_repo) by editing the upstream_scopes on the managed
resource after install.
Policy patterns
Unlike Gmail and Google Drive, the GitHub connector does not ship
its own Rego bundle today. The reference policy patterns below live in
the global evaluations_ext.rego (in the AS's bundled OPA policies)
and are wired up by the managed-gateway demo's seed scripts. Treat
them as a recipe, not a turnkey install:
1. Repository owner allowlist (RBAC by namespace)
deny contains msg if {
_is_github(context)
owner := tool_arguments.owner
attrs := data.github_subject_attributes[subject.sub]
not owner in attrs.allowed_orgs
msg := sprintf("github: %v is not in your allowed orgs", [owner])
}
Every connector route includes a {owner} path variable, which the
gateway extracts as tool_arguments.owner. Subject attributes seed
each developer with the orgs (or personal namespaces) they are allowed
to touch. The same rule covers all seven tools — the operator only
needs to maintain the per-subject allowed_orgs list.
2. Sensitive file path block
deny contains msg if {
_is_github(context)
path := tool_arguments.path
_github_sensitive_path(path)
msg := sprintf("github: path %v is blocked", [path])
}
_github_sensitive_path(p) if contains(p, ".env")
_github_sensitive_path(p) if endswith(p, ".key")
_github_sensitive_path(p) if endswith(p, ".pem")
_github_sensitive_path(p) if contains(p, "secrets/")
Fires for get_repo_file and upsert_repo_file. No subject attributes
needed — the rule depends only on the URL path. Customize the
predicates to match your secret-scanning conventions.
3. Inactive-user kill switch
deny contains msg if {
_is_github(context)
attrs := data.github_subject_attributes[subject.sub]
attrs.employment_status != "active"
msg := "github: inactive employee"
}
Flipping employment_status for a user blocks every GitHub tool call
within the OPA bundle poll interval (~2 s). No token revocation is
needed — the deny fires at introspect time, so already-issued tokens
are immediately blocked.
Subject-attribute schema
The reference rules read from data.github_subject_attributes[<sub>].
The shape used by the demo:
{
"allowed_orgs": ["acme", "acme-research"],
"teams": ["eng", "platform"],
"employment_status": "active",
"clearance": "high"
}
The sub claim on a token is a numeric GitHub user ID (not the
login). Find it via https://api.github.com/users/<login> and store
the id field as the key.
Scope model
| Scope | Used for | |
|---|---|---|
| PBAC scopes (internal) | gh:repo:read, gh:repo:write, gh:issues:read, gh:issues:write, gh:workflows:write | Per-route OAuth gating on the managed gateway. A token without gh:repo:write cannot reach upsert_repo_file regardless of ABAC outcome. |
| Upstream OAuth scopes | read:user, repo, read:org | The grants the user is asked to approve when they sign in to GitHub via the AS. |
Splitting reads from writes at the OAuth scope layer keeps the ABAC policy small — most operator concerns (who can write where, what paths are sensitive) compose with the scope gate rather than reimplementing it.
Static (PAT) mode
When the connector is installed with upstream_auth.type = "static",
the wizard asks for a secret reference (pat_env) instead of running
the IdP setup. Create the secret first via the Secrets page or inline
via the form's + Add secret button. The PAT must carry whichever
GitHub OAuth scopes the routes will exercise — typically repo and
workflow.
PAT mode forwards the same token for every caller. Subject attributes
still apply (the PBAC sub and scopes come from the token PBAC issued,
not the upstream PAT) but you lose the per-user GitHub permission
layer. Use this mode for automations where the operator owns the
identity and the permission model is enforced entirely in PBAC.
Verifying the install
A direct introspect probe is the fastest way to confirm policy reaches OPA correctly:
curl -s -X POST "$PBAC_AS_URL/oauth/introspect" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "token=$TOKEN" \
--data-urlencode 'context={
"resource_type": "urn:connector:identos:github",
"resource_name": "github",
"scopes": ["gh:repo:read"],
"tool_arguments": {"owner": "acme", "repo": "demo", "path": ".env"}
}' | jq '{active, allow}'
{ "active": true, "allow": false } for a .env path confirms the
sensitive-path rule fires. Switch path to README.md to confirm the
allow path. The full validation runbook lives at
docs/superpowers/runbooks/github-mcp-connector-validation.md.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Wizard shows "Step 1 · Set up GitHub sign-in" but you want PAT mode | Pick static from the Authentication dropdown in Step 2; the wizard now treats Step 1 as advisory and lets you submit. |
| First-time PKCE login fails with a JSON parse error | Older AS versions sent no Accept header to GitHub's token endpoint. Upgrade — the fix is in IdpFederationService. |
idp_passthrough returns 401 from api.github.com after PBAC allows | The user's GitHub token does not carry the requested scope (e.g. repo for a private repo). Have them re-authorize the OAuth App and accept the missing scope. |
| All tools return 403 even for an authenticated user | If you seeded subject attributes, confirm sub is the user's numeric GitHub ID. The login string will silently miss every rule body and fall through to deny. |
Manifest reference
- ID:
identos.github - Version:
1.0.0 - Resource type:
urn:connector:identos:github
Supported auth modes
| Type | Details |
|---|---|
idp_passthrough | requires IdP github |
static | scheme bearer; setup fields: pat_env |
Setup fields
| ID | Label | Default | Secret? | Notes |
|---|---|---|---|---|
upstream_auth.type | Authentication | idp_passthrough | no | idp_passthrough forwards each user's own GitHub OAuth token (recommended). static uses a single shared Personal Access Token (machine identity). |
pat_env | Personal Access Token | — | yes | Pick a secret containing the GitHub PAT. Required only for the static auth mode. / shown when upstream_auth.type == 'static' |
Scopes
| Scope |
|---|
gh:repo:read |
gh:repo:write |
gh:issues:read |
gh:issues:write |
gh:workflows:write |
Routes
| Method | Pattern | Scope | Resource template |
|---|---|---|---|
GET | /repos/{owner}/{repo} | gh:repo:read | github://{{owner}}/{{repo}} |
GET | /repos/{owner}/{repo}/contents/{path:.*} | gh:repo:read | github://{{owner}}/{{repo}} |
PUT | /repos/{owner}/{repo}/contents/{path:.*} | gh:repo:write | github://{{owner}}/{{repo}} |
GET | /repos/{owner}/{repo}/issues | gh:issues:read | github://{{owner}}/{{repo}} |
POST | /repos/{owner}/{repo}/issues | gh:issues:write | github://{{owner}}/{{repo}} |
PATCH | /repos/{owner}/{repo}/issues/{issue_number} | gh:issues:write | github://{{owner}}/{{repo}}/issues/{{issue_number}} |
POST | /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches | gh:workflows:write | github://{{owner}}/{{repo}}/workflows/{{workflow_id}} |
MCP tools
| Name | Scope | Description |
|---|---|---|
get_repo | gh:repo:read | Get metadata for a GitHub repository. |
get_repo_file | gh:repo:read | Read a file or directory listing from a GitHub repository at a given path. |
upsert_repo_file | gh:repo:write | Create or update a file in a GitHub repository. |
list_issues | gh:issues:read | List issues in a GitHub repository. |
create_issue | gh:issues:write | Open a new issue in a GitHub repository. |
update_issue | gh:issues:write | Update fields on an existing GitHub issue. |
dispatch_workflow | gh:workflows:write | Trigger a manual run of a GitHub Actions workflow on a given ref, with optional inputs. |