[Hackathon] cybersec-blackhat: dpop_jwt auth plugin + security validators#9
Open
mariagorskikh wants to merge 2 commits into
Open
[Hackathon] cybersec-blackhat: dpop_jwt auth plugin + security validators#9mariagorskikh wants to merge 2 commits into
mariagorskikh wants to merge 2 commits into
Conversation
…e PoP
The default jwt plugin is a deliberately toy HMAC blob with no audience,
no jti, no proof-of-possession, and a custom payload|sig format. In a
multi-agent swarm that ships bearer tokens over the in-memory transport,
any observer can replay any token against any service that shares the
secret until expiry.
dpop_jwt is an opinionated, dependency-free hardened replacement:
- RFC-7519-shaped (header.payload.sig, base64url).
- HS256 only; alg=none and alg confusion rejected before the MAC check.
- aud, iss, iat, nbf, exp, jti enforced; clock-skew leeway tunable.
- jti replay cache scoped per verifier, bounded by token expiry.
- Optional cnf.jkt binding to an agent's identity public key.
Verification then requires a fresh DPoP proof (audience + jti +
iat, signed with the bound key).
- Revocation by jti, with raw-token fallback for Auth-protocol compat.
Registered as ('auth', 'dpop_jwt') in PluginRegistry so scenarios can
opt in via auth: dpop_jwt.
33 tests cover adversarial vignettes: alg=none, tampered payload, cross-
audience replay, jti replay, expired/future tokens, wrong issuer, DPoP
key mismatch, jti rebinding, audience swap on the proof, expired/future
proofs, tampered proof signatures, malformed inputs, determinism under
seeded RNG, and Auth-protocol conformance via runtime isinstance check.
A generic-over-trace-events validator module that flags the classic
adversarial patterns when a scenario emits auth.* events into its
trace stream. The validators are conservative: when a field is absent
they return PASS, so existing scenarios (which do not emit auth events
yet) are not affected.
Checks:
- no_token_replay: same jti accepted twice by the same verifier.
- audience_binding: presented_aud != token's aud.
- subject_matches_sender: token sub != claimed sender on the hop.
- no_expired_acceptance: verify_success at t with exp < t.
- dpop_binding_when_required: configurable per-audience policy that
flags unbound bearer tokens for high-security audiences.
Plus an aggregate validate_security_events() and a JSONL trace loader.
16 tests cover both positive and negative paths and confirm non-auth
traces produce zero false positives.
There was a problem hiding this comment.
Sorry @mariagorskikh, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
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.
Persona
cybersec-blackhat — senior security researcher. I look at every API and ask "how do I break this?" first, then "how do I harden it?" Auth, identity, trust layers are home turf.
Piece I picked
Layer 5 — Auth. Plus a new security validator module that can be applied to traces from any scenario.
The default
jwtplugin is honestly described in the README as "HMAC-SHA256 token; not RFC JWT." Reading the code adversarially, it's worse than that label suggests for a multi-agent simulator that explicitly invites Byzantine peers:aud— a token meant for the registry can be replayed against payments.jti— perfect replay attacks; revocation requires storing entire token strings.payload|sigformat — not actually a JWT.iss— multi-tenant confusion.nbf, no clock-skew tolerance.In a swarm that ships these tokens over
in_memorytransport, every other agent in the same process can scrape them out of the event queue.Core idea
Two focused additions, no breakage:
1.
auth: dpop_jwt—DpopAuthpluginA dependency-free, stdlib-only hardened auth implementation:
base64url(header).base64url(payload).base64url(sig).HS256.alg: none,alg: RS256confusion, and unknown algs are rejected before the MAC check — kills the entirealgfamily of JWT bugs.aud).verify_for_audience(token, audience=...)refuses tokens whoseauddoes not match. A token issued forregistrycannot be replayed againstpayments.jti+ per-verifier replay cache, bounded by token expiry (lazy GC + capacity cap, so it can't be flooded).cnf.jkt. Verification then requires a freshDpopProof(audience + this-token's-jti + iat, signed with the bound key). Stealing the token alone is not enough; the attacker also needs the key.iss+nbf+ configurable clock skew.jti(compact and bounded), with raw-token fallback so the bareAuthprotocol'srevoke(token)still works.Registered as
("auth", "dpop_jwt")inPluginRegistry. Scenarios opt in withauth: dpop_jwt.2.
nest_core.security_validators— trace-level security checksA new validator module generic over auth-related trace events. Any plugin or scenario that emits
auth.*events (shape documented in the module docstring) becomes inspectable for:no_token_replay— samejtiaccepted twice by the same verifier.audience_binding—presented_aud != token.aud.subject_matches_sender—sub != claimed sender on the hop.no_expired_acceptance—verify_success at t with exp < t.dpop_binding_when_required— configurable per-audience policy flagging unbound bearer tokens for high-security audiences.Validators degrade gracefully on missing fields, so existing traces (which don't emit auth events yet) get zero false positives.
How to test
The plugin shows up via the registry:
The test files are written as adversarial vignettes — each test names an attack that succeeds against the baseline
JwtAuthand shows thatDpopAuthblocks it. Two tests (test_baseline_jwt_has_no_audience_concept,test_baseline_jwt_accepts_replays) keep the original plugin honest by demonstrating exactly what it doesn't defend against.Key assumptions
cryptography) or extending the existingIdentityplugin to expose generic sign/verify; I kept this PR stdlib-only.DpopAuthinstance. Operators who want cross-verifier replay protection wire their verifiers to a shared instance; operators who want strict per-audience isolation use one per audience. Both shapes are intentional.auth.*trace events is documented insecurity_validators.pyand meant to be a public contract for plugin authors who want to feed the validators.Future work
Identityplugin (sign DPoP proofs with the agent'sdid:keyrather than HMAC).AuthLoggermixin that emitsauth.*events into the trace so the new validators can run end-to-end on the built-in scenarios.marketplace) todpop_jwtand add a Byzantine "token replayer" agent — concrete demo of the validator catching real misuse.https://claude.ai/code/session_01C5j2D4MgCkPgsjSCqBVpWW
Generated by Claude Code