
Benign, self-contained reproduction of CVE-2026-77521 (MaxKB prompt-injectable command execution)
A self-contained, benign lab that reproduces
GHSA-f36j-f34j-h3rx
/ CVE-2026-77521: in MaxKB (an
open-source enterprise AI assistant / RAG platform), any assistant with a tool,
MCP tool, skill, or sub-application gets an auto-injected shell execute tool
that is not behind a human-approval gate — so prompt injection (from chat
or an ingested document) reaches a shell. A sandbox was supposed to contain it,
but a shell=True string let metacharacters escape it.
| Advisory | GHSA-f36j-f34j-h3rx |
| CVE | CVE-2026-77521 |
| Project | 1Panel-dev/MaxKB |
| Affected | < 2.10.5-lts |
| Patched | 2.10.5-lts (commit 594f50f) — partial, see below |
| CVSS | 10.0 CRITICAL (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) |
| Weakness | CWE-78 (OS command injection) · CWE-250 (unnecessary privileges) · CWE-749 (exposed dangerous method) |
This reproduces a patched, publicly disclosed vulnerability for educational and defensive purposes. It runs entirely locally and benignly:
id, writing a marker file
the PoC deletes). No real gosu/sandbox/root is involved — the privilege-drop
wrapper is replaced by an inert env marker so the demo runs anywhere.A MaxKB assistant with any capability attached is built on deepagents with a
SandboxShellBackend. deepagents auto-injects a shell execute tool (plus
read_file/write_file/…), and MaxKB uses no excluded_tools, so execute is
always present. The interrupt_on config — which decides what needs human
approval — is {"write_file": False, "read_file": False, "edit_file": False}:
execute isn't in it, so it runs with no approval. Any text that can steer
the model (a chat message, or a poisoned document the RAG pipeline ingests) can
therefore make the agent run shell commands. On source installs SANDBOX
defaults off, so commands run as the app user; in the official (root) container
the sandbox wrapper was a shell=True string, so ;/>/$() ran outside
gosu, as root.
interrupt_on, an injected execute call runs with no approval; adding
"execute": True gates it. → poc/01_missing_hitl_gate.py;+redirect escapes the
single-string gosu prefix; the 2.10.5-lts _build_sandbox_command()
re-quoting neutralizes it. → poc/02_sandbox_escape.pyexecute with the attacker's command (and doesn't on a clean
doc). → poc/03_real_llm.pyThe shipped 2.10.5-lts patch (commit 594f50f)
hardens only the sandbox escape — it adds _build_sandbox_command(), which
splits on shell operators, re-quotes every token with shlex.quote, and wraps
each simple command in the gosu prefix. Verified against the
v2.10.5-lts source,
the agency core is unchanged: interrupt_on still omits execute, the
backend is still SandboxShellBackend, and SANDBOX still defaults to 0. So
after the fix, a prompt-injected execute still runs without human approval —
the inner wall was reinforced while the gate stayed open. PoC 1 demonstrates the
recommended mitigation (gate execute), which the vendor did not ship.
No dependencies for PoC 1/2 (Python 3.9+):
./run.sh # all three (PoC 3 skips without an endpoint)
python3 poc/01_missing_hitl_gate.py # missing approval gate
python3 poc/02_sandbox_escape.py # metacharacter sandbox escape
Presenting this?
DEMO.mdis a runbook anddemo.shdrives it one keypress at a time.
Any OpenAI-compatible endpoint (self-hosted or hosted open-model). Example (Groq):
export OPENAI_BASE_URL=https://api.groq.com/openai/v1
export OPENAI_API_KEY=$GROQ_API_KEY # read from env only; never logged
MODEL="openai/gpt-oss-20b" python3 poc/03_real_llm.py
Behind a TLS-inspection proxy it retries unverified with a warning
(INSECURE_SSL=1 to force it). It skips cleanly if no endpoint is set.
| Path | What it is |
|---|---|
sandbox_shell.py | Benign model of MaxKB's execute() command construction — vulnerable string vs patched _build_sandbox_command() |
agent_sim.py | Minimal model of the deepagents interrupt_on human-approval gate |
payloads/poisoned-document.md | Attacker-poisoned KB document (indirect injection) |
poc/01_missing_hitl_gate.py | Injected execute runs with no approval; gating blocks it |
poc/02_sandbox_escape.py | Metacharacters escape the gosu prefix; the 2.10.5 parser contains them |
poc/03_real_llm.py | A real model calls execute from the poisoned doc (clean vs poisoned) |
DEMO.md / demo.sh | Presenter runbook and paced demo driver |
assets/attack-flow.{svg,png} | Attack-flow diagram (blog hero) |
assets/poc3-groq-sample.txt | Recorded PoC 3 output (demo fallback) |