diff --git a/java/ql/lib/change-notes/2026-04-04-trust-boundary-regexp-barrier.md b/java/ql/lib/change-notes/2026-04-04-trust-boundary-regexp-barrier.md new file mode 100644 index 000000000000..b80c0611b6de --- /dev/null +++ b/java/ql/lib/change-notes/2026-04-04-trust-boundary-regexp-barrier.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `java/trust-boundary-violation` query now recognizes regular expression checks (including `String.matches()` guards and `@javax.validation.constraints.Pattern` annotations) as sanitizers, consistent with the existing treatment of ESAPI validators. This reduces false positives when input is validated against a pattern before being stored in a session. diff --git a/java/ql/lib/semmle/code/java/security/TrustBoundaryViolationQuery.qll b/java/ql/lib/semmle/code/java/security/TrustBoundaryViolationQuery.qll index d234f3df20ce..78589add96dc 100644 --- a/java/ql/lib/semmle/code/java/security/TrustBoundaryViolationQuery.qll +++ b/java/ql/lib/semmle/code/java/security/TrustBoundaryViolationQuery.qll @@ -31,17 +31,26 @@ private class ExternalTrustBoundaryValidationSanitizer extends TrustBoundaryVali ExternalTrustBoundaryValidationSanitizer() { barrierNode(this, "trust-boundary-violation") } } +private class SimpleTypeTrustBoundaryValidationSanitizer extends TrustBoundaryValidationSanitizer instanceof SimpleTypeSanitizer +{ } + +private class RegexpCheckTrustBoundaryValidationSanitizer extends TrustBoundaryValidationSanitizer instanceof RegexpCheckBarrier +{ } + +private class HttpServletSessionTypeTrustBoundaryValidationSanitizer extends TrustBoundaryValidationSanitizer +{ + HttpServletSessionTypeTrustBoundaryValidationSanitizer() { + this.getType() instanceof HttpServletSession + } +} + /** * Taint tracking for data that crosses a trust boundary. */ module TrustBoundaryConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof TrustBoundaryViolationSource } - predicate isBarrier(DataFlow::Node node) { - node instanceof TrustBoundaryValidationSanitizer or - node.getType() instanceof HttpServletSession or - node instanceof SimpleTypeSanitizer - } + predicate isBarrier(DataFlow::Node node) { node instanceof TrustBoundaryValidationSanitizer } predicate isSink(DataFlow::Node sink) { sink instanceof TrustBoundaryViolationSink } diff --git a/java/ql/test/query-tests/security/CWE-501/TrustBoundaryViolations.java b/java/ql/test/query-tests/security/CWE-501/TrustBoundaryViolations.java index d676e3e96783..06e9c6cc929f 100644 --- a/java/ql/test/query-tests/security/CWE-501/TrustBoundaryViolations.java +++ b/java/ql/test/query-tests/security/CWE-501/TrustBoundaryViolations.java @@ -31,5 +31,19 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) { } } catch (Exception e) { } + + // GOOD: A direct String.matches(...) regex check constrains the input before it is written to the session. + String input4 = request.getParameter("input4"); + if (input4.matches("[a-zA-Z0-9]+")) { + request.getSession().setAttribute("input4", input4); + } + } + + @javax.validation.constraints.Pattern(regexp = "^[a-zA-Z0-9]+$") + String validatedField; + + public void doPost(HttpServletRequest request, HttpServletResponse response) { + // GOOD: The field is constrained by a @Pattern annotation. + request.getSession().setAttribute("validated", validatedField); } } diff --git a/java/ql/test/query-tests/security/CWE-501/options b/java/ql/test/query-tests/security/CWE-501/options index 37d627da7e82..15ba67d18321 100644 --- a/java/ql/test/query-tests/security/CWE-501/options +++ b/java/ql/test/query-tests/security/CWE-501/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/esapi-2.0.1:${testdir}/../../../stubs/javax-servlet-2.5 +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/esapi-2.0.1:${testdir}/../../../stubs/javax-servlet-2.5:${testdir}/../../../stubs/javax-validation-constraints