The approval gate that always said yes: OpenSRE's headless shell bypass
A vector breakdown of CVE-2026-109444 / GHSA-fqh3-76w5-5vvf (OpenSRE through 0.1.2026.10.19)
I write a lot about approval gates that check the wrong thing. This one doesn't check anything. In OpenSRE's headless mode the function that decides whether the agent may run a shell command ignores its inputs and returns True. Put that next to an agent whose job is reading alerts and Slack messages, and anyone who can get text in front of it can run commands on the host.
What the advisory actually says
From GHSA-fqh3-76w5-5vvf and the VulnCheck advisory, published October 20, 2026 at 03:32 UTC:
- Product: OpenSRE (Tracer-Cloud), an AI site-reliability agent. Affected: through 0.1.2026.10.19. No fixed release is listed.
-
Weakness: CWE-862, Missing Authorization. Severity: High, CVSS 3.1 score 8.8 (
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H). It's network-reachable, needs no privileges, and needs one "user interaction": the agent has to triage the attacker's content. - The bug: "an approval gate bypass vulnerability that allows attackers to execute shell_run commands without confirmation because the headless presenter's execution_allowed() always returns True."
- Impact: "Attackers can inject instructions into alerts or Slack messages triaged by the agent to run commands on the host and exfiltrate LLM, Slack and cloud credentials."
- Credit: VulnCheck credits Noy Pearl, Michael Sromin and Ophir Dror.
The advisory links two lines in the source, and both are worth reading.
The vector: untrusted text in, shell commands out
- An attacker gets text in front of the agent. The advisory names two routes: an alert the agent is asked to triage, or a Slack message.
- The text contains instructions. The model can't reliably tell "data I'm analyzing" from "instructions I should follow," so it decides to call
shell_run. - Before running a command, OpenSRE asks the active presenter whether execution is allowed. In the interactive terminal that's where a human confirms.
- In headless surfaces (the Slack gateway, scheduled runs, embedded use) the presenter is
HeadlessSubprocessPresenter. Itsexecution_allowed()takes the policy result and an action summary, discards both, and returnsTrue. - The command runs on the host with whatever the process can reach, and the advisory says that includes LLM, Slack and cloud credentials.
No authentication step is skipped. The attacker never logs in. They just write something the agent will read.
Why this class of bug exists: three root causes
The advisory covers what's wrong, not the fix. Here's the class split into causes you can look for in any agent framework.
1. Approval treated as a UI feature instead of a security control
Mechanism. The decision to run a risky action is handed to the presentation layer, the part that draws prompts and buttons. That works when there's a terminal and a human. When the same agent runs headless, the presentation layer has nobody to ask, so someone writes a stub. A stub for a print method is harmless. A stub for the permission check is the vulnerability.
Evidence. The module's own docstring describes it as the presenter "for non-TTY agent surfaces (gateway, scheduled, embedded)." Its execution_allowed() ignores the ExecutionPolicyResult it's handed and returns True. The project's commit history shows the presenter was registered in an August 2026 change (#4845) so that "the default agent can execute" shell_run. That was a reasonable fix for "the agent can't run commands," made through a component that never enforced policy.
What fixes it. The allow/deny decision belongs in the execution path, not the presenter. The presenter can show a prompt, but the policy result itself should decide whether the command runs. If a surface can't ask a human, the policy has to answer, and the safe default answer for an unreviewed shell command is no.
2. "No human available" fails open
Mechanism. Approval prompts are annoying in chat. They stall, time out and clutter threads. The tempting fix is to drop them on that surface. Unless something else takes over the gate, that turns "needs approval" into "approved."
Evidence. The second line the advisory links is in the Slack dispatcher, where a comment says: "No approval prompts in Slack for now. Button waits stalled the turn until the timeout and posted extra replies in the thread." The turn is then set up with tool_hooks=None. That's an honest usability tradeoff. It also means that in Slack the shell tool runs on the model's say-so.
What fixes it. When an approval can't be collected, fail closed: refuse the action, or queue it for a human somewhere else and tell the user it's waiting. Faster is fine for read-only tools. Anything that writes, executes or touches credentials should be denied by default on surfaces with no approver.
3. The agent reads attacker text with operator authority
Mechanism. An SRE agent exists to read noisy, untrusted input such as alerts, logs and messages from anyone in a channel. If the same model turn that reads that input can also call a shell, the attacker's text has the same pull on the model as the operator's instructions. And if the shell runs in a process holding long-lived credentials, every successful injection becomes a credential leak.
Evidence. The advisory's attack path is exactly this: instructions "inject[ed] into alerts or Slack messages triaged by the agent," leading to command execution and exfiltration of "LLM, Slack and cloud credentials." The CVSS vector requires no privileges, only that the agent processes the content.
What fixes it. Treat everything the agent triages as untrusted data. Split reading from acting: a turn that ingests external content shouldn't hold the shell tool, or its tool calls should need approval that untrusted content can't grant. Run the executor without the agent's own API keys in its environment, and give it scoped, short-lived cloud credentials.
How this connects to the other approval-gate bugs
Usually the gate exists but checks too little, such as the verb without the object or the parent agent without its sub-agents. Here the gate exists in the interactive path and is a no-op in the headless one. It's the same lesson again: an approval control is only as strong as its weakest execution surface. If you can't name who approves a shell command in Slack, nobody does.
Defensive takeaways you can check this week
-
Find out if you run it. If you have OpenSRE at or below 0.1.2026.10.19, assume headless surfaces (Slack gateway, scheduled runs, embedded use) execute
shell_runwith no confirmation. No fixed release is listed yet, so watch the advisory for one. - Stop exposing the headless path to untrusted input. Until there's a fix, don't connect the Slack gateway to channels where people outside your on-call team can post, and don't point scheduled triage at alert sources that carry text an outsider controls.
- Take the credentials away from the shell. Run the agent in a container or VM whose environment doesn't hold your LLM keys, your Slack bot token or broad cloud credentials. Use scoped, short-lived roles.
- Rotate if you were exposed. If an affected version triaged outside content in headless mode, rotate the LLM, Slack and cloud credentials it could read, and review its command history and outbound network logs.
-
Audit your own agents for the same pattern. Search for approval or
*_allowedmethods that return a constant, and for surfaces that passNonefor hooks or approvers. For each one, ask what happens when nobody is there to click. - Test the headless path. Add a test that puts a prompt-injection string in an alert or chat message and asserts the shell tool is refused, not just that a prompt was shown.
Sources
- GitHub advisory GHSA-fqh3-76w5-5vvf
- NVD entry for CVE-2026-109444
- VulnCheck advisory
- Lasso Security vulnerability database entry
- subprocess_presenter.py at the advisory's referenced commit
- Slack dispatcher at the advisory's referenced commit
If you're building agent backends and want a checklist for this kind of trust boundary, the Trust Boundary pack covers it.
This article was drafted with AI assistance and edited by a human. All facts come from the advisory, its CVE record and the source lines it references.
Top comments (0)