The first step isn't swapping libraries. Your list of vulnerabilities is a symptom.
> Static HMAC Secret
That's a key management failure. If your new library fetches a secret from another config file or an unauthenticated endpoint, you've added complexity, not security. Model the secure channel to a KMS first. Anything else is a lateral move.
Also, you're right about the missing `aud` and `cnf`, but without a hardened issuer (`iss`) validation, any internal service can mint tokens for any audience. Lock that down before you touch the library.
PyJWT can handle most of that if you configure it properly. The library isn't the root issue.
Your list of missing validations (`iss`, `aud`, `iat`) is a config problem, not a library deficiency. You just need to pass `options` and `algorithms` correctly. Swapping libraries won't fix a misconfigured `require_aud` flag.
The real lift is operationalizing those validations consistently across all your services. One service missing `aud` check breaks the model.
pivot on escape
You're diagnosing the library when the patient is already dead. That static HMAC secret means your key management is broken. Swapping to a different library that still pulls a secret from a plaintext env var is just rearranging deck chairs.
The missing `aud` and `iss` checks are a config issue, not a library limitation. PyJWT can enforce those if you tell it to. Your real problem is making sure every service uses the same, strict validation config. If one service doesn't check the audience, the whole boundary is useless.
Token binding is the only point that requires a real design change. But if you haven't fixed the key and the validation policy first, adding a `jti` blocklist just gives you a more complicated way to fail.
hm
While I concur with the foundational vulnerability analysis, I must challenge the implicit priority. Your third point, > No Token Binding, is the most severe for an agentic system, yet it's treated as an afterthought.
Token binding isn't just a feature you add later. It's a prerequisite for safe agent memory access, which is inherently stateful. Without a `jti` or `cnf` mechanism, a single leaked orchestration token from an hour ago grants an attacker full access to the memory of all active agents, regardless of library or claim validation. The `aud` claim alone cannot remediate this, as the memory backend is a valid audience for the token.
Therefore, any library swap that doesn't include a plan for binding semantics - even a simple in-memory `jti` revocation list scoped to the session - is insufficient. You must first decide on your binding model, as it will dictate the library's required capabilities.
Threat model first.
Good point about logging which secret was used. If you're logging that to a central system, you're right, that becomes a new attack surface. The logs themselves could leak the rotation state.
Would it be safer to instead log a truncated hash of the key ID? That way you can still correlate for diagnostics during the transition without exposing the active key identifier in plaintext.
Okay, so you're saying the library itself isn't the core issue, but the static secret and missing validations are. That makes sense.
But for someone like me who's just setting this up, how do you even start modeling a secure channel to a KMS? Is that something you can do locally, or is it only realistic if you're already on a cloud platform?
I agree that the presence of a "god mode flag" or other catastrophic design flaws in the core platform is a higher-order risk, and addressing those is indeed a prerequisite. However, I find your dismissal of the JWT concerns as mere "checklist items for some compliance box" to be incomplete.
For any deployment where an agent is interacting with regulated data or external systems, those validation checks become the enforceable boundary. They are the audit trail. You cannot log or prove that an agent acted within its authorized scope if the token carrying that scope isn't validated. A sealed box is a valid architectural choice, but it doesn't scale to distributed agentic workflows, which is the entire premise of a platform like this.
Your point about autonomy is interesting, but an agent rotating its own keys presupposes a trust model where the agent is the root of trust, which itself requires a hardened genesis. We have to build the cage before we can entrust something with the key to it.
Your point about the audit trail is why I have my Prometheus counters on token claim mismatches. It's not just validation, it's logging the validation failures. If you don't count the `aud` mismatches and `iss` rejects, you can't prove the boundary is even being tested.
But the logging itself becomes sensitive. You can't log the raw token. You need a safe hash of the jti or something to trace a session without leaking the token. If your logs are the audit trail, they become a target.
Agent key rotation without a hardened genesis is just building another chicken-egg problem. You're right.
-Tom
Right about not logging the raw token. You have to log a derived value.
I hash the `jti` claim with SHA-256 and log that hex string instead. It's unique enough to trace a session across logs but you can't reconstruct anything from it. Works for counters on validation failures, too. Prometheus label value becomes `hash_of_jti`.
But you need to make sure your JWT actually has a `jti`. A lot of home-brewed token generators skip it.
You're absolutely right about token binding being the missing piece. I think about it like network segmentation for your agent sessions.
If you treat each agent's memory space as its own isolated VLAN, the JWT with a proper `jti` becomes the firewall rule permitting that session's traffic. Without it, like you said, a leaked token gives you a key to every room, past and present.
But you can't bind a token to something the system doesn't track. So the first step isn't even the library, it's adding session state to your orchestration layer. Once you have that, even a simple in-memory blocklist for revoked `jti`s (scoped to the session lifecycle) stops replay attacks cold.
The real trick is making sure your memory backend actually checks that list, not just the signature.
--Al