Skip to content

docs(readme): surface MCP-grade security highlights#191

Merged
appleboy merged 3 commits into
mainfrom
readme-mcp-security-highlights
May 19, 2026
Merged

docs(readme): surface MCP-grade security highlights#191
appleboy merged 3 commits into
mainfrom
readme-mcp-security-highlights

Conversation

@appleboy
Copy link
Copy Markdown
Member

Summary

Surfaces three security properties of AuthGate that were previously only documented in docs/MCP.md and other deep-dive guides, making them visible to README readers — especially MCP / multi-resource integrators evaluating AuthGate:

  1. Refresh-as-access token confusion protection — every JWT carries a type claim (access vs refresh); a refresh token cannot be replayed as an access token by a resource server that only checks signature / iss / exp / aud. Operational constraint surfaced: JWT_AUDIENCE must be unset or AS-only, never a resource server's aud.
  2. Device-code resource confirmation — resource-bound device codes always route through an explicit confirmation screen (client + requested resources) before authorization, regardless of verification_uri_complete or manual user-code entry. Prevents silent resource binding by an attacker-controlled MCP server.
  3. Standards-compliance baseline — RFC 8628 / 6749 / 7636 (PKCE S256) / 8707 / 8414 / 7591 / 7009 / 7662 assembled in one bulleted list.

No code change — README.md only (+26 / -0).

AI Authorship

  • AI was used. Details:
    • Tool / model: Claude Opus 4.7 (1M context) via Claude Code, plan-mode workflow with /simplify review pass
    • AI-authored files: README.md (the only file in the diff)
    • Human line-by-line reviewed: README.md — reviewed via plan-mode approval gate (plan saved at ~/.claude/plans/refresh-as-access-token-distributed-lamport.md) and again through the /simplify review pass (which flagged and removed an overclaim, a duplicate link, and converted an 87-word RFC sentence to a bulleted list)

Change classification

  • Leaf node — docs-only change. No executable code, no behavior change. Failure mode is "marketing/discoverability is wrong"; no system-wide impact.

