Skip to content

PraisonAI agent/platform control boundaries and Formie submission overwrite batch

Source: GitHub Security Advisories REST fallback, updated 2026-05-29: GHSA-vg22-4gmj-prxw / CVE-2026-47391, GHSA-4mr5-g6f9-cfrh / CVE-2026-47392, GHSA-8444-4fhq-fxpq / CVE-2026-47393, GHSA-9cr9-25q5-8prj / CVE-2026-47394, GHSA-5cxw-77wg-jrf3 / CVE-2026-47395, GHSA-86qc-r5v2-v6x6 / CVE-2026-47396, GHSA-hvhp-v2gc-268q / CVE-2026-47397, GHSA-78r8-wwqv-r299 / CVE-2026-47398, GHSA-5c6w-wwfq-7qqm / CVE-2026-47390, GHSA-3qg8-5g3r-79v5 / CVE-2026-47410, GHSA-h8q5-cp56-rr65 / CVE-2026-47407, GHSA-6h6v-6m7w-7vxx / CVE-2026-47399, GHSA-gv23-xrm3-8c62 / CVE-2026-48169, GHSA-h37g-4h4p-9x97 / CVE-2026-47405, GHSA-w388-2392-px73 / CVE-2026-47409, GHSA-c2m8-4gcg-v22g / CVE-2026-47416, GHSA-4x6r-9v57-3gqw / CVE-2026-47406, GHSA-5jx9-w35f-vp65 / CVE-2026-47414, and GHSA-pgxq-p76c-x9cg / CVE-2026-47266.

This batch is durable because it captures reusable offensive validation patterns for agent stacks: public unauthenticated agent control planes, LLM-driven tool invocation into unsafe sinks, prompt/mention SSRF, MCP file-read boundaries, sandbox escape regression checks, untrusted project/module execution, workspace-scope IDOR, role escalation, hardcoded development secrets, and unauthenticated form-submission overwrite.

What changed

  • PraisonAI A2A example tool execution — the first-party unauthenticated A2A example binds to 0.0.0.0, accepts /a2a message/send, and registers an unsafe calculate tool implemented with Python eval().
  • PraisonAI Python sandbox escape — execute_code() subprocess mode missed print.__self__ and vars() paths that can recover the real builtins module despite earlier sandbox patches.
  • PraisonAI generated API server auth-disabled default — praisonai deploy --type api can emit a Flask /chat and /agents server where auth_enabled defaults to false while user workflows may bind externally.
  • PraisonAI call server fail-open auth — CALL_SERVER_TOKEN unset makes the agent invocation router return successfully from its auth dependency, exposing agent listing, metadata, invocation, and deletion.
  • PraisonAI MCP file-read residuals — the prior rules-path fix missed workflow.show, workflow.validate, and deploy.validate, leaving unauthenticated MCP tools/call paths that read arbitrary host files or leak fragments through parser errors.
  • PraisonAI SSRF and file-write agent boundaries — @url: prompt mentions and spider tools can fetch loopback/private-like URLs through weak normalization, while web-crawled hidden metadata can steer write_file when production passes workspace=None.
  • PraisonAI untrusted module execution — YAML-controlled module_path values in agents_generator.py still reach unguarded spec.loader.exec_module() sinks, bypassing earlier local-tool gates.
  • PraisonAI Platform tenant isolation failures — workspace routes check membership on the URL workspace but fetch inner resources by global ID; member-management routes only require ordinary membership; several advisories also cover hardcoded dev-secret-change-me JWT defaults, role-promotion, member removal, dependency/label IDOR, and activity-log disclosure.
  • Formie front-end submission overwrite — unauthenticated users can post a known or guessed submission ID to the front-end save action and overwrite existing Craft CMS Formie submissions.

Operator triage

  1. Separate example risk from deployed pattern risk: the A2A eval() issue is strongest where teams copied the official unauthenticated example or exposed an A2A service with comparable unsafe local tools.
  2. Map every agent-facing network listener: check A2A, generated Flask API, call server, MCP server, and platform API separately; each has a distinct auth/default boundary.
  3. Treat prompt preprocessing as fetch-capable input: @url: and crawler-derived content are pre-tool trust boundaries, not harmless context decoration.
  4. Bind workspace ID to object ID: PraisonAI Platform findings are a clean test case for cross-tenant object substitution in routes that appear workspace-scoped.
  5. Use canaries, not secrets: validate file reads/writes, SSRF, and code execution with synthetic files, callback endpoints, marker values, and disposable workspaces only.
  6. For Formie, require owner-approved test records: submission overwrite validation should use a known lab submission ID, never guessed production records.

