patless
v0.1.1
Published
Vendor-neutral GitHub OIDC exchange, PAT migration, and lifecycle proof for MCP agents
Maintainers
Readme
PATless
Remove long-lived PATs from MCP agent workflows.
PATless is a vendor-neutral GitHub Action and CLI. The Action exchanges a job's OIDC identity with your authorization provider for a short-lived credential bound to one MCP resource:
- uses: bharath31/patless@FULL_40_CHARACTER_COMMIT_SHA
id: mcp_auth
with:
token_endpoint: https://auth.example.com/oauth/token
audience: https://auth.example.com/workloads
resource: https://mcp.example.com/mcp
scope: tools:readThe CLI finds existing PAT references, generates conservative GitHub Actions migrations, and proves the new credential can be granted, used, revoked, and rejected after revocation:
patless scan
patless migrate ...
patless prove ...No browser flow. No required identity vendor. No secret values in scan reports, migration manifests, lifecycle evidence, or broker audit events.
PATless is not an identity provider. Auth0, a cloud authorization service, or the bundled reference broker can sit behind it. The Action handles the runtime exchange; the CLI handles find → replace → prove.
v0.1 is an early preview, not a production release. The reference broker is single-process, and the workload-identity exchange follows the request shape under discussion in MCP SEP-1933. Pin integrations and expect that draft contract to evolve.
The GitHub Actions problem
A coding agent needs to call a protected MCP server from CI, so the workflow gets a standing credential:
jobs:
agent:
permissions:
contents: read
steps:
- name: Run MCP coding agent
env:
MCP_TOKEN: ${{ secrets.ENGINEERING_MCP_PAT }}
run: npx coding-agentGitHub encrypts the secret value, but the PAT can still outlive the job, be reused outside that workload, cover more than one MCP resource, and require manual rotation. Moving it to a different secret store protects storage; it does not remove the standing credential.
GitHub Actions can instead mint an ephemeral OIDC identity for one job. PATless adds the missing last mile: discover the old reference, wire that identity to the team's chosen token endpoint, and verify the replacement end to end. GitHub documents that id-token: write only permits requesting the OIDC token; it does not itself grant repository or resource access (GitHub OIDC reference).
After migration, the workflow uses an exchange step and passes only its short-lived output to the agent:
jobs:
agent:
permissions:
contents: read
id-token: write
steps:
- name: Exchange GitHub identity for MCP credential
id: mcp_auth
uses: bharath31/patless@0123456789abcdef0123456789abcdef01234567
with:
token_endpoint: https://auth.example.com/oauth/token
audience: https://auth.example.com/workloads
resource: https://mcp.example.com/mcp
scope: tools:read tools:call
- name: Run MCP coding agent
env:
MCP_TOKEN: ${{ steps.mcp_auth.outputs.access_token }}
run: npx coding-agentThe exact action SHA above is illustrative. Replace it with the published commit you reviewed; PATless refuses --apply with a floating action tag.
Try it from source
PATless currently requires Node.js 22 or newer.
npm install -g patlessOr work from source:
npm install
npm test
npm run demo:github-actionsThe demo creates a disposable GitHub Actions workflow containing a PAT reference, scans it, applies the migration, re-scans it, then starts a local OIDC issuer, HTTP broker, and MCP echo server. It exchanges two fresh assertions, makes one allowed MCP tool call, revokes the credential, retries it, and emits secret-free evidence of the HTTP 401 denial. npm run demo:lifecycle runs only the broker lifecycle.
1. Find
node ./bin/patless.js scan --root . --out patless-scan.jsonThe GitHub Actions scanner currently detects:
- likely MCP credential references such as
${{ secrets.MCP_PAT }}in step-levelenvandwithvalues; - well-known hardcoded GitHub and GitLab PAT formats in workflow and MCP configuration files; and
- whether a workflow can be rewritten without guessing about implicit GitHub permissions.
It does not call the GitHub Secrets API or resolve ${{ secrets.* }}. Hardcoded values are redacted before entering a finding. ${{ secrets.GITHUB_TOKEN }} is ignored because GitHub creates that token per job; PATless targets standing credentials for other protected MCP resources.
Use the scanner as a CI guard after migration:
node ./bin/patless.js scan --root . --fail-on findingsExit code 2 means the selected threshold found credential debt; operational errors use exit code 1.
2. Replace
Preview a migration first:
node ./bin/patless.js migrate \
--report patless-scan.json \
--token-endpoint https://auth.example.com/oauth/token \
--audience https://auth.example.com/workloads \
--resource https://mcp.example.com/mcp \
--scope tools:read \
--scope tools:call \
--action bharath31/patless@FULL_40_CHARACTER_COMMIT_SHAThe default is a dry run. Apply the reviewed change explicitly:
node ./bin/patless.js migrate \
--report patless-scan.json \
--token-endpoint https://auth.example.com/oauth/token \
--audience https://auth.example.com/workloads \
--resource https://mcp.example.com/mcp \
--scope tools:read \
--scope tools:call \
--action bharath31/patless@FULL_40_CHARACTER_COMMIT_SHA \
--applyPATless preserves an explicit job permission map, or copies an explicit workflow-level map into only the changed job before adding id-token: write. If permissions are implicit or expressed as a scalar such as read-all, it stops and explains the manual prerequisite. It will not silently change the effective permissions of a working job.
An applied migration writes a manifest under .patless/. The manifest contains file hashes and removed reference names, never credential values. PATless immediately re-scans the repository and records whether each migrated reference is absent.
That removes the standing credential from the workload, but it does not invalidate the original value at its issuer. The manifest marks legacy credential retirement as a required external action: revoke the old PAT where it was issued, then delete the obsolete GitHub secret.
3. Prove
From a GitHub Actions job with id-token: write:
node ./bin/patless.js prove \
--github \
--migration .patless/migration_ID.json \
--token-endpoint https://auth.example.com/oauth/token \
--revoke-endpoint https://auth.example.com/oauth/revoke \
--audience https://auth.example.com/workloads \
--resource https://mcp.example.com/mcp \
--scope tools:read \
--method tools/listprove requests a fresh workload assertion for grant and another for revocation. It succeeds only when all checks pass:
- the migrated PAT reference is still absent;
- the provider grants a resource-bound, scoped credential;
- that credential completes the requested MCP operation;
- revocation succeeds; and
- the same MCP request then receives an actionable HTTP 401 Bearer challenge.
The resulting patless.lifecycle-proof/v1 document contains timestamps, scopes, resource identifiers, HTTP outcomes, and scan/migration IDs. It never contains either OIDC assertion or the issued access token.
The lifecycle proof revokes the new short-lived credential to verify fail-closed behavior. It does not claim to revoke the old PAT; retirement of that credential remains an issuer-specific release step.
Provider contract
The exchange action is vendor-neutral at the protocol boundary. It sends this form request:
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=<ephemeral workload JWT>
resource=<canonical MCP server URI>
scope=<space-separated scopes>The token endpoint returns an OAuth Bearer access_token and should return expires_in, scope, and resource. The resource value is mandatory in PATless because current MCP authorization requires tokens to be issued for the canonical MCP server URI (MCP authorization specification).
The v0.1 proof runner uses the reference broker's revocation profile: a form POST containing a fresh assertion and the short-lived token. Providers with a different revocation contract need a small adapter. See the provider contract.
Is this competing with Auth0?
No. If Auth0 accepts GitHub workload identity and returns a resource-bound MCP credential, it is a PATless backend. Auth0 owns identity policy and token issuance; PATless owns discovery, repository migration, and lifecycle evidence. If a runtime implements the exchange natively, PATless can generate that runtime configuration instead of its own action.
Reference broker
The bundled broker exists so a team can test the complete lifecycle without waiting for a provider integration. It validates OIDC signatures and exact issuer/audience/subject policy; rejects reused jti values; caps access-token lifetime to both broker policy and source-identity expiry; binds every token to one configured MCP resource; removes bearer credentials before proxying; and stores only SHA-256 token hashes.
It also publishes OAuth authorization-server metadata, RFC 9728 protected-resource metadata, standards-shaped Bearer challenges, and uses the MCP 2026-07-28 per-request HTTP metadata (MCP-Protocol-Version, Mcp-Method, and Mcp-Name).
node ./bin/patless.js serve --config ./examples/broker.config.jsonThe reference broker is intentionally single-process. Credentials and revocations live in memory; restart invalidates all of them. Do not run multiple replicas or expose the upstream MCP server around the broker. See the HTTP API and threat model.
What PATless does not claim
- It cannot replace a PAT unless the target MCP resource or its authorization service accepts workload federation.
- A short-lived access token is still a bearer credential during its lifetime; “PATless” does not mean tokenless.
- The first scanner targets GitHub Actions and common MCP config files, not every CI or workload platform.
- It does not provide end-user OAuth consent, SSO, a gateway fleet, SIEM integration, or an enterprise dashboard.
- The bundled MCP server is a protocol test target, not the official MCP conformance suite.
- v0.1 evidence is structured but not cryptographically signed; preserve it in a trusted CI artifact store or attach your own attestation.
Product and release status
The product is a personal project by Bharath Natarajan under bharath.sh. The public surfaces are patless.bharath.sh, bharath31/patless, the npm package patless, and the patless CLI. mcp-auth and headless-auth remain temporary compatibility aliases during the rename.
PATless is copyright 2026 Bharath Natarajan and licensed under the Apache License 2.0. Before relying on the GitHub Action, pin it to the full commit SHA you reviewed. The reference broker remains a lifecycle test implementation, not a production authorization service.
