Skip to content

VNX-641: Improper Restriction of Names for Files and Other Resources

Detects upload file names classified by something other than their final extension: the extension taken from the first dot (split('.')[1], indexOf('.'), explode('.')[1]) or an allowlist tested as a substring ('.png' in name, includes('.jpg'), contains(".pdf"), unanchored regexes), so shell.php.jpg passes. Java, JavaScript/TypeScript, Python and PHP.

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

Overview

VNX-641 maps to CWE-641: Improper Restriction of Names for Files and Other Resources. A file name is restricted by the part of it that decides how the server treats the file: the text after the last dot. Code that looks anywhere else lets a double extension through: avatar.jpg.php contains .jpg, and shell.png.php has png after its first dot, yet both are stored and served as PHP.

Reported, on a file-name expression (filename, fileName, fname, originalname, f.filename, file.name, getOriginalFilename(), getSubmittedFileName(), $_FILES[...]['name'], or a variable assigned straight from one in the 15 lines above):

  • The extension taken from the first dot: Python split('.')[1], split('.', 1)[1], partition('.'); JavaScript and Java split('.')[1] / split("\\.")[1] and substring/substr/slice from indexOf('.'); PHP explode('.', $name)[1], substr($name, strpos($name, '.')), strstr($name, '.').
  • An allowlist tested as a substring: Python '.png' in name or any(ext in name for ext in ALLOWED); JavaScript includes('.jpg') or indexOf('.jpg') !== -1; Java contains(".pdf") or indexOf(".pdf") >= 0; PHP strpos/stripos/strstr($name, '.jpg'); and an extension regex with no $ anchor in JavaScript .test()/.match() or PHP preg_match().

What Gets Flagged

ext = f.filename.split(".")[1].lower()          # FLAGGED: first dot
if ".png" in f.filename:                        # FLAGGED: substring
if (file.originalname.includes('.jpg')) return cb(null, true);   // FLAGGED
if (/\.(pdf|docx)/i.test(file.originalname)) return cb(null, true);  // FLAGGED: no $ anchor
$ext = explode('.', $name)[1];                  // FLAGGED

What Is Not Flagged

  • The final extension: rsplit('.', 1), split('.')[-1], split('.').pop(), lastIndexOf('.'), os.path.splitext, path.extname, pathinfo, FilenameUtils.getExtension.
  • endswith/endsWith checks and $-anchored patterns.
  • The same calls on values that are not file names (versions, host names, e-mail addresses, JWTs), comments and string literals.

Storing an upload under the client’s name is VNX-66; a case-sensitive extension check is VNX-178.

Remediation

Take the final extension, lower-case it, compare it with an allowlist, and store the file under a generated name:

ext = os.path.splitext(f.filename)[1].lower()
if ext not in {".png", ".jpg", ".gif"}:
    abort(400)
f.save(os.path.join(UPLOADS, f"{uuid.uuid4().hex}{ext}"))

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-641
// nosec vnx-641

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

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