Replayable validation boundaries

PraisonAI exposed agent service checks

  • Inventory reachable PraisonAI listeners and record component, bind address, version, and authentication state.
  • For A2A, use a lab server copied from the affected example and a harmless calculation/canary tool. Prove that unauthenticated message/send can reach tool invocation without running OS-impacting commands.
  • For generated Flask API, run deploy --type api in a disposable project and confirm whether /chat and /agents accept requests without an auth header under default config.
  • For the call server, start a lab instance with CALL_SERVER_TOKEN unset and verify unauthenticated agent listing/invocation against a benign echo agent; repeat with a token set to show the missing-token default is the boundary.

PraisonAI MCP and local filesystem checks

  • Use a disposable MCP server process under a test Unix user.
  • Create a canary file outside the expected workflow directory, then call workflow.show, workflow.validate, or deploy.validate with that path.
  • Capture whether the response returns full content, file-existence evidence, or parser-error fragments.
  • Avoid targeting real keys, .env files, cloud credentials, or system secrets; the report should show the path-containment gap without collecting sensitive data.

PraisonAI prompt fetch, crawler, and write-file checks

  • For @url:, run the CLI with a tester-controlled loopback HTTP service that returns a synthetic marker and confirm it is prepended to model context.
  • Repeat with alternate loopback spellings only in a lab to show URL-normalization drift.
  • For crawler-to-write-file, host a page containing benign hidden metadata that requests a temp-directory canary write. Verify that production workspace=None skips containment and writes only the marker file.
  • Record the exact prompt/crawler path that introduced the attacker-controlled metadata; this distinguishes model behavior from tool-boundary failure.

PraisonAI sandbox and module-loading checks

  • For execute_code(), validate in an isolated container with no secrets mounted. Use a marker-only proof such as reading an environment variable set solely for the test or writing a temp canary.
  • Confirm the bypass survives the target version's existing blocked-attribute patches before claiming regression impact.
  • For agents_generator.py, supply a YAML module_path to a benign local module that only emits a marker, then show it executes without PRAISONAI_ALLOW_LOCAL_TOOLS or equivalent approval.

PraisonAI Platform workspace isolation checks

  • Create two disposable workspaces and two low-privileged users.
  • In workspace A, make the caller a normal member. In workspace B, create synthetic agents, projects, issues, labels, comments, and dependencies.
  • Send requests under /api/v1/workspaces/{workspace_A}/... while substituting object IDs from workspace B; stop at read-only proof where possible, or mutate only synthetic records.
  • Test member-management actions from a normal member account: self-promotion, adding an owner/admin, role changes, and member removal. Restore all lab memberships afterward.
  • If PLATFORM_ENV is unset, verify whether JWTs signed with the documented development secret are accepted against a lab account; do not forge real-user tokens.

Formie submission overwrite check

  • In a lab Craft CMS/Formie instance or explicitly authorized staging form, create a harmless submission and record its ID.
  • Submit to actions/formie/submissions/save-submission or the equivalent front-end action without authentication, using only the lab submission ID.
  • Prove that fields are overwritten or rejected based on the patched/unpatched version. Avoid guessing or enumerating production IDs.

Reporting heuristics

  • For PraisonAI, report by boundary rather than dumping all CVEs together: exposed service auth, prompt/URL fetch, MCP file access, sandbox escape, untrusted module loading, and platform tenant isolation.
  • Include the exact role used, environment variables set or unset, bind address, route, and whether the test came from a documented quickstart/example.
  • For tenant IDOR, always include both IDs: the workspace authorized by the route prefix and the object whose owning workspace differed.
  • For sandbox/module execution, keep evidence to marker-only execution and name the missing validation primitive (__self__, vars, unguarded exec_module, or missing local-tool gate).
  • For Formie, include whether front-end editing is enabled, the affected Formie major version, and the unauthorized submission ID overwrite path.

August 25 follow-up: PraisonAI SSRF/origin/auth fail-open wave, mcp-shell allowlist bypasses, and Chainlit MCP stdio command injection

A second, larger PraisonAI wave plus two adjacent agent-runtime/MCP families landed on 2026-08-25. They reinforce the same boundary classes as this page but add three new, durable patterns: SSRF patch-bypass via redirect/DNS-rebinding, origin/auth allowlist fail-open, and MCP command-tool allowlist bypass.

