Skip to content

VNX-186: Overly Restrictive Regular Expression

Reports denylist filters for script, markup or SQL injection tokens (<script, javascript:, onerror, UNION) that match case-sensitively, so <SCRIPT> or JavaScript: slips through: removals with an empty replacement, regexes without a case-insensitive flag, and membership tests of a bare token, in Python, JavaScript/TypeScript, Java, PHP and Ruby.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-186
Severity Medium
SARIF level warning
Kind sast
Languages Java, JavaScript / TypeScript, Python, PHP, Ruby
CWE CWE-186
CAPEC CAPEC-79
ATT&CK T1190
CWSS vector CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags regex validation denylist xss cwe-186
Source internal/sast/rules/vnx-186.rego

Overview

VNX-186 maps to CWE-186: Overly Restrictive Regular Expression. A filter that is meant to catch a dangerous value but only matches one spelling of it lets every other spelling through. The rule reports denylists for script, markup and SQL injection tokens that are matched case-sensitively: HTML, JavaScript URLs and SQL keywords are all case-insensitive, so <SCRIPT>, JaVaScRiPt: and UNION SELECT pass a filter written for the lower-case form.

What Gets Flagged

A pattern or literal naming <script, </script, javascript:, vbscript:, onerror, onload, onmouseover, <iframe, <svg, <img, <object, <embed, union, drop table, sleep( or benchmark(, used as:

  • a removal with an empty replacement (.replace, .replaceAll, .gsub, .sub, .delete, str_replace);
  • a regular expression with no case-insensitive flag (re.sub/re.search/re.compile without re.I, a JavaScript or Ruby /.../ literal or new RegExp without i, Pattern.compile without CASE_INSENSITIVE, preg_replace/preg_match without the i modifier);
  • a membership test of a bare token ("<script" in value, .includes("<script"), .contains("union select"), strpos($s, "<script")).
name = name.replace("<script>", "").replace("</script>", "")      # flagged
comment = re.sub(r"<script.*?>.*?</script>", "", request.form["c"])  # flagged
$name = str_replace('<script>', '', $name);                          // flagged
$msg = preg_replace('/<script(.*?)>/', '', $_POST['msg']);            // flagged
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/g, '');      // flagged
return !/^javascript:/.test(u);                                       // flagged

Not flagged: (?i), re.I/re.IGNORECASE, the i flag, Pattern.CASE_INSENSITIVE, str_ireplace/stripos, a subject lower-cased first (value.lower()), a replacement that inserts content (template assembly), regex extraction (.match, .exec, findall), a check for one exact payload, test code, minified files and comments.

Remediation

  1. Encode output for its context (HTML-escape, attribute-escape, URL-encode) or use a vetted sanitiser such as DOMPurify, bleach or OWASP Java HTML Sanitizer, instead of stripping tokens.
  2. For SQL, use parameterised queries; never filter keywords.
  3. If a denylist must stay as defence in depth, match case-insensitively and loop until the input stops changing.
import html
safe = html.escape(request.args.get("name", ""))

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

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

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