NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
HIGH severity

CVE-2026-113663 — ag2

HIGHFix: ag2ai/ag2@3cd8672

CVE-2026-113663 is a high-severity (CVSS 8.8) OS Command Injection vulnerability in ag2. A fix is available for ag2 — see the affected versions and patch details below.

AG2: Newline injection in SandboxShellTool bypasses allowed=[...] and readonly=True, enabling arbitrary command execution

Also known asGHSA-2h4w-29cv-2cmv
Published
Updated
Affected
2 pkgs
Patched
1 / 2
Exploits
None indexed
Exploitation data as of Nov 10, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • A successful exploit gives an attacker total control of the affected component, not partial access.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-113663.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs29th percentile — riskier than 29% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

Where this sits among everything scored

Real-World Exposure

2 pkgs affected
🐍ag2🐍autogen

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

SandboxShellTool's restricted modes — allowed=[...] (explicit command allowlist) and readonly=True (implicit READONLY_COMMANDS allowlist) — can both be bypassed by embedding a newline character inside the command string. The filter only inspects a single-line set of shell metacharacters, but execution runs the raw string through sh -c, where a shell treats an embedded newline exactly like a ; statement separator. An agent (or content that steers the agent, e.g. via prompt injection) can therefore run arbitrary shell commands even when the tool was configured to allow only a specific command prefix or only read-only commands. With the default LocalEnvironment() backend this is a real host subprocess (its own docstring: "Commands run via subprocess on the host, so there is no real isolation"), not an isolated container, so this is full host command execution, not merely an escape from an already-sandboxed environment.

Details

Both restricted modes are enforced by ShellAdapter._filter() in ag2/tools/sandbox/adapter/shell.py:

def _filter(self, command: str) -> str | None:
    if self._allowed is not None:
        if not any(matches(p, command) for p in self._allowed):
            return f"Command not allowed: {command!r}"
        if contains_shell_operator(command):
            return f"Command not allowed (shell operators are not permitted in restricted mode): {command!r}"
    ...

readonly=True reuses this exact same branch: SandboxShellTool.__init__ / ShellAdapter.__init__ sets self._allowed = list(READONLY_COMMANDS) when readonly is set and no explicit allowed is given, so both modes share one filter and one bypass.

contains_shell_operator() in ag2/tools/sandbox/filter.py checks the command against:

_SHELL_OPERATORS: tuple[str, ...] = (">", ">>", "|", ";", "&&", "||", "`", "$(")

This tuple has no entry for \n (or \r). matches() only checks that the command starts with an allowed prefix followed by a space or end-of-string; it never inspects the remainder of the string. Execution is:

argv = ["sh", "-c", command]
...
return await sandbox.exec(argv, env=self._env, timeout=self._timeout)

A POSIX shell splits sh -c "<line1>\n<line2>" into two sequential statements, the same as if they were separated by ;. So a command such as "cat allowed_file.txt\nrm -rf /anything":

  1. Passes matches("cat", command) — the string starts with "cat ".
    1. Passes contains_shell_operator(command) — \n is not a recognized operator.
    1. Reaches sh -c "cat allowed_file.txt\nrm -rf /anything", which runs both statements.

Proof of Concept

Verified directly against the current main branch (github.com/ag2ai/ag2, commit a05ff1cb6ff4955ef252a4e0d4a27d3a32ad57f0, cloned 2026-09-21), by importing the real ag2/tools/sandbox/filter.py module (stdlib-only, no other AG2 dependencies needed) and reproducing ShellAdapter._filter()'s allowlist branch verbatim:

=== allowed=['cat'] ===
'cat /etc/passwd; rm -rf /tmp/x'   -> Command not allowed (shell operators are not permitted in restricted mode): ...
'cat /etc/passwd && rm -rf /tmp/x' -> Command not allowed (shell operators are not permitted in restricted mode): ...
'cat /etc/passwd | rm -rf /tmp/x'  -> Command not allowed (shell operators are not permitted in restricted mode): ...
'cat /etc/passwd\nrm -rf /tmp/x'   -> None   <-- BYPASS: newline variant is NOT rejected

=== readonly=True (uses READONLY_COMMANDS as the allowed list) ===
'echo hi\nid'                      -> None   <-- BYPASS in readonly mode too

=== sh -c really treats the embedded newline as a second statement (mirrors ShellAdapter._exec_async) ===
$ sh -c 'echo first-command-output\necho SECOND-STATEMENT-RAN-$((21*2))'
first-command-output
SECOND-STATEMENT-RAN-42

The three standard chaining attempts (;, &&, |) are correctly rejected by the real filter, confirming it does real work; the newline-separated variant, expressing the same intent, is accepted by the identical filter in both restricted modes, and a real sh -c call confirms the shell executes both statements.

Impact

Any agent configured with SandboxShellTool(allowed=[...]) or SandboxShellTool(readonly=True) — the two configurations the tool's own docstring presents as actual security boundaries, as opposed to blocked, which the same docstring explicitly says "is not a security boundary" — can be made to execute arbitrary shell commands via a single embedded newline in the command argument. Because tool arguments are LLM-generated and can be steered by content the agent processes (a fetched document, tool output, or any other text reaching the model — i.e. indirect prompt injection), this is reachable without the operator of the agent writing the malicious command themselves. With the default LocalEnvironment() backend, this is command execution on the host process running the agent, not confined to a container.

Suggested Remediation

  • Add "\n" and "\r" to _SHELL_OPERATORS in ag2/tools/sandbox/filter.py so contains_shell_operator() rejects embedded newlines the same way it rejects ;.
    • More robustly, execute allowlisted/readonly commands without invoking a shell in restricted mode (shlex.split + exec with no sh -c wrapper), so redirection, chaining, substitution, and every kind of statement separator are inert by construction instead of relying on an enumerated denylist.

Environment Tested

  • Repository: github.com/ag2ai/ag2, commit a05ff1cb6ff4955ef252a4e0d4a27d3a32ad57f0 (main), cloned 2026-09-21.
    • PyPI package ag2, version 1.0.5 per pyproject.toml on this commit.
    • Python 3, stdlib only (subprocess, importlib) against the real, unmodified ag2/tools/sandbox/filter.py — no AG2 runtime dependencies required to reproduce.

Affected Packages

2 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIag2≥ 0.13.2&&< 1.1.11.1.1pip install --upgrade 'ag2==1.1.1'
🐍PyPIautogen≥ 0.13.2No fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for ag2, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update ag2 to 1.1.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-113663 is resolved across your whole dependency graph.

  3. Workarounds

    Stop passing untrusted input into the interpreter or shell: call the affected binary with an argument array rather than a composed command string, reject anything outside a strict allowlist of expected values, and run the component under an account that cannot reach beyond the work it legitimately does.

Frequently Asked Questions

### Summary SandboxShellTool's restricted modes — allowed=[...] (explicit command allowlist) and readonly=True (implicit READONLY_COMMANDS allowlist) — can both be bypassed by embedding a newline character inside the command string. The filter only inspects a single-line set of shell metacharacters, but execution runs the raw string through `sh -c`, where a shell treats an embedded newline exactly like a `;` statement separator. An agent (or content that steers the agent, e.g. via prompt injection) can therefore run arbitrary shell commands even when the tool was configured to allow only a spe
O3 Security · Impact-Aware SCA

Is CVE-2026-113663 in your dependencies?

Find it across PyPI, including transitive dependencies.

CVE-2026-113663: ag2 — Fixed in 1.1.1