PraisonAI / praisonaiagents (19 GHSAs, published 2026-08-25)

  • SSRF protection is validated-once-then-fetched — bypass it two ways. web_crawl (and the Jobs webhook_url validator) resolves the host once, blocks private/loopback literals, then fetches with httpx.Client(follow_redirects=True) (or a fresh httpx.AsyncClient for webhooks) with no re-validation of the redirect target. Two independent bypasses: (a) a public URL that 302-redirects to 169.254.169.254 / 127.0.0.1, and (b) DNS rebinding (TTL=1s host that resolves public at validation, private at fetch). spider_tools._host_is_blocked() is worse — it never does DNS resolution at all, so 127.0.0.1.nip.io reaches loopback directly. The except socket.gaierror: pass in the webhook validator is a fail-open on DNS error plus a validate-then-fetch TOCTOU. See GHSA-5r34-2g38-6569, GHSA-8hjw-25cg-g52h, GHSA-vg6p-v9vm-6fgj, GHSA-x44h-65qv-cw74, GHSA-rg5q-pp8p-f7jm, GHSA-hmfx-4v44-9qw9.
  • Origin allowlists that use startswith or unanchored regex are bypassable. http://localhost.evil.example passes a startswith("http://localhost") origin check; the WebSocket Chrome-extension origin regex re.match(r"chrome-extension://[a-z0-9]{32}") is unanchored at the end, so any longer origin passes. Both enable browser-mediated unauthenticated MCP tools/call / WebSocket start_session against a local agent runtime. See GHSA-pvph-5j39-v8qc, GHSA-wj6g-v78p-6fx3, GHSA-6g6r-q6gw-w8fg.
  • Auth fail-open and ignored --api-key. praisonai serve / serve agents parse --api-key but never wire it into the FastAPI app, so POST /agents runs unauthenticated even with a key set. The Recipe server fail-opens when auth: api-key|jwt is set but no secret exists (if not expected_key: return await call_next(request)). The async Jobs API (/api/v1/runs) ships with no auth at all. See GHSA-pvxx-r596-f5qj, GHSA-7ww9-85pg-cv4x, GHSA-r7v3-x45f-g7hp, GHSA-gfq8-hmph-9gjv, GHSA-2jgc-f764-c5r2.
  • Workspace containment that keys on abspath not realpath. praisonai.code tools call is_path_within_directory() with os.path.abspath(), so a symlink inside the workspace whose target is outside passes the check while open() follows it; list_files() never calls the containment helper at all. FileMemory.__init__ builds paths from an unsanitized user_id, enabling ../ file writes. ast_grep_rewrite rewrites arbitrary files without the @require_approval gate every sibling mutation tool has. See GHSA-ch89-h4r2-c8f8, GHSA-gxmw-5f7x-6g22, GHSA-cfxv-8fw8-rwpv.
  • Workflow include executes an included recipe's tools.py module-level code even when the documented autoload opt-in is unset, reachable through praisonai.recipe.run() without a network service. See GHSA-hxmv-c4g6-5fqc.
  • Unauthenticated unbounded MCP session accumulation (DoS) via initialize with no TTL enforcement. See GHSA-wv94-5qcp-6m36.

mcp-shell allowlist bypasses (3 GHSAs)

mcp-shell "secure mode" restricts shell_exec to an allowlist, but the default config and validator are bypassable:

  • Git shell-alias injection. The metacharacter blocklist omits !, so /usr/bin/git -c alias.pwn=!<cmd> runs arbitrary OS commands as the process user. Default Docker image runs as mcpuser (UID 1000) with Git installed and secure mode on — exploitable out of the box. See GHSA-74hp-mggr-hv58.
  • Default /bin/bash in the allowlist. The validator only checks the first token, so /bin/bash -c <cmd> runs anything in the container. See GHSA-3x77-wg38-92r3.
  • Security disabled by default in the bare-binary deploy path (Security.Enabled: false unless MCP_SHELL_SEC_CONFIG_FILE is set). See GHSA-f5pj-2738-996m.

Chainlit MCP stdio command injection (CVE-2026-45018)

With features.mcp.enabled = true (MCP is disabled by default since v2.7.0), POST /mcp for stdio transport accepts a user-controlled fullCommand. validate_mcp_command() checks only the executable name against an allowlist and does not restrict arguments, so npx -y -c '<arbitrary command>' executes arbitrary shell commands as the Chainlit process, unauthenticated. Patched in 2.12.0. See GHSA-w3fx-mc44-mf6j. The durable pattern: an allowlist that validates the command token but not its arguments/flags is not a command-execution boundary.