Verification

  • Anchor links manually verified by grepping headings in docs/MCP.md, docs/CONFIGURATION.md, and confirming docs/JWT_VERIFICATION.md exists. Two anchors I originally planned (#token-verification, #device-code-resource-confirmation) did not exist in docs/MCP.md; replaced with the real anchors #audience-binding-via-resource-indicators-rfc-8707 and #configuration-checklist.
  • All RFC reference IDs used in the new content (rfc6749, rfc7009, rfc7591, rfc7636, rfc7662, rfc8414, rfc8628, rfc8707) confirmed defined in the README footer.
  • TOC entry #mcp--multi-resource-hardening matches the GitHub-generated anchor for the new ### MCP & multi-resource hardening heading (GitHub strips & and collapses spaces; the double-dash is intentional).

Reviewer manual check: render this PR's README diff in GitHub preview, confirm the callout blockquote renders as a single yellow block (not three plain bullets), and click each new internal doc link to confirm it resolves to the intended section.

Risk & rollback

  • Risk: None functional. Doc-only. Worst case: a link breaks or wording is unclear to a reader.
  • Rollback: git revert <merge-commit> — single content commit.

Reviewer guide

  • Read carefully: the three bullets in the top callout (README.md:141-145) and the new ### MCP & multi-resource hardening subsection (README.md:579-593). These are the new claims about AuthGate's security posture that should be sanity-checked against docs/MCP.md:101-148, 212-236 and the implementation in internal/token/local.go and internal/handlers/device.go:266-280.
  • Spot-check OK: TOC entry, the three new bullets under "What AuthGate Protects".

🤖 Generated with Claude Code

- Add an MCP / multi-resource security callout at the end of "Why AuthGate?" highlighting the JWT type-claim refresh-as-access guard, mandatory device-code resource confirmation, and RFC compliance baseline
- Extend the "What AuthGate Protects" checklist with refresh-token confusion, device-flow phishing, and cross-resource replay coverage
- Add an "MCP & multi-resource hardening" subsection under Security with the JWT_AUDIENCE operational constraint and the full RFC stack (8628 / 6749 / 7636 / 8707 / 8414 / 7591 / 7009 / 7662)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 19, 2026 05:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This docs-only PR surfaces MCP and multi-resource security guidance in the README so evaluators can find key hardening properties without digging into deep-dive docs.

Changes:

  • Adds a README callout for MCP / multi-resource security highlights.
  • Expands the security protection list with refresh-token confusion, device-flow confirmation, and cross-resource replay mitigations.
  • Adds a new “MCP & multi-resource hardening” subsection with standards references and links.
Comments suppressed due to low confidence (2)

README.md:575

  • This should not say “every JWT” because AuthGate’s OIDC ID tokens are JWTs without a type claim. Please scope this to access/refresh token JWTs to keep the protection list accurate.
- ✅ Refresh-token-as-access-token confusion (mandatory `type` claim on every JWT)

README.md:581

  • This paragraph has two inaccuracies: AuthGate-issued OIDC ID tokens are JWTs but intentionally have no type claim, and /oauth/tokeninfo rejects refresh tokens rather than returning a response with aud stripped. Please narrow the type statement to access/refresh token JWTs and describe tokeninfo as rejecting non-access tokens before emitting an audience.
**Refresh tokens cannot masquerade as access tokens.** Refresh JWTs are signed with the same key as access JWTs, so a resource server that only verifies signature/`iss`/`exp`/`aud` would silently accept a refresh token as a valid access token whenever `JWT_AUDIENCE` happens to match its resource identifier. AuthGate hard-codes the distinction: every issued JWT carries a `type` claim (`access` or `refresh`), `ValidateToken` rejects any token whose `type` is not `"access"`, and `/oauth/tokeninfo` strips `aud` from refresh-token introspection so it cannot be mistaken for an access-token response. **Operational constraint: `JWT_AUDIENCE` MUST be either unset or set to an AS-only identifier — never a resource server's `aud`.** See [docs/MCP.md → Configuration checklist](docs/MCP.md#configuration-checklist) and [docs/CONFIGURATION.md](docs/CONFIGURATION.md#environment-variables).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md Outdated
Comment thread README.md Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

- Scope the `type`-claim guarantee to OAuth bearer tokens (access / refresh); OIDC ID tokens are JWTs without a `type` claim and follow a separate code path in `internal/token/idtoken.go`
- Correct the `/oauth/tokeninfo` description — it returns 401 for refresh tokens via `ValidateToken` rather than stripping `aud`; note that `/oauth/introspect` is the RFC 7662 endpoint that omits `aud` on refresh tokens
- Qualify the Dynamic Client Registration mention with the `ENABLE_DYNAMIC_CLIENT_REGISTRATION=true` opt-in (default is false in `config.go:586`)

Addresses Copilot review on #191.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

README.md:576

  • This list item is broader than the behavior documented below: AuthGate forces confirmation for resource-bound device codes, which mitigates silent resource binding, but it does not protect against all device-flow phishing. Please scope this bullet to the specific mitigation to avoid promising a stronger security guarantee than the implementation provides.
- ✅ Device-flow phishing (forced confirmation page for resource-bound device codes)

Comment thread README.md Outdated
Comment thread README.md Outdated
- Clarify the AS / RS division of responsibility for the `type` claim: AuthGate enforces it at its own endpoints, but resource servers validating JWTs locally must also check `type == "access"` (otherwise a refresh token can be accepted if `JWT_AUDIENCE` collides with the RS audience)
- Narrow the device-code callout from "phishing surface eliminated" to "silent resource binding blocked" — the confirmation page only fires for resource-bound device codes (`handlers/device.go:266-280`), so the broader phishing claim overstated the mitigation
- Apply the same scope correction to the "What AuthGate Protects" bullet

Addresses Copilot review on #191.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@appleboy appleboy merged commit e3cc642 into main May 19, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants