Skip to content

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

  1. 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_on map, 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 whose execution_allowed() is hardcoded True, 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_after set at one construction site but not the sub-agent constructor; presenter classes with execution_allowed/confirm returning constants.
  2. Mock/stub identity functions ship to production. DB-GPT's get_user_from_headers is a mock that returns admin for everyone — a test fixture on the request path. Grep any agent/LLM platform source for mock, fake_, get_user, default_user, admin returns 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 + unsandboxed code_interpreter = zero-credential root — on recon, treat an exposed /chat*, /tools/call, or /react-agent route as a candidate pre-auth shell before crediting the login wall.
  3. The sandbox boundary is environment + network, not just filesystem. Letta's LocalSandbox inherits the parent's os.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.
  4. 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 Flowise importlib \b-boundary and chr() bypasses (Aug 4 page) and the PraisonAI find -exec allowlist-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.
  5. Tenant binding must be checked on the object, not the request envelope. Coze's IDOR accepts your own spaceID alongside a foreign workflowID — 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/nodeDebug also convert a read IDOR into an execution IDOR — score them first.
  6. Self-asserted identity headers are the cheapest auth bypass in the fleet. Letta's user_id header 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.
  7. Compose/quickstart files ARE the default security posture. Letta's shipped compose.yaml omitted 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): SamlLoginServlet callback 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.cgi OS cmd injection (3g79/104586, critical, public) — shared-hosting control-plane CGI cmdi, thin description; watch for KEV/writeup.
  • MISP Modules html_to_markdown SSRF deny-list gaps (x7r8/112991) and IBM ContextForge MCP Gateway OAuth token_url SSRF (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.