Operator triage for the August 25 wave

  1. Treat every "validate the URL/origin/auth then use it" sink as two separate checks. Redirect-following and DNS re-resolution invalidate a one-time host check. Probe with an owned no-content peer that 302-redirects to loopback/metadata and with a TTL=1 rebind host; stop at the callback/marker, never at real internal data.
  2. Search allowlists for startswith, unanchored re.match, and "first-token-only" logic. Origin/host/executable allowlists that compare prefixes or only the argv[0] are the bypass class.
  3. Assume --api-key/auth flags may be no-ops. Confirm the flag is actually read by the middleware, not just parsed by the CLI.
  4. For workspace containment, key on realpath/resolve, not abspath. Symlinks and ../ are the two escapes; check that every sibling tool (not just the patched one) enforces the boundary and the approval gate.
  5. For MCP command tools, prove the argument boundary. Show an allowlisted executable whose -c/alias.!/-y -c flag reaches arbitrary execution; keep the marker inert (id/pwd/marker file) and lab-only.

Notes on skipped items from this scan

  • Bazaar bzr+ssh dash-prefixed hostname command execution was updated in GitHub Advisories but is an old 2017 issue; it remains useful supply-chain history, not fresh Skillz Wiki content.
  • PraisonAI activity-log disclosure and several narrower dependency/label/member-management advisories were folded into the broader platform tenant-isolation/role-escalation workflow instead of each receiving a standalone page.
  • Stigmem items updated around the same window were configuration-obvious, defensive-hardening, or already reviewed in the prior scan.
  • CISA KEV stayed catalog 2026.05.29 with PAN-OS CVE-2026-0257 already reflected. PortSwigger, ProjectDiscovery, GitHub Security Blog, Trail of Bits /feed.xml, and Disclosed had no separate promotable deltas in this pass.

October 7 follow-up: AgentMail webhook mode accepts forged unsigned message.received events — attacker-chosen sender invokes the agent (CVE-2026-61428 + CVE-2026-61436 / GHSA-qj9c-59p6-8cgx, GHSA-7c92-x8vg-4258, high)

PraisonAI's AgentMail webhook mode (AgentMailBot(mode="webhook") / BotConfig.mode=webhook) binds an aiohttp receiver to 0.0.0.0 and _handle_email_webhook() trusts the parsed JSON body directly: it reads body["type"] == "message.received", schedules _process_webhook_payload(), and returns 200 — with zero verification of the svix-id / svix-timestamp / svix-signature headers AgentMail documents, and the secret returned by webhooks.create() is never even stored. The forged payload builds a BotMessage from attacker-controlled data.from, data.subject, data.extracted_text, data.thread_id, and dispatches it through _handle_message() — firing message hooks, running the configured agent session with the injected text, and replying to the attacker-chosen sender address.

Durable axes for agent/email-gateway platforms:

  1. Alternate ingress mode = alternate authz surface — the signature only ever lives on the provider path. PraisonAI's polling and WebSocket paths receive SDK-typed, provider-verified events; only the webhook receiver parses raw JSON. Same product, same handler, three transports, one unauthenticated door. Audit rule for any bot/agent platform with a mode= switch: test every mode's ingress independently with a forged event; the default mode's auth guarantee does not transfer. (Sibling of this wiki's REST-vs-MCP entry-point parity rule.)
  2. The handler's own accepted field names fingerprint the forgery. The provider's verified payload uses event_type; PraisonAI's handler accepts type + data. When probing any webhook receiver for signature-skip, don't copy the provider's documented schema — grep the handler (or fuzz both shapes) for the field names it reads. An endpoint returning 200 to a wrong-shape-but-handler-correct body while the provider's own test ping gets ignored is the tell.
  3. Sender-spoofed agent invocation is a prompt-injection trigger leg with a reply channel. Forged data.from + injected extracted_text = attacker picks both the prompt and, via hooks/tools, potentially an outbound channel. Frame findings as unauthenticated → agent tool invocation, not just "missing signature": prove in a lab bot with an inert tool (echo/marker), check whether tools/approval gates fire, and whether the auto-reply path sends agent output back to the spoofed address. Never drive a production email agent; owned test inboxes only.
  4. Deploy fingerprint: webhook mode is opt-in (mode=webhook, binds a configured port on all interfaces) — externally, an open port that answers POST <configured webhook path> with 200 to {"type":"message.received","data":{}} while never requiring svix-signature is the test. GitHub's July 2026 advisories GHSA-4mp6-8448-9vgv / GHSA-m64w-vfg6-36ph were withdrawn as duplicates of these — same advisory, do not double-report.

