VNX-1230: Exposure of Sensitive Information Through Metadata
Reports a handler that stores a user-uploaded image byte for byte, so the EXIF block it carries (GPS coordinates, device make and serial, capture time, sometimes the owner's name) is published with it when the image is served. Reported, in a file that handles images (an image/ MIME check, an image extension, or avatar/photo/picture naming) and never re-encodes or strips them (no sharp, jimp, gm, Pillow Image.open/ImageOps, piexif, exif or strip-metadata call in the file; Python's str.strip() does not count, and comment lines are ignored when judging image context): Node fs.writeFile/writeFileSync of an uploaded buffer (req.file.buffer, or a variable taken from req.file/req.files or a .buffer), express-fileupload .mv() on a req.files entry, multer({ dest }) and multer.diskStorage(); Python Flask request.files[...] .save() (directly, through a variable, or in a for loop over request.files.getlist()), Django default_storage/FileSystemStorage .save() of a request.FILES entry, and a chunks() loop copying one to disk. Not reported: images decoded and re-encoded or stripped in the same file, uploads in files with no image context (documents, CSV), comments and test files. Stripping performed in another module is not visible to this line-oriented rule.
| Field | Value |
|---|---|
| Rule ID | VNX-1230 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | JavaScript / TypeScript, Python |
| CWE | CWE-1230 |
| 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 | metadata-exposure cwe-1230 |
| Source | internal/sast/rules/vnx-1230.rego |
Overview
VNX-1230 maps to CWE-1230: Exposure of Sensitive Information Through Metadata. Reports a handler that stores a user-uploaded image byte for byte, so the EXIF block it carries (GPS coordinates, device make and serial, capture time, sometimes the owner’s name) is published with it when the image is served. Reported, in a file that handles images (an image/ MIME check, an image extension, or avatar/photo/picture naming) and never re-encodes or strips them (no sharp, jimp, gm, Pillow Image.open/ImageOps, piexif, exif or strip-metadata call in the file; Python’s str.strip() does not count, and comment lines are ignored when judging image context): Node fs.writeFile/writeFileSync of an uploaded buffer (req.file.buffer, or a variable taken from req.file/req.files or a .buffer), express-fileupload .mv() on a req.files entry, multer({ dest }) and multer.diskStorage(); Python Flask request.files[…] .save() (directly, through a variable, or in a for loop over request.files.getlist()), Django default_storage/FileSystemStorage .save() of a request.FILES entry, and a chunks() loop copying one to disk. Not reported: images decoded and re-encoded or stripped in the same file, uploads in files with no image context (documents, CSV), comments and test files. Stripping performed in another module is not visible to this line-oriented rule.
Why This Matters
This weakness class (Exposure of Sensitive Information Through Metadata) creates a concrete exploit surface: the rule searches for the concrete source-level patterns most commonly associated with CWE-1230 and surfaces them for review. Each finding should be evaluated in context, the rule catches the pattern, not the context.
What Gets Flagged
const buffer = req.file?.buffer
if (!kind.mime.startsWith('image')) return res.sendStatus(415)
await fs.writeFile(`public/images/avatars/${id}.${kind.ext}`, buffer) // FLAGGED
const clean = await sharp(req.file.buffer).rotate().toBuffer() // re-encoded:
await fs.writeFile(target, clean) // not flagged
# Python / Flask
f = request.files["avatar"]
f.save(os.path.join("static/avatars", name)) # FLAGGED
Remediation
- Review each flagged line and determine whether the pattern represents a real instance of CWE-1230 or a false positive.
- Replace the flagged construct with a documented safe alternative appropriate to your language and framework.
- For confirmed false positives, add a
# vulnetix-ignore: VNX-1230comment on the line. - Ensure equivalent test coverage exists to prevent regression.
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-1230
// nosec vnx-1230
Or record an ignore rule: vulnetix ignore add --rule vnx-1230 --reason "...". See Suppressing findings.