Skip to content

VNX-1321: Prototype pollution through unguarded computed-key assignment

Detects JavaScript/TypeScript writes through a key an attacker can set to __proto__ or constructor: hand-written recursive merges, set-by-path walks, and nested assignments keyed by request data, with no key guard.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-1321
Severity High
SARIF level error
Kind sast
Languages JavaScript / TypeScript
CWE CWE-1321
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags prototype-pollution cwe-1321
Source internal/sast/rules/vnx-1321.rego

Overview

VNX-1321 maps to CWE-1321: Improperly Controlled Modification of Object Prototype Attributes (‘Prototype Pollution’). When code writes target[key] for a key taken from untrusted data, the key __proto__ (or constructor then prototype) makes the write land on Object.prototype, and every object in the process inherits the injected property: authorisation flags, template options, child-process options.

The rule reports three shapes:

  1. A recursive merge or extend: inside a for...in, for...of Object.keys/getOwnPropertyNames/entries(...) or Reflect.ownKeys(...) or Object.keys(...).forEach loop over a source object, a call that passes target[key] together with source[key] (deepMerge(target[key], source[key]), extend(dst[k] || {}, src[k]), Object.assign(t[k], s[k])).
  2. A set-by-path walk: a loop that walks cur = cur[part] followed within a few lines by cur[...] = value.
  3. A nested assignment keyed by request data: obj[A][B] = v or obj[A].x = v whose first key comes from req.body/query/params/cookies/headers, ctx.query or ctx.request.body, directly or through a variable assigned from it in the 10 lines above.

Not reported: code whose surrounding 15 lines name __proto__, constructor or prototype, use Object.create(null) or a Map, or call a safe-key check; a shallow one-level copy; nested assignments with constant keys or loop indices; comments and strings. hasOwnProperty is deliberately not a guard: JSON.parse gives an own __proto__ key. Library merges called with request data are VNX-NODE-006.

What Gets Flagged

function deepMerge(target, source) {
  for (const key in source) {
    if (isObject(source[key])) {
      if (!target[key]) target[key] = {};
      deepMerge(target[key], source[key]);   // FLAGGED
    } else {
      target[key] = source[key];
    }
  }
}
router.post('/settings', (req, res) => {
  store[req.body.section][req.body.key] = req.body.value;   // FLAGGED
});

What Is Not Flagged

const UNSAFE = new Set(['__proto__', 'constructor', 'prototype']);
for (const key in source) {
  if (UNSAFE.has(key)) continue;
  deepMerge(target[key], source[key]);
}

Remediation

  1. Skip __proto__, constructor and prototype keys in every merge, extend and set-by-path helper.
  2. Merge into Object.create(null) objects, or use a Map for data keyed by user input.
  3. Validate request-supplied keys against an allowlist before using them as property names.
  4. Consider Object.freeze(Object.prototype) at startup as defence in depth.

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

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

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