October 7 follow-up: dependency-edge delete authorized against the caller-selected endpoint issue (CVE-2026-62179 / GHSA-mxmx-rh57-jx58, medium, fixed praisonai-platform 0.1.9; vulnerable >=0.1.6 <=0.1.8)

DELETE /workspaces/{ws}/issues/{issue_id}/dependencies/{dep_id} loads the edge by dep_id, accepts it when either dep.issue_id == issue_id or dep.depends_on_issue_id == issue_id, then runs require_delete_permission(resource_owner_id=issue.creator_id) — but issue here is the URL issue, which the caller chose. An ordinary workspace member cannot delete a dependency edge through the owner-created issue endpoint (403), yet the same edge is deletable through the member-owned related issue endpoint (204), destroying owner-side blocks/blocked_by workflow state. The same route family lets any member create edges on owner-created issues with only require_workspace_member (201), so a member can both inject false workflow relationships into owner issues and remove owner-created ones by endpoint selection. Advisory ships a runnable PoV with a full version sweep (0.1.4 masked the bypass by broader older member-delete authorization; 0.1.6+ exposes it).

Durable axes:

  1. Relationship/edge resources addressed from either end: authorization on the URL side is not authorization on the edge. Where a route accepts parent_id matching either endpoint of a relation, the permission check must bind to the canonical/primary side (or both), never the caller-selected one. Sweep every edge-addressed route family (dependencies, labels, comments, links, memberships, shares) with the two-endpoint probe: same edge id, both legitimate parent URLs, one low-priv caller.
  2. Fix-history differential as a discovery map. The 403 on the "obvious" endpoint (owner-issue delete) was added by an earlier advisory round (GHSA-rh39-9c67-59mh broad member-delete fix); the bypass only became interesting after that hardening narrowed the surface. When a product has an advisory history of ownership-check fixes, re-probe alternate legitimate addressing paths for the same object before concluding the class is fixed — patched-guard vs unpatched-sibling-path is the standard drift this product has now shown three times (4x6r cross-workspace IDOR, rh39 broad delete, this endpoint-selection variant).
  3. Decision-table fingerprint for reporting: owner-endpoint 403 vs member-endpoint 204 for the same dep_id, plus owner list-count 0 afterwards, plus member-create 201 on an owner issue — all provable with one owner, one member, and two synthetic issues in a single disposable workspace (in-memory SQLite harness in the advisory). Prove delete-then-list only on synthetic edges; never touch live planning state.

October 7 follow-up (16:1xZ): deploy generator interpolates agents_file into generated source via f-string — config/CLI field is a code-execution input (CVE-2026-62176 / GHSA-g6j7-pffp-8whg, critical, fixed 4.6.78)

deploy/api.py builds the deployable Flask server source by interpolating the agents_file value directly into an f-string template (PraisonAI(agent_file="{agents_file}")), writes it to disk, and runs it via subprocess.Popen(['python', server_file]). A value like "); import os; os.system("id"); # breaks out of the string context in the generated file; the identical pattern exists in deploy/docker.py for Dockerfile generation. agents_file is reachable from CLI arguments, configuration, or an upstream API — so a repo-local config or a CI-passed parameter reaches code execution at deploy time with no validation step in between.

Durable axes:

  1. Codegen interpolation = injection surface, quote-context is the boundary. Any field embedded inside quotes in a generated code/template string must be tested with the target language's breakout sequence (");+statement+comment for Python). Audit grep for any generator: f-strings/.format()/template engines that emit code, and diff which interpolations get escaping/shlex/repr treatment vs raw. Same class as the May wave's generated-report/SSTI legs; the deploy-time executor (Popen(['python', …])) is what makes it RCE instead of stored XSS.
  2. Supply-chain leg: in agent platforms the agents_file/config value routinely originates from a cloned repo or a shared template. Treat "config field → generated file → executed by the deployer" as a PR-to-RCE chain and report it as such, with the injection proven in a disposable directory on your own host (advisory ships a template-echo PoC that proves the string breakout without executing anything).
  3. This is the third distinct PraisonAI bug class published the same day (webhook signature skip, edge-addressing authz, codegen injection) — the same-product full-re-sweep rule again: after any advisory lands on a small agent framework, audit every remaining sink family, not just the reported one.

