A 0.89.0 serve audit found that the permissions firewall protects only the CLI process. PermissionEngine is built exclusively in the Typer callback (cli.py:374) and carried in ctx.obj; there are zero check_cli_permission / permission_engine references anywhere under src/keboola_agent_cli/server/ (grep count 0). A persisted permissions set --mode deny policy, --deny-writes, and --deny-destructive therefore have no effect on kbagent serve, which exposes all 226 routes — including DELETE /storage/buckets, POST /token/{p}/delete, DELETE /configs/... — behind a single all-or-nothing bearer token (server/auth.py:42).
Aggravating detail: permissions.py:320 contains a load-bearing comment asserting the opposite — "The serve's own routes enforce their own permissions on top." — which is false today and misleads the http.* risk classification directly beneath it.
Proposed direction:
- Build the
PermissionEngine in create_app() from the same config dir and enforce it in a FastAPI dependency (map route → cli:read|write|destructive|admin category, mirroring the CLI risk registry), OR
- at minimum document loudly (docs/web-server.md +
permissions command help) that serve is outside the firewall, fix the false comment, and add a read-only GET /permissions/show so clients can discover the policy.
Given the guard-rails-not-sandbox philosophy (#271), option 1 with a doc caveat seems right: the firewall guards against agent mistakes, and agents are exactly what serve hosts (scheduled ai_agent tasks calling back via kbagent http).
A 0.89.0 serve audit found that the permissions firewall protects only the CLI process.
PermissionEngineis built exclusively in the Typer callback (cli.py:374) and carried inctx.obj; there are zerocheck_cli_permission/permission_enginereferences anywhere undersrc/keboola_agent_cli/server/(grep count 0). A persistedpermissions set --mode denypolicy,--deny-writes, and--deny-destructivetherefore have no effect onkbagent serve, which exposes all 226 routes — includingDELETE /storage/buckets,POST /token/{p}/delete,DELETE /configs/...— behind a single all-or-nothing bearer token (server/auth.py:42).Aggravating detail:
permissions.py:320contains a load-bearing comment asserting the opposite — "The serve's own routes enforce their own permissions on top." — which is false today and misleads thehttp.*risk classification directly beneath it.Proposed direction:
PermissionEngineincreate_app()from the same config dir and enforce it in a FastAPI dependency (map route →cli:read|write|destructive|admincategory, mirroring the CLI risk registry), ORpermissionscommand help) that serve is outside the firewall, fix the false comment, and add a read-onlyGET /permissions/showso clients can discover the policy.Given the guard-rails-not-sandbox philosophy (#271), option 1 with a doc caveat seems right: the firewall guards against agent mistakes, and agents are exactly what
servehosts (scheduledai_agenttasks calling back viakbagent http).