diff --git a/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql b/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql index 1057f9ccca50..3e18cfba3caf 100644 --- a/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql +++ b/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql @@ -47,10 +47,23 @@ import MissingRegExpAnchor::Make from DataFlow::Node nd, string msg where - isUnanchoredHostnameRegExp(nd, msg) - or - isSemiAnchoredHostnameRegExp(nd, msg) - or - hasMisleadingAnchorPrecedence(nd, msg) -// isLineAnchoredHostnameRegExp is not used here, as it is not relevant to JS. + ( + isUnanchoredHostnameRegExp(nd, msg) + or + isSemiAnchoredHostnameRegExp(nd, msg) + or + hasMisleadingAnchorPrecedence(nd, msg) + ) and + // Exclude patterns used with .test() where unanchored alternatives are simple words + // (no dots), indicating intentional partial matching for role/type checks, not URL validation + not exists(DataFlow::MethodCallNode testCall | + testCall.getMethodName() = "test" and + nd.(DataFlow::RegExpCreationNode).getARegExpObject().flowsTo(testCall.getReceiver()) and + forall(RegExpTerm alt | + alt = nd.(DataFlow::RegExpCreationNode).getRoot().(RegExpAlt).getAChild() and + not alt.getAChild*() instanceof RegExpAnchor + | + not alt.getAChild*().(RegExpConstant).getValue().matches("%.%") + ) + ) select nd, msg diff --git a/javascript/ql/test/query-tests/Security/CWE-020/MissingRegExpAnchor/tst-intentional-test.js b/javascript/ql/test/query-tests/Security/CWE-020/MissingRegExpAnchor/tst-intentional-test.js new file mode 100644 index 000000000000..86fead75de53 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-020/MissingRegExpAnchor/tst-intentional-test.js @@ -0,0 +1,10 @@ +(function() { + // GOOD: simple word alternation used with .test() for role checking + // These are intentional partial matches, not URL validation + var rolePattern = /^admin|user|guest/; + if (rolePattern.test(role)) { /* ... */ } + + // BAD: hostname pattern with misleading anchor precedence + var urlCheck = /^https?:\/\/good.com|https?:\/\/evil.com/; // $ Alert + if (urlCheck.test(url)) { /* ... */ } +});