October 7 20:2xZ follow-up: Crawl4AI backend re-resolves DNS itself (SSRF guard is backend-dependent) and prompt-injection defense defaults to log-only

Two legs from the 20:22–20:43Z PraisonAI wave (GHSA-6g59-gm2v-qhvq / CVE-2026-61429, fixed praisonaiagents 1.6.78; GHSA-fj8f-m44g-c479 / CVE-2026-61439, fixed 4.6.78):

  1. Per-connection SSRF guards die when the backend is a browser. The web_crawl DNS-rebinding/redirect bypass originally proved on the httpx/urllib backend also works with the crawl4ai (headless Chromium via Playwright) backend, which auto-selects when installed: the validator sees one IP, Chromium re-resolves DNS and follows redirects on its own with no guard in between. Advisory is runtime-confirmed with internal-canary read-back. Durable rule for every AI tool with pluggable fetchers: validate the backend actually used, not the component name — enumerate installed optional backends (crawl4ai, playwright, selenium, requests vs httpx) because the guard is written against one fetch path and the auto-selected backend silently swaps the resolver+redirect stack. The rebinding battery (short-TTL A→127.0.0.1, multi-record round-robin, redirect-to-internal) must be replayed per backend, not per product.
  2. Security features with default-off enforcement are log features. The "Injection Defense" layer scans six categories but the block threshold defaults to CRITICAL = 3+ simultaneous detections; a single hit in its own "dangerous" categories (instruction override, financial manipulation) logs and passes. Audit rule for shipped AI-guardrails: read the default threshold config first, then craft inputs that trip exactly one detector — detection-count-gated blocking means the defense is bypassable by paraphrase-per-category. Report as defense-bypass with the single-detector PoC the advisory ships, not as "no injection defense."

October 8 17:1xZ follow-up: second coordinated praisonaiagents wave (<=1.6.77 → 1.6.78, seven distinct legs) — approval-cache keying, sibling-class validation parity, and repo-config-to-write sinks

The 16:36–16:49Z wave republishes the same product at scale (each leg paired with a duplicate GHSA marker — treat the dupes as bookkeeping, not findings). New durable legs, all fixed praisonaiagents 1.6.78:

  1. Human-in-the-loop approval is cached by tool name only and reused for all later calls with arbitrary arguments (GHSA-29r9-67vg-qj56 / CVE-2026-60087). ApprovalRegistry.is_already_approved keys a per-run contextvar set on tool_name; approve_sync/async short-circuit on it before consulting the backend; clear_approved is never called in shipped code, and the cache is inherited by child tasks. Critical-tier tools (execute_command/execute_code) are exempt — write_file is not: one benign approval ("overwrite this scratch file?") buys silent arbitrary-path writes for the rest of the run. Audit rule for every agent framework shipping approval gates: check what the approval cache key includes — name-only keys turn one reviewed argument set into a standing grant; grep for already_approved/is_approved short-circuits ahead of the backend call, and test the approve-benign-then-abuse-same-tool sequence as a first-class case.
  2. Sibling-class validation parity: FileTools._validate_path() exists; SkillTools.run_skill_script() never calls it (GHSA-c44f-37qr-gw3f / CVE-2026-61443). The traversal detector lives in one tool class while another tool class execs script_path from LLM-directed args with zero containment — plus the YAML approve: field bypasses @require_approval for high-risk tools. Same guard-coverage-diff rule as the October wave's HTTP-client pages: diff containment-helper invocation sites across tool classes, not helper existence; and test whether declarative config (YAML approve:) can pre-answer an approval gate meant to be interactive.
  3. Repo-config-to-write sink: project-local .praisonai/config.toml sets defaults.output.output_file to an absolute/.. path and agent.start() auto-saves there with open(...,"w"), mkdir -p included (GHSA-qjw5-xwrp-xwpq / CVE-2026-60089). Cloning an untrusted repo + running its agent = attacker-chosen file write as the developer. Repo-trust fold (May 5 agent-git page genealogy): every config field that names an output path is a write primitive, not a preference.
  4. FastContext search tools accept absolute + .. paths outside workspace_path (GHSA-4xxv-6wmf-xf45 / CVE-2026-61432) — code-search/read/grep/list tools return out-of-workspace file content into model context; prompt-influenced args reach it. The "root directory" documented as a boundary was only ever a default cwd.
  5. Two zero-gate exec sinks from repo-controlled files: AgentFlow._resolve_pydantic_class exec_module()s a sibling tools.py whenever a workflow step uses a string output_pydantic (GHSA-4gfv-wg42-7jw5 / CVE-2026-61437) — silent, unsandboxed, ignores the PRAISONAI_ALLOW_*_TOOLS env gates used elsewhere; and plugin auto-discovery exec_module()s any .py in .praisonai/plugins/ with only a docstring-field "check" (GHSA-m6wp-h223-4c8g / CVE-2026-61446). Both are clone-and-run PR-to-RCE legs; the durable grep is spec_from_file_location + exec_module without an allow-list, and "the env gate that guards sink A does not guard sink B/C" parity check.
  6. CodeAgent ignores its own sandbox=True config: LLM-generated code runs via python tempfile.py subprocess with os.environ.copy() — full parent secrets, zero AST/import validation (GHSA-2xv2-w8cq-5gxw / CVE-2026-61447, critical). Config-flag-does-not-gate-the-sink rule (same shape as the SGLang PickleWrapper fold the same day): read what the "sandbox" option actually changes in the code path before trusting it; for any agent code-exec tool, prove with a marker env var that is absent in a real sandbox.
  7. web_crawl check-time-vs-connection-time DNS rebinding with response read-back, runtime-confirmed with internal canary markers (GHSA-qg25-6gc4-48mg / CVE-2026-61430) — the rebinding leg already covered by the October 7 follow-up above; this is the original guard's first spelling. Any validator that calls getaddrinfo at check time and hands the URL string to the fetcher is rebinding-testable by construction.

