PT-2026-53125 · Pypi · Praisonai
Publicado
2026-06-18
·
Atualizado
2026-06-18
CVSS v3.1
8.2
Alta
| Vetor | AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N |
PraisonAI recipe serve Typer command bypasses the non-localhost authentication guard
Summary
PraisonAI's installed console entrypoint is Typer-first. In current releases,
the
recipe command is registered in the Typer app and
praisonai recipe serve dispatches to the deprecated Typer command in
src/praisonai/praisonai/cli/commands/recipe.py.That Typer command can start the Recipe HTTP server on a non-localhost
interface with no authentication:
text
praisonai recipe serve --host 0.0.0.0 --adminIt prints a deprecation warning, then launches the server with:
json
{
"host": "0.0.0.0",
"config": {
"cors origins": "*",
"enable admin": true
}
}Because
config.auth is absent, create app() does not attach the API-key or
JWT middleware. Unauthenticated requests can then reach the recipe API and, when
enabled, /admin/reload.This is an incomplete hardening / sibling-callsite issue. The legacy feature
handler in
src/praisonai/praisonai/cli/features/recipe.py rejects the same
non-localhost/no-auth combination, and current create auth middleware() now
fails closed if API-key/JWT auth is selected without a secret. The installed
Typer command bypasses both expectations by never requiring or setting auth.Affected product
- Repository:
MervinPraison/PraisonAI - Package:
praisonai - Component:
src/praisonai/praisonai/ main .pysrc/praisonai/praisonai/cli/app.pysrc/praisonai/praisonai/cli/commands/recipe.pysrc/praisonai/praisonai/cli/features/recipe.pysrc/praisonai/praisonai/recipe/serve.py
Confirmed affected:
text
v4.6.58 1ad58ca02975ff1398efeda694ea2ab78f20cf3e
v4.6.57 e90d92231853161ad931f3498da57651a9f8b528
v4.6.56 d3c4a2afadfbf3a3e172e460e607ba4efad263a6
v4.6.34 e5928449f73f66cc8af1de61621aa974ab255133
v4.6.33 dfbb8d78ec7e8dc7118bc722ab1b2524bc98ddab
v4.6.10 4b1b17b963cbd0625e41394a30168c95b26429b2
v4.5.128 b4e3a8a84ade44ac3dd9102b792cdb4311a95937
v4.5.112 bfe3d94bad6db92fc2927c2e3c081ae8303e209eSuggested affected range:
praisonai >= 4.5.112, <= 4.6.58.The lower bound is conservative and based on sampled tags. Maintainers should
confirm the exact introduction point before publishing a final range.
Root cause
The installed entrypoint routes registered Typer commands before falling back
to the legacy dispatcher:
python
if first cmd in get typer commands():
run typer(argv)
else:
run legacy(argv)cli/app.py registers commands.recipe as the recipe Typer command:python
from .commands.recipe import app as recipe app
...
app.add typer(recipe app, name="recipe", help="Recipe management")The deprecated Typer
recipe serve implementation accepts a remote host,
defaults CORS to *, and only enables authentication when --api-key is
explicitly provided:python
host: str = typer.Option("127.0.0.1", "--host", "-h", ...)
api key: str = typer.Option(None, "--api-key", ...)
cors: str = typer.Option("*", "--cors", ...)
admin: bool = typer.Option(False, "--admin", ...)
...
serve config = {}
...
if api key:
serve config["api key"] = api key
serve config["auth"] = "api-key"
if cors:
serve config["cors origins"] = cors
if admin:
serve config["enable admin"] = True
...
serve(host=host, port=port, reload=reload, config=serve config, workers=workers)There is no equivalent to the hardened non-localhost guard in the legacy
feature handler:
python
if host != "127.0.0.1" and host != "localhost" and auth == "none":
self. print error("Auth required for non-localhost binding. Use --auth api-key or --auth jwt")
return self.EXIT POLICY DENIEDThe Recipe server only installs auth middleware when
config["auth"] is set:python
auth type = config.get("auth")
if auth type and auth type != "none":
auth middleware = create auth middleware(...)
if auth middleware:
middleware.append(Middleware(auth middleware))On current
v4.6.58, the selected-auth paths fail closed correctly:auth=api-keywith no key returns503.auth=api-keywith a key but no request header returns401.
The vulnerable Typer path does not select auth at all.
Local-only PoV
Run from the harness checkout:
bash
uv run
--with starlette --with httpx --with typer --with rich --with pyyaml
--with sse-starlette --with click --with python-dotenv
python submission-bundle/praisonai-prai-cand-016-recipe-serve-typer-auth-bypass/poc/pov prai cand 016 recipe serve typer auth bypass.py
--repo artifacts/repos/praisonai-v4.6.58
--label v4.6.58The PoV does not bind a socket. It monkey-patches the recipe server launcher,
invokes the real
praisonai. main .main() entrypoint with
recipe serve --host 0.0.0.0 --admin, captures the launch config, and then
uses Starlette's in-process test client to exercise the resulting app.Observed
v4.6.58 result:json
{
"candidate": "PRAI-CAND-016",
"entrypoint exit code": 0,
"typer recipe command registered": true,
"captured launch": {
"host": "0.0.0.0",
"port": 8765,
"config": {
"cors origins": "*",
"enable admin": true
}
},
"bypass": {
"admin reload": {
"path": "/admin/reload",
"status": 200
},
"openapi": {
"path": "/openapi.json",
"status": 200
}
},
"controls": {
"auth api key no secret": {
"admin reload": {
"status": 503
}
},
"auth api key no header": {
"admin reload": {
"status": 401
}
}
},
"feature handler nonlocalhost noauth exit": 4,
"auth fail closed current control": true,
"ok": true
}Stored evidence:
evidence/current-v4.6.58.jsonevidence/version-sweep.tsv
Why this is not intended behavior
This is not only a disagreement about whether operators should configure auth.
PraisonAI's current security documentation says recent hardening changed API
servers so anonymous requests return
401 and servers bind to 127.0.0.1 by
default. Recipe server docs say auth: api-key should be used for production,
admin endpoints require auth, and public servers should not run without
authentication.The implementation also shows the intended boundary:
create auth middleware()now returns503if API-key/JWT auth is selected without a secret.RecipeHandler.cmd serve()refuses non-localhost binding whenauthisnone.- The vulnerable Typer command is marked deprecated and tells users to use the
newer command, but the installed entrypoint still routes
praisonai recipeto that Typer command before the legacy handler can enforce the guard.
The official local HTTP sidecar docs describe the sidecar as communicating over
localhost and "no external network required", but the Docker example still uses:
text
CMD ["praisonai", "recipe", "serve", "--host", "0.0.0.0", "--port", "8765"]That command exposes the Typer path above and does not enable auth, even if
PRAISONAI API KEY is present in the environment, because this path only sets
auth when --api-key is passed or a config file sets auth.Impact
If an operator follows the vulnerable command path on a reachable interface,
any network caller that can reach the Recipe HTTP server can access recipe
runner endpoints without credentials.
Affected endpoints include:
GET /v1/recipesPOST /v1/recipes/runPOST /v1/recipes/streamPOST /v1/recipes/validate- optional
POST /admin/reloadwhen admin endpoints are enabled
The exact impact depends on configured recipes and deployment context. At a
minimum, an attacker can enumerate recipes and trigger recipe validation or
execution flows intended for local or authenticated callers. In deployments
with powerful recipes, tool-enabled recipes, or admin endpoints, this can cause
unauthorized workflow execution, model/API spend, state changes, or recipe
registry reload operations.
This report does not claim arbitrary code execution by default.
Suggested fix
Prefer one canonical Recipe server CLI path and enforce the same preflight for
every wrapper.
Recommended changes:
- Remove or hard-disable the deprecated Typer
praisonai recipe servecommand, or make it delegate to the hardenedRecipeHandler.cmd serve()code path. - Add the same non-localhost/no-auth guard to
cli/commands/recipe.py. - Treat
PRAISONAI API KEYas a secret only whenauth=api-keyis selected; do not rely on the env var's presence alone unless the command also enables auth explicitly. - Fix the deprecated command's help examples so remote binding always includes auth.
- Consider changing
--corsdefault from*to no CORS or localhost origins. - Add regression tests that invoke the installed
praisonai. main .main()entrypoint, not only the legacy feature handler:
praisonai recipe serve --host 0.0.0.0fails before launch unless auth is selected and configured;praisonai recipe serve --host 0.0.0.0 --admincannot expose/admin/reloadwithout auth;- selected but misconfigured auth still returns
503; - configured auth with no header returns
401.
Correção
Improper Authentication
Missing Authentication
Missing Authorization
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾
Identificadores relacionados
Produtos afetados
Praisonai