Repository navigation
Sprint review: JWTPlugin gets the middleware's checks; object dispatch leaves service.py - #34
Merged
Merged
Conversation
…t misuse; share the WSGI test helpers Characterisation tests on the unchanged code, ahead of hardening JWTPlugin and splitting object dispatch out of service.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…http middleware got The sprint hardened mk_jwt_middleware, but mk_app only builds Bottle apps, so JWTPlugin is what guards a py2http service. It still caught only DecodeError (an expired token, or one with a disallowed alg, escaped as a 500), echoed the token back in the 401 body, took any header's 8th character onward as the token, accepted an empty secret, and passed **kwargs as positional args on CORS preflight. Both now share py2http.auth: jwt_algorithms (validated at construction), bearer_token, decode_jwt (client-safe reasons, never the token) and key_matches (constant time). ApiKeyAuthPlugin and mk_superadmin_middleware refuse an empty key (which let header-less requests through) and compare in constant time; the superadmin middleware raises its 401 instead of returning it. The Bottle plugins keep the wrapped handler's attributes (functools.wraps), so JWTPlugin(ignore_methods=...) no longer fails on /openapi or when stacked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d sizeof service.py had grown to 1266 lines, half of it the sprint's object-dispatch machinery (attribute policies, owned and LRU stores, class and instance dispatchers) and a deep-sizeof walker. They now live in py2http.obj_dispatch and py2http.sizeof, and every name stays importable from py2http.service. Small cleanups on the way: LRUObjStore.peek replaces OwnedObjStore reaching into another object's _data, quota is validated directly rather than by building a throwaway store, and the size check drops a redundant ternary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…seam (#25) From the adversarial review of this branch: - a non-UTF-8 Authorization header raised (Bottle while decoding it, aiohttp in key_matches / jwt.decode) and became a 500; it is now refused; - the mixed-algorithm-family check only applies when signatures are verified; - a server-side key problem (InvalidKeyError...) is logged, not just sent to the client as an opaque 401. decode_kwargs= on JWTPlugin and mk_jwt_middleware passes audience, issuer, leeway... to jwt.decode (without it a token with an aud claim was always refused); it may not override key, algorithms or options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Sprint review (2026-10-11 → 13) of #23, #24, #27, #29, #31, #33: adversarial review, design review, coverage first.
What this changes
Auth (closes #25). The sprint hardened
mk_jwt_middleware(aiohttp), butmk_apponly builds Bottle apps, soJWTPluginis what actually guards a py2http service, and it still had every flaw #24 fixed:alg, or one of the wrong key family escaped as a 500 (onlyDecodeErrorwas caught);Authorizationminus its first 7 characters was taken as the token, whatever the scheme;handler(*args, *kwargs);ignore_methodscrashed on/openapi(nomethod_name).Both now share a framework-free
py2http.auth(jwt_algorithms,bearer_token,decode_jwt,key_matches,jwt_decode_kwargs).ApiKeyAuthPluginandmk_superadmin_middlewarerefuse an empty key (it let header-less requests through) and compare in constant time; the superadmin middleware raises its 401 rather than returning it. Newdecode_kwargs=onJWTPluginandmk_jwt_middlewarepassesaudience/issuer/leewaytojwt.decode(the seam #25 asked for).Design.
service.pywas 1266 lines, half of it the sprint's object-dispatch machinery. It now lives inpy2http.obj_dispatchandpy2http.sizeof(moved verbatim, AST-checked by the reviewer), and every name stays importable frompy2http.service. Small cleanups:LRUObjStore.peek, direct quota validation, a redundant ternary. The tests' three copies of a WSGI_posthelper are onecall_wsgi.Caller-visible changes (all refusals of unsafe setups)
JWTPlugin(secret="")withverify=True,JWTPlugin(algorithms="HS256")(a bare string; pass a list), and mixed HMAC + public-key algorithms withverify=Truenow raise at construction, asmk_jwt_middlewarealready did after JWT middleware: verify signatures, one algorithm family by default, 401 for every bad token #24.ApiKeyAuthPlugin("")/mk_superadmin_middleware("")raise.py2http.bottle_pluginsno longer importsjwt/abortat module level;import py2httpno longer loadsjwtorcryptography.No dependents in the local package fleet use
JWTPlugin,ApiKeyAuthPlugin,mk_superadmin_middlewareor the moved private names.Review
An independent adversarial review (Opus) tried 28 hostile tokens, Bearer-prefix variants, request-token leakage between Bottle requests, import cycles, and the verbatim move. Nothing got through. Its findings (a 500 on non-UTF-8
Authorizationheaders, the mixed-family check firing without verification, no server-side log for key problems) are fixed in the last commit, with tests.Tests
Characterisation tests were committed first and pass on the unchanged code. Full suite: 211 passed (was 149). Coverage of touched modules:
bottle_plugins22% → 94%,middleware87% → 100%,default_configs68% → 82%, newauth100%,obj_dispatch97%,sizeof95%; total 73% → 78%.🤖 Generated with Claude Code