Meta-lesson for this product: this is the third multi-leg wave in ten days on PraisonAI (May core wave, Oct 7 five-leg wave, Oct 8 seven-leg wave) — the same-product full-re-sweep rule keeps paying: after any advisory lands here, audit all remaining sink families (approval caches, tool-class parity, config-driven paths, discovery/exec sinks, code-exec sandboxing), because each wave confirms the audit was never finished.

October 8 17:5xZ follow-up: same-sweep continuation — three more legs in the next 30 minutes (≤1.6.78)

The 17:1xZ seven-leg fold was not the end of the sweep; three more legs landed 17:52–17:58Z:

  1. ContextGatherer include resolution reads outside the workspace (GHSA-q7m5-3jmv-vm48 / CVE-2026-61431): repo-controlled .praisoncontext / .praisoninclude files name absolute or .. paths; context gathering opens them and appends contents to the model prompt/caller bundle. Clone-to-file-read via the context builder — the sibling of the config.toml write leg: every "gather project context" feature that trusts repo-supplied include lists is an exfil channel.
  2. Custom command templates interpolate @path reads into prompts (GHSA-xpx6-x8c2-mw5w / CVE-2026-60088): .praisonai/commands/*.md bodies auto-discover, and @../outside_secret.txt / @/etc/passwd expand relative to CWD with no canonicalization. Same clone-to-read shape through a second feature (commands vs context) — the guard-parity sweep rule again: one path-containment helper, N feature loaders.
  3. PGVector/Cassandra knowledge stores interpolate dimension into CREATE TABLE DDL (GHSA-wf65-4jjx-q444 / CVE-2026-60090): schema/keyspace names get validated; the numeric dimension argument is concatenated raw — Python type hints enforce nothing at runtime. Identifier validation without value validation. On any vector-store API where you can create a collection, fuzz the dimension/count numeric fields with SQL/CQL tokens; DDL-position injection class beside this repo's Oct 6 Payload join-predicate leg.

Meta-lesson strengthened: four batches in ~90 minutes on one product = a bulk advisory train, not a wave. When the same package publishes twice in one hour, treat the first batch as a sample of the backlog, wait for the train to end (re-check the updated feed before closing the run), and sweep the whole sink family at once — this page now catalogs approval caches, tool-class parity, config-path writes, discovery exec, sandbox no-op, rebinding, context-gather reads, command-template reads, and DDL interpolation.

October 8 20:1xZ follow-up: same codegen sink, second interpolated field; Host-header localhost gate (≤4.6.78 train continues)

  1. Second field into the same generated server source (GHSA-79fv-7hq9-w7xg / CVE-2026-61433, high): generate_api_server_code() also splices deploy.api.host from agents.yaml raw into app.run(host='{config.host}', ...) and agents_file again into route-handler expressions — a second route-time injection site beside the October 7 agents_file leg. Same rule restated: fix the interpolation discipline of a code generator once, per-field; per-field fixes leave the next raw {...} slot live. On any config-to-generated-code audit, enumerate every interpolation in the template, not just the reported field.
  2. PRAISONAI_CALL_AUTH=disabled localhost gate reads request-derived URL host, not socket binding (GHSA-2gpf-2492-q9jh / CVE-2026-61435, high): verify_token() admits the auth-disabled fast path when request.url.hostname ∈ {127.0.0.1, localhost, ::1} — spoofable via Host: header, so an unauthenticated network caller lists/invokes registered agents. Tracked as one more instance of the Host-is-data-not-identity family (July 8 loopback control-plane page, DeepSeek Harness 8m2g leg): the gate implementation (request.url.hostname string compare vs socket peer) is the fingerprint. Folded here only as product state.
  3. Prompt-defense 3+-detector threshold re-listing (GHSA-4r3p-w3mc-5v34 / CVE-2026-60086) duplicates the already-covered fj8f detection-count-gated-blocking leg — tracked, no new axis.

October 8 22:1xZ follow-up: fifth batch in ~4 hours — npm AgentOS ships unauthenticated by default, SecurityPolicy is a config object the sandbox never reads, and find -exec beats the metacharacter block

The 21:57–22:10Z train (each leg carrying duplicate-GHSA bookkeeping markers) added the fifth PraisonAI batch today. Durable legs:

  1. Middleware registered only when a secret exists (GHSA-6wjp-v33h-5cvq / CVE-2026-61426, npm AgentOS; GHSA-4w49-gwv8-fpjg / CVE-2026-60091, Jobs API; GHSA-hc5v-gxvj-58wh / CVE-2026-61427, MCP http-stream): API-key/CORS/auth middleware is conditionally registered — absent key = no middleware at all, 0.0.0.0 bind + CORS * with credentials. Quickstart-served AgentOS leaks agent names/roles/system prompts via GET /api/agents and executes agents via POST /api/chat; the MCP transport exposes tools/list (~50 tools) with no Authorization, and the dispatcher forwards arguments without validating against the advertised inputSchema — schema is documentation, not enforcement. Same-shape trio, one rule: on any agent-platform surface, the auth gate's existence can be config-conditional — fingerprint "middleware only if secret" by requesting the admin-shaped route before authenticating; system-prompt read is a first-class bounty item (prompt/IP disclosure), and MCP tools/list from an unauthenticated peer is the standing recon probe.
  2. SecurityPolicy fields are fiction: the default SubprocessSandbox implements none of them (GHSA-5r6c-gj4g-r697 / CVE-2026-60085): blocked_commands/blocked_paths/allow_subprocess=False/strict() are a documented data class; the shipped executor writes code to a temp file and create_subprocess_execs it with zero checks against the policy. The doc-claim-vs-code-path audit rule from today's CodeAgent sandbox=True leg, generalized: grep the enforcement sites of every security-config field; a policy object with no reader is a compliance checkbox. For assessments: configure the strictest documented policy in a lab, then prove a blocked command/path still executes — that's the report.
  3. Allowlist bypass via find -exec ... {} + (GHSA-cv3g-hj65-pcfh / CVE-2026-61434): the fix for the earlier shell-chaining CVEs blocks metacharacters and spawns with shell:false — but find stays allowlisted and is itself an executable runner: -exec cmd {} + uses the + batch terminator instead of the blocked ;, zero metacharacters needed. First-token allowlists must ask of every entry "does this binary embed an exec primitive?" (find -exec, awk system(), git !, less !, man, zip -pipe...). The blocklist was on shell syntax; the danger was argument semantics. Four parallel implementations of the agent stack each carried the same gap — sweep every implementation, not just the package on PyPI.
  4. AICoder UI tools: prompt injection → arbitrary write + exec, as root in Docker (GHSA-9mp3-24cc-77mg / CVE-2026-61445, critical): write_to_file/execute_command exposed to the LLM with no path validation/sanitization — authorized_keys//etc/crontab writes, containers run root. The canonical prompt-injection-to-host chain; fold on the sandbox-fiction legs above (the missing piece was never a policy engine being buggy, it was a policy engine never being wired).

Meta: five batches in one day on one product line — the bulk-train lesson from 17:5xZ reconfirmed. For wiki purposes this page is now the PraisonAI sink catalog; future legs fold here only if they add a new class (default-off auth middleware registration, allowlist-embedded-runner, doc-claim-vs-enforcement are today's additions). The platform-side legs (c78w PATCH owner-rewrite/lead-reassignment, xxgv label rewrites) are re-spellings of the already-covered either-end edge-addressing and coarse-PATCH classes — tracked, no new axis.