Agent approval-gate bypass wave: delegation legs, headless presenters, mock auth shipped to prod, and shared-bridge sandboxes (2026-10-20 03:32Z coordinated GHSA cluster)¶
Source: hourly offensive-security scan, 2026-10-20 03:32Z GitHub Advisory wave (landed in the 08:1xZ scan) — a coordinated multi-product disclosure against the Python agent-framework ecosystem (Letta, DB-GPT, Coze Studio, LangAlpha, EvoScientist, OpenSRE, piflow, Langchain-Chatchat, Aix-DB, JoySafeter; CVE-2026-77926–77938 and CVE-2026-109440–109444 bands). Durable because ~15 advisories describe the same three defects wearing different framework hats: (1) an approval/human-in-the-loop gate that is enforced only in the interactive presenter path and silently absent in every other execution context, (2) identity resolved from a stub/mock/header that production ships verbatim, and (3) a "sandbox" that is a subprocess with the parent's environment on a shared network. This page is the approval-gate enforcement-context catalog the May 29 PraisonAI page started, completed across ten products at once.
The wave at a glance¶
| Product | Leg | Defect shape |
|---|---|---|
| EvoScientist ≤0.2.4 | GHSA-626g-hx6f-fj54 / CVE-2026-77930, high | start_async_task sub-agents lack the interrupt_on gate the parent has — prompt planted in a fetched web page runs host shell commands unattended |
| OpenSRE ≤0.1.2026.10.19 | GHSA-fqh3-76w5-5vvf / CVE-2026-109444, high | Headless presenter's execution_allowed() always returns True — shell_run executes without confirmation; injection via alerts/Slack messages the agent triages |
| DB-GPT ≤0.8.2 | GHSA-5vjv-vw3m-qpqp / CVE-2026-77932, critical | /api/v1/chat/react-agent uses a mock get_user_from_headers that grants every caller admin; unsandboxed code_interpreter/shell_interpreter run as root in the webserver container |
| Letta ≤0.16.8 | GHSA-8p6h-849r-ww4h / CVE-2026-77937, high | API identity taken from a self-asserted user_id HTTP header — spoof another tenant, read/modify their agents, memory, config (even with LETTA_SERVER_SECURE on) |
| Letta ≤0.16.8 | GHSA-g524-hhf9-5x95 / CVE-2026-77936, critical | Shipped compose.yaml never sets LETTA_SERVER_SECURE → password middleware never added, all routes unauth + POST /v1/tools/run arbitrary Python + superuser Postgres port with default creds (COPY PROGRAM → OS cmd) |
| Letta ≤0.16.8 | GHSA-jxmg-qw9j-hxj9 / CVE-2026-77938, high | LocalSandbox tool subprocesses inherit the full server environment — LETTA_PG_URI, POSTGRES_PASSWORD, provider API keys readable from any tool run |
| Coze Studio <0.5.0 | GHSA-46jr-m373-8hpx / CVE-2026-77934, high | Workflow code nodes run in unsandboxed python3 subprocess; regex import filter bypassed with __import__('subprocess'); env vars, model keys, MySQL/MinIO/Redis/etcd reachable |
| Coze Studio ≤0.5.1 | GHSA-w9wj-jq72-68p2 / CVE-2026-77935, high | workflow.go IDOR: pair your spaceID with a foreign workflowID on save/update/batch_delete/canvas/test_run/nodeDebug — read, run, overwrite, delete other tenants' workflows |
| LangAlpha ≤2026.09.07 | GHSA-fcmr-j7xj-36vp / CVE-2026-109440, high | Indirect prompt injection → Bash/ExecuteCode no allow-list, no human-in-the-loop → workspace sandbox code exec, vault secrets exfil from _internal/.vault_secrets.json |
| LangAlpha ≤2026.09.07 | GHSA-g5q6-qcmx-6vw9 / CVE-2026-109441, medium | All tenant sandbox containers on Docker's default bridge network with ICC enabled — code exec in your own sandbox = direct network access to other tenants' containers, bypassing require_workspace_owner |
| piflow 2.0.0–2.0.1 | GHSA-9925-27m4-6rvp / CVE-2026-109442, critical | POST /chat + /chat/stream no authentication; exec_shell tool runs subprocess.run with no approval/allow-list → LLM keys + DB creds |
| Langchain-Chatchat ≤0.3.1.3 | GHSA-5rx2-xp53-9x5r / CVE-2026-75128, critical | POST /tools/call invokes any registered tool by name, no auth — the always-registered shell tool = one request → OS commands as root in shipped Docker image |
| Aix-DB 1.2.4 | GHSA-m2vr-p43p-p3h6 / CVE-2026-77926, high | ChatBI common agent's deepagents execute tool ungated — authenticated non-admin runs OS commands via POST /dify/get_answer or poisoned document uploads |
| JoySafeter ≤1.0.0 | GHSA-qmmc-575c-v2h8 / CVE-2026-109443, high | Self-register → "standard mode" WebSocket chat.start → Copilot subprocess.run(shell=True) on the host, no sandbox → cross-tenant data + server secrets |
Durable operator rules¶
- Approval gates are per-execution-context, and every non-interactive context is the bypass. Two fresh mechanisms this wave: delegation legs (EvoScientist — the child/async task is spawned without the parent's
interrupt_onmap, so the same dangerous tool that pauses for a human inline runs unattended when deferred) and presenter legs (OpenSRE — the headless deployment wires a presenter object whoseexecution_allowed()is hardcodedTrue, so the approval UI exists but its answer is pre-baked). Audit rule for any agent framework with human-in-the-loop: enumerate every path by which a tool call reaches an executor (inline, async/sub-agent, headless/CI presenter, API-driven, scheduled/cron agent, webhook-triggered) and check each one consults the real approver; then test the approve-benign-inline / abuse-deferred sequence the PraisonAI page already cataloged, plus never-approve-inline and fire it deferred. Grep fingerprints:interrupt_on/interrupt_afterset at one construction site but not the sub-agent constructor; presenter classes withexecution_allowed/confirmreturning constants. - Mock/stub identity functions ship to production. DB-GPT's
get_user_from_headersis a mock that returns admin for everyone — a test fixture on the request path. Grep any agent/LLM platform source formock,fake_,get_user,default_user,adminreturns on auth-dependency functions; from outside, fingerprint by calling an admin-shaped endpoint unauthenticated and scoring 200-vs-401 (the missing-handler-auth oracle again). Chain note: mock-auth + unsandboxedcode_interpreter= zero-credential root — on recon, treat an exposed/chat*,/tools/call, or/react-agentroute as a candidate pre-auth shell before crediting the login wall. - The sandbox boundary is environment + network, not just filesystem. Letta's
LocalSandboxinherits the parent'sos.environ(every cloud/provider key readable through an "innocent" tool run) and LangAlpha's tenants share Docker's default bridge with inter-container communications on (one tenant's code exec = direct sockets to every neighbor container). Validation battery for any hosted agent sandbox you're authorized to test: (a) print the env inside the sandbox — keys present = boundary fail regardless of path jail; (b) enumerate RFC1918/link-local neighbors on the sandbox network and probe well-known ports — reachable sibling containers with no service auth = cross-tenant read; (c) check for a secrets file convention (.vault_secrets.json, mounted tokens) inside the workspace itself. - Import-filter regexes are deny-lists, and Python gives infinite spellings. Coze Studio's code nodes blocked bare
import os-style strings but__import__('subprocess')passes — same shape as the Flowiseimportlib \b-boundary andchr()bypasses (Aug 4 page) and the PraisonAIfind -execallowlist-embedded-runner (May 29 page). Sweep order for a code-node filter:__import__,importlib.import_module,exec(open(...).read()),os.__dict__,sys.modules['os'],builtins.__import__, string-concatenated module names. Prove with an inert marker (file write / echo), never destructive commands. - Tenant binding must be checked on the object, not the request envelope. Coze's IDOR accepts your own
spaceIDalongside a foreignworkflowID— every route in the family (save, update_meta, batch_delete, canvas, test_run, nodeDebug) honored the pair. Either-end-addressing rule restated: on any parent-child resource API, run your-ID × foreign-ID × both-foreign decision matrix across every verb, including the debug/test verbs which are the forgotten, often less-gated siblings.test_run/nodeDebugalso convert a read IDOR into an execution IDOR — score them first. - Self-asserted identity headers are the cheapest auth bypass in the fleet. Letta's
user_idheader is trusted even in secure mode — no token, just a name. Standing probe on every agent-platform API: re-run one authenticated read with only the identity header changed to a second lab tenant. Header-is-data-not-identity family (DeepSeek Host gate Oct 19, Nginx UI node secret Oct 9), new carrier: plain custom user-id header. - Compose/quickstart files ARE the default security posture. Letta's shipped
compose.yamlomitted the env var that installs auth middleware — "follow the docs, get an open admin panel with arbitrary-Python execution" — and DB-GPT/Aix-DB/piflow/Langchain-Chatchat all shipped root-in-container + default DB creds alongside. For product assessment: deploy the vendor's own compose file in a lab, diff which middleware/gates exist vs the env-flag inventory in.env.example/docs; for live targets, these stacks fingerprint on route shapes (/v1/tools/run,/api/v1/chat/react-agent,/tools/call,/dify/get_answer) — score unauthenticated POST acceptance as a pre-auth RCE candidate with inert canaries only.
Validation boundaries¶
Own lab deployments of each framework via vendor compose only; two synthetic tenants with disposable data; inert markers (touch, echo-to-file, benign env dump of your own synthetic vars) for exec/env proofs; owned callbacks for SSRF-adjacent legs. Never enumerate or connect to real sibling tenant containers outside a lab you operate, never read live provider keys, never run commands beyond inert markers on shared hosts. Cross-tenant proofs require two accounts you own.
Tracked from the same wave, no publish¶
- Apple iOS 26.7/27 + macOS patch-day ~40-leg memory-safety/authorization pack (CVE-2026-86868–86981, 84508/84547/84579/84582/84613 band) — boilerplate zero-detail singles, memory-safety class per precedent.
- Roundcube ≤1.6.20/1.7.5 IMAP command injection via CRLF in
_uid(qwfm/113386, high) — CRLF-into-protocol-framing class (Net::IMAP/Netty codec precedent) — thin single, tracked; revisit if a writeup shows a stronger sink. - OpenMetadata token-appended open redirect (j399/81029, high):
SamlLoginServletcallback param stored unvalidated, then JWT + email + name appended and redirect issued — token-carrier open redirect = full ATO; class kin to Mattermost invite-link/redirect legs, folded-in knowledge sufficient. - AUTOM8N (WHM/cPanel plugin)
borg_restore.cgiOS cmd injection (3g79/104586, critical, public) — shared-hosting control-plane CGI cmdi, thin description; watch for KEV/writeup. - MISP Modules
html_to_markdownSSRF deny-list gaps (x7r8/112991) and IBM ContextForge MCP Gateway OAuthtoken_urlSSRF (6f4r/8355) — promoted as folds on the July 30 URL-policy and Oct 1 MCP-gateway pages respectively, not repeated here. - MLflow FileStore trace-ID traversal (wqmh/53995) — folded on the Aug 19 MLflow page.
- IBM Langflow three message endpoints without ownership (qf42/107366) — bulk-delete/overwrite/session-ID-reassign of other users' conversations: coarse per-object authz drift on message routes; the June 19 Langflow page's ownership-drift catalog already carries the class; tracked.
- ASUS router unauth-critical-function + overflow (8vcw/75753, 8j23/17518), Zyxel EX3501-T0 DAL authz (c3cx/12489), H3C Magic NX15 ×4 (2cfh/xpx5/r3g6/8prf), Atsumi DVR hidden-functionality pair (wvwj/97627, 9p4x/100737), Armoury Crate search-path LPE (978r/102438), IBM Sterling/VIA/TS4500/TRIRIGA boilerplate singles, unRTF OOB write (q9jg/109831), RingRTC FSM-queue DoS (5c63/113380), Ning compress-lzf OOB (7xjc/109395), Markdown2/anchorme ReDoS pair, ELADMIN stored XSS, gomarkdown short-URL panic re-listing, rejected-CVE batch, Linux kernel wave re-listings.
KEV unchanged (catalog 2026.10.16, count 1748, entry-set sha 03799809-method-dependent; newest-set identical: BeyondTrust 102764, Fortinet 61624, Atlassian 21589, Ahsay 105133/105134, PAN-OS 0322, Cisco SEG 76519). None of this wave's CVEs KEV'd. RouterOS 84411 still not KEV'd. BeyondTrust primitive still withheld.
October 21 18:3xZ follow-up: webhook-gateway identity via composite string — colon in a route name escalates toolset authorization (GHSA-4h98-2jhp-qgm2 / CVE-2026-114463, Nous Research Hermes Agent webhook platform gateway)¶
The gateway builds the session chat_id as webhook:{route_name}:{delivery_id} and later re-derives the authorizing route by splitting that string. A route name containing ":" resolves toolset authorization to a different, higher-privileged prefix route than the one actually authenticated — an attacker holding the target route's HMAC gains the higher route's toolsets. The signature check passes; only the identity re-derivation is wrong.
Durable rule for this page's family (shared-bridge / identity-carrier legs): - Composite identity strings that are later re-parsed are authorization input, and every attacker-influenceable segment must be delimiter-free (or the parse must be prefix-anchored on trusted fields only). The generalization of the wiki's "credential routing keys must be session-scoped" rule from JSON-RPC ids to stringly-typed identities: grep for split(":")/strings.Cut/parts[1] on any field that participates in an authz decision, then test delimiter injection through every segment of the composite (here, the operator-chosen-but-attacker-reachable route name). This is the same genealogy as the MCP TS SDK id-routing leg (routing by JSON-RPC id alone) — replace "id" with "concatenated identity" and the probe is identical: does the re-derived identity match the authenticated one? - Probe shape on any webhook/relay gateway with a delivery-id or channel-id scheme: register (in a lab) a name containing the join delimiter that re-prefixes onto a privileged namespace, deliver with the low-privilege route's valid signature, and observe which toolset/permission set the executor loads. HMAC-valid-but-misrouted is the pass condition.
October 23 21:1xZ follow-up: Pydantic AI Harness — CodeMode nested calls bypass tool_calls_limit because the check lives on the wrong dispatch path (GHSA-3589-pxp7-3gj8 / CVE-2026-114386, <0.59.0)¶
UsageLimits.tool_calls_limit is enforced via check_before_tool_call on the model's direct tool dispatch, but the CodeMode toolset's sandboxed program invokes tools through run_nested_call() which never reaches that check — a user or prompt-injected content makes the sandboxed code call costly/side-effecting tools past operator limits. Also new this wave: Livewire 4 #[Authorize] desync trio (GHSA-mhf7-xp6r-6qrc, <4.4.8): the attribute resolves model parameters by array key while the action receives them positionally, so {"1":2,"0":1} authorizes against one record and executes on another (same desync through #[On] event __dispatch), and #[Json] methods execute their body before authorization runs — 403 response, side effects already committed (DB writes done).
- Gate-on-the-path rule: for every operator limit/approval gate, enumerate all dispatch entry points that can reach the protected resource — direct tool-call, nested/programmatic call, event dispatch, batch/continuation. A guard wired on one caller while other callers reach the same executor is this page's core pattern in its cheapest form. Grep
run_nested_call/recursive-invoker analogues on any agent harness. - Key-vs-position binding mismatch (Livewire): any framework that maps request params to handler args by two different rules (object keys for one pass, positional order for another) gives attackers record-choice divergence. Battery: send params as an object with reordered integer keys and diff which record the authz layer binds vs which the handler mutates. Joins the standing either-end/alternate-binding family (PraisonAI either-edge Oct 7, Gitea URL-repo-vs-owning-repo Oct 14).
- 403-with-committed-side-effects is the report-worthy half:
#[Json]-before-authorize means the denial is cosmetic. On any declarative-gate framework, run the mutation with a request you expect to be denied, then check whether the side effect landed despite the 403 — gate-order-vs-handler-start is the invariant. - Validation boundary: lab harness with a synthetic costly tool (counter increment), lab Livewire component with two disposable records owned by different test users; never run reordered-key deletes against real data.