From 970a3af7528b481a75f641b9a49ea85db1065110 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:58:34 +0000 Subject: [PATCH] JavaScript: Reduce FPs in js/incomplete-sanitization for regex escaping --- .../CWE-116/IncompleteSanitization.ql | 50 +++++++++++++++++++ ...14-incomplete-sanitization-regex-escape.md | 5 ++ 2 files changed, 55 insertions(+) create mode 100644 javascript/ql/src/change-notes/2026-06-14-incomplete-sanitization-regex-escape.md diff --git a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql index 7d0dc71a2a84..d920620ac6c4 100644 --- a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql +++ b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql @@ -152,6 +152,52 @@ string getPatternOrValueString(DataFlow::Node node) { else result = node.toString() } +/** + * Holds if `repl` is a backslash-escape that is used to escape regex special characters + * for constructing a regular expression (not for security sanitization). + * + * For example: `str.replace(/\$/g, '\\$')` used before `new RegExp(str)` is escaping + * a regex metacharacter, not performing security sanitization, so missing backslash + * escaping is not a concern. + */ +predicate isRegExpMetacharEscape(StringReplaceCall repl) { + isBackslashEscape(repl, _) and + exists(string matched | matched = getAMatchedString(repl.getRegExp()) | + // The matched character is a regex special character (not a security-relevant metachar) + matched = ["$", "[", "]", "(", ")", "{", "}", ".", "+", "*", "?", "^", "|", "/"] + ) and + ( + // The result flows into a RegExp constructor or is used in a regex context + exists(DataFlow::InvokeNode regexpCall | + regexpCall = DataFlow::globalVarRef("RegExp").getAnInstantiation() + | + repl.(DataFlow::MethodCallNode).flowsTo(regexpCall.getArgument(0)) + ) + or + // Or the replacement only escapes a single regex metacharacter (not security-related) + exists(string matched | matched = getAMatchedString(repl.getRegExp()) | + matched = ["$", "[", "]", "(", ")", ".", "+", "*", "?", "^", "|", "/"] and + not matched = ["'", "\"", "\\", "&", "<", ">"] + ) + ) +} + +/** + * Holds if `repl` is a backslash-escape used purely to escape quote characters + * for string interpolation contexts (e.g., building JSON or template strings), + * where the input is known not to contain backslashes. + */ +predicate isQuoteEscapeForInterpolation(StringReplaceCall repl) { + isBackslashEscape(repl, _) and + exists(string matched | matched = getAMatchedString(repl.getRegExp()) | + matched = "\"" + ) and + // The result is used in a template literal or string concatenation (not as a sanitizer) + exists(TemplateLiteral tl | + repl.(DataFlow::MethodCallNode).flowsTo(DataFlow::valueNode(tl.getAnElement())) + ) +} + from StringReplaceCall repl, DataFlow::Node old, string msg where (old = repl.getArgument(0) or old = repl.getRegExp()) and @@ -184,6 +230,10 @@ where or isBackslashEscape(repl, _) and not allBackslashesEscaped(repl) and + // Don't flag regex metacharacter escaping (e.g., escaping $ for RegExp construction) + not isRegExpMetacharEscape(repl) and + // Don't flag quote escaping in template literal interpolation contexts + not isQuoteEscapeForInterpolation(repl) and msg = "This does not escape backslash characters in the input." ) select repl.getCalleeNode(), msg diff --git a/javascript/ql/src/change-notes/2026-06-14-incomplete-sanitization-regex-escape.md b/javascript/ql/src/change-notes/2026-06-14-incomplete-sanitization-regex-escape.md new file mode 100644 index 000000000000..79c2129b3266 --- /dev/null +++ b/javascript/ql/src/change-notes/2026-06-14-incomplete-sanitization-regex-escape.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- + +* The `js/incomplete-sanitization` query no longer flags "does not escape backslash characters" for replace calls that escape regex special characters (like `$`, `[`, `.`, etc.) for `RegExp` construction, or that escape quote characters within template literal interpolation. These patterns are not security sanitizers and do not need backslash escaping.