Skip to content

VNX-646: Uploaded file accepted or classified by its client-supplied name or extension

Reports upload handlers (multer, formidable, express-fileupload, Flask/Werkzeug, FastAPI, Django request.FILES and FileExtensionValidator) that decide on a file from the extension of the name the client sent, in a file that never inspects the content.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-646
Severity Medium
SARIF level warning
Kind sast
Languages JavaScript / TypeScript, Python
CWE CWE-646
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags file-upload file-extension cwe-646
Source internal/sast/rules/vnx-646.rego

Overview

VNX-646 maps to CWE-646: Reliance on File Name or Extension of Externally-Supplied File. It reports a condition, return or membership test that looks at the extension of an uploaded file’s client-supplied name (originalname, originalFilename, req.files.x.name, .filename), directly or through a variable derived from it, when nothing in the same file checks the file’s content.

Why This Matters

The client picks the file name. shell.php.jpg, an executable renamed to invoice.pdf or an HTML page called avatar.png passes an extension check. If the server then stores, serves or parses the file according to that name, the attacker chooses how it is treated.

What Gets Flagged

if (!/\.(jpe?g|png|gif)$/i.test(file.originalname)) { return cb(new Error('images only')); }

const ext = path.extname(req.file.originalname).toLowerCase();
if (!['.png', '.jpg'].includes(ext)) { ... }
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS

validators=[FileExtensionValidator(allowed_extensions=["pdf", "docx"])]

Not flagged: the same checks in a file that also sniffs the content (fileTypeFromBuffer, magic.from_buffer, Image.open(...).verify()), building a storage name such as Date.now() + path.extname(file.originalname), and extension checks on paths that are not uploads.

Remediation

  1. Determine the type from the content: magic bytes (file-type, python-magic) or a decoder that rejects anything else (Pillow verify()).
  2. Store uploads under a server-generated name and serve them with a server-chosen Content-Type and Content-Disposition: attachment.
  3. Keep extension allow-lists as a usability check, never as the security check.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-646
// nosec vnx-646

Or record an ignore rule: vulnetix ignore add --rule vnx-646 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-646.rego, cli/internal/sast/docs/vnx-646.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.