Repository navigation
Restrict hosted OAuth to one Reddit account - #2
Merged
Merged
Conversation
jwaldrip
marked this pull request as ready for review
August 20, 2026 07:44
jwaldrip
added a commit
that referenced
this pull request
Sep 2, 2026
* Reddit MCP as a hosted HTTP server behind a Reddit sign-in The han reddit plugin ran a public unauthenticated server, so it could read all of Reddit except the part that belongs to you. The first fix put an authenticated server inside the han monorepo, over stdio from npx, which meant a service living in a plugin marketplace and every user hand-minting a Reddit refresh token onto every machine they use. This is that server as its own repo, speaking HTTP, with an OAuth 2.1 authorization server in front that bridges to Reddit own grant. An agent registers itself, the human is sent to Reddit consent screen, and the refresh token that comes back is sealed at rest with AES-256-GCM and never leaves the service. No Reddit password ever reaches us. The Reddit client, its retry and pagination logic and its 56 tests are carried over unchanged. New here is the transport, the authorization server, the container and the deploy. Authorization codes are sealed rather than signed, and single use, enforced by a Firestore create that fails when the document exists. Our tokens are stored under a hash of themselves, so a database read yields fingerprints rather than working credentials. Terraform for the Cloud Run service lives in jwaldrip/cld beside the other services in that project. This repo owns the code and which revision serves. * Say what bootstrap actually creates The bring-up order claimed bootstrap.sh creates the deployer this repo authenticates as. It does not, and its own comments say so: the pool and provider are bootstrap substrate, while the deployer account and every role on it are terraform, so they sit beside the rest of what that account may do. An operator following the old wording would have looked for an account that does not exist yet. * Refuse scopes this server cannot deliver The authorize handler wrote whatever scopes the client asked for into the transaction. The SDK advertises scopesSupported in metadata but does not enforce it, so a client could request anything and see it echoed back in the token response. That is a lie rather than a privilege, since naming a scope here does not obtain it from Reddit, and it becomes a real hole the moment a route gates on scope. The refresh grant only narrows against the original set, so an inflated original would have propagated for the life of the grant. Unsupported scopes are now refused at the point they are asked for, returned to the client as invalid_scope per OAuth 2.1 rather than rendered, and never forwarded to Reddit. * Restrict hosted OAuth to one Reddit account (#2) * Prove hosted OAuth needs an account boundary * Restrict hosted OAuth to one Reddit account * Document the hosted account boundary
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Jason is applying for Reddit Data API access for personal, single-account use. The hosted branch leaves dynamic client registration open and, before this change, accepted whichever Reddit identity completed the callback. Open registration without an owner check would contradict that application.
What changed
REDDIT_ALLOWED_USERNAMEat the hosted HTTP entry point and carry it into the Reddit grant.u/or/u/on the configured value.access_deniedfor a different account. An unset or empty value also returnsaccess_deniedwith a message explaining that this is a single-account server with no account configured.Guard proof
The regression tests were committed before the implementation. Against the unguarded callback, the different-account, unset, and empty cases all failed because the callback returned an authorization code instead of
access_denied.I repeated that check after implementation by temporarily removing
grant.assertAllowedUsername(username)and runningnpm test -- test/oauth/reddit-grant.test.ts. All three refusal tests failed with:Restoring the guard returned the file to 7 passing tests.
Verification
npm run lintnpm run typechecknpm test