Skip to content

Commit 1d0b248

Browse files
Azdarothclaude
andcommitted
Trim the round's comments to what only the code can say
fullstack-lint. CI linters were already green with their exact flags (rubocop 64/0; brakeman --force --no-progress --quiet --no-pager 0/0; rspec 598/0). This is the judgment half. The concern had drifted to 38% comments — the same file the last lint round cut to 29%, and the same cause: duplication rather than documentation. - `mcp_oauth_loopback_redirect_uri?` carried a 17-line restatement of `oauth_allow_loopback_redirects`' rationale. That argument belongs where the setting lives; here it is now a pointer plus the parsing detail, which is the only part this file alone can say. - `mcp_oauth_encryptor` had 22 comment lines over 6 of code, re-explaining why the signing secret exists — which is `Configuration#oauth_signing_secret`'s job. Kept: why HMAC, why no KDF, and why NullSerializer specifically, since that one reads like a redundant option and is the reason Marshal never sees the payload. - Dropped a comment that had already gone stale within this branch: it justified building the callback URL by string partly because "a host may legitimately allowlist an opaque URI" — untrue since the same round made that an ArgumentError at config time. Exactly the failure mode this round exists to fix, so it does not get to ship. Down to 34%, over four more methods than the file had. RSpec pass clean: no anonymous subjects, no `create(` (the suite is Rails-free), no ticket references or commented-out code in the diff. View rules N/A — the template carries no JS, and simple_form is deliberately not a dependency of a dependency-light gem. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f907f81 commit 1d0b248

1 file changed

Lines changed: 17 additions & 42 deletions

File tree

lib/mcp_toolkit/oauth/controller_methods.rb

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -174,25 +174,14 @@ def mcp_oauth_redirect_uri_allowed?
174174
mcp_oauth_loopback_redirect_uri?(redirect_uri)
175175
end
176176

177-
# RFC 8252 §7.3 loopback: the ONLY target accepted without being named, because
178-
# it is the only one that CANNOT be named — the client listens on an ephemeral
179-
# port chosen at runtime, so no allowlist could enumerate it. That, and not
180-
# "native clients are trusted", is the whole justification: an allowlist entry
181-
# is impossible here and merely inconvenient everywhere else.
177+
# RFC 8252 §7.3 loopback — the one target accepted unnamed, and only http(s) to
178+
# a loopback host: NOT private-use schemes, however local they look. See
179+
# Configuration#oauth_allow_loopback_redirects for why that line is drawn there.
182180
#
183-
# A private-use scheme (`cursor://…`, §7.1) is deliberately NOT accepted here,
184-
# even though it also keeps the code on the device. Its redirect URI is a fixed
185-
# string, so it can simply go in `oauth_allowed_redirect_uris` — there is no
186-
# forcing reason to accept it unnamed, and accepting whole SCHEMES generically
187-
# cannot be done safely: the only way to separate a private-use scheme from a
188-
# registered network one (`ssh:`, `ldap:`, `gopher:` — each naming a REMOTE
189-
# host) is to enumerate the IANA registry, and a denylist of the ones you
190-
# thought of is exactly the shape that fails open.
191-
#
192-
# Checked against the PARSED URI, never the string: `host` is what a browser
193-
# resolves, so `http://127.0.0.1@evil.example/` (userinfo; host evil.example)
194-
# and `http://127.0.0.1.evil.example/` are both correctly seen as remote. A
195-
# fragment is refused because OAuth forbids one on a redirect_uri.
181+
# Judged on the PARSED URI, never the string, because `host` is what a browser
182+
# resolves: `http://127.0.0.1@evil.example/` (userinfo — host is evil.example)
183+
# and `http://127.0.0.1.evil.example/` are both correctly remote. A fragment is
184+
# refused because OAuth forbids one on a redirect_uri.
196185
def mcp_oauth_loopback_redirect_uri?(redirect_uri)
197186
return false unless mcp_oauth_config.oauth_allow_loopback_redirects
198187

@@ -269,28 +258,16 @@ def mcp_oauth_code_key(code)
269258
"#{CODE_CACHE_PREFIX}#{Digest::SHA256.hexdigest(code)}"
270259
end
271260

272-
# Keyed on a SERVER-HELD secret as well as the code, and that is the whole
273-
# point: deriving from the code alone made the code the entire secret, and the
274-
# code is not one. Rails logs it twice per flow at INFO — `Redirected to
275-
# ...?code=...` (only `config.filter_redirect` touches that line) and the token
276-
# endpoint's `Parameters:` (no stock `filter_parameters` entry matches `code`)
277-
# — so an artifact that is more widely read, longer retained and more
278-
# replicated than a 60-second cache entry was carrying the key to it. Mixing in
279-
# `oauth_signing_secret` means the cache, the logs and the code together still
280-
# open nothing without a secret that lives in ENV and is never logged.
281-
#
282-
# HMAC rather than a bare digest because two independent inputs are being
283-
# combined and HMAC is what does that safely. No password-stretching: both
284-
# inputs are already high-entropy (a 256-bit `SecureRandom` code, a real
285-
# `secret_key_base`), so a PBKDF2 run per request would buy nothing.
261+
# HMAC because two independent inputs are being combined (why the secret is one
262+
# of them: Configuration#oauth_signing_secret). No password-stretching — both
263+
# are already high-entropy, so a PBKDF2 run per request would buy nothing.
286264
#
287-
# Cipher AND serializer are pinned rather than inherited the gem supports
265+
# Cipher and serializer are pinned, not inherited: the gem supports
288266
# ActiveSupport >= 6.1, where both defaults are Rails-configuration-dependent.
289-
# The serializer especially: every default in that range (`:marshal`, and
267+
# The serializer especially every default in that range (`:marshal`, and
290268
# 7.1+'s `:json_allow_marshal`) reaches `Marshal.load`, so a host with cache
291-
# write access forging one blob would get remote code execution. The payload is
292-
# already a JSON String, so NullSerializer is exactly right and JSON.parse ends
293-
# up the only parser that ever sees it.
269+
# write access forging one blob would get code execution. The payload is already
270+
# a JSON String, so NullSerializer leaves JSON.parse the only parser that sees it.
294271
def mcp_oauth_encryptor(code)
295272
key = OpenSSL::HMAC.digest("SHA256", mcp_oauth_signing_secret, "#{CODE_CACHE_PREFIX}key:#{code}")
296273
ActiveSupport::MessageEncryptor.new(
@@ -355,11 +332,9 @@ def mcp_oauth_endpoint_url(action)
355332
# Dropping any inbound `code`/`state` keeps the response OAuth-shaped whatever
356333
# was passed in.
357334
#
358-
# Built by string, not by `URI#query=`: the value here has already been checked
359-
# by the redirect policy, and re-parsing it to reconstruct it only invents ways
360-
# for the emitted URL to differ from the one that was approved — `URI#query=`
361-
# also raises outright on an opaque URI (`com.example.app:cb`), which a host may
362-
# legitimately allowlist.
335+
# Built by string, not by `URI#query=`: this value has already been checked by
336+
# the redirect policy, so re-parsing it to reconstruct it only invents ways for
337+
# the emitted URL to differ from the one that was approved.
363338
def mcp_oauth_callback_url(code)
364339
redirect_uri = params[:redirect_uri].to_s
365340
base, _, existing = redirect_uri.partition("?")

0 commit comments

Comments
 (0)