Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
4381d24
chore(core): Add passThrough rules for Spring DeferredResult
misonijnik May 4, 2026
7b29007
chore(core): Move DeferredResult passThrough rules to spring-web jar-…
misonijnik May 4, 2026
af0d922
feat: add new xss sinks rules
misonijnik Apr 13, 2026
710594c
test: add tests for xss sinks
misonijnik Apr 13, 2026
e240a53
docs: Spring XSS rule dynamic verification design
misonijnik Apr 16, 2026
97f4532
docs: plan for Spring XSS rule dynamic verification
misonijnik Apr 16, 2026
65c29c3
docs: record Appendix A runtime verdict table for Spring XSS matrix
misonijnik Apr 16, 2026
2925850
docs: record Appendix B raw-vs-generic ResponseEntity probe outcome
misonijnik Apr 16, 2026
ea42d90
test: cover 21 Spring XSS sink variants verified dynamically
misonijnik Apr 16, 2026
baf29ad
feat: discriminate non-HTML produces in Spring XSS sink
misonijnik Apr 17, 2026
da5c86d
docs: record Appendix D post-rule-update matrix
misonijnik Apr 17, 2026
c02eb73
docs: add Appendix A.2 — real-browser re-verification
misonijnik Apr 17, 2026
e1bed2f
feat: honest TP/FP labels + extended non-HTML content type coverage
misonijnik Apr 17, 2026
89ce902
docs: extend Appendix D/E with honest gap matrix
misonijnik Apr 17, 2026
9296a67
Improve XSS rules: programmatic safe-CT sanitizers, @RestController, …
misonijnik Apr 28, 2026
2d3ea58
Fix metavariable binding in XSS pattern-(not)-inside and sanitizer focus
misonijnik Apr 28, 2026
28d92bf
Rewrite XSS sanitizers to assignment-form / multi-statement style
misonijnik Apr 28, 2026
15cc25f
Compact XSS sinks; add engine-limitation regression tests
misonijnik Apr 29, 2026
c989ce4
Rewrite XSS html-response sinks to single-pattern shape
misonijnik Apr 29, 2026
649ec82
Add regression tests for two more engine issues
misonijnik Apr 29, 2026
ca04e58
Add minimal repro for taint-through-concat in `return` sinks
misonijnik Apr 29, 2026
b644a9e
Refactor Spring XSS html-response sink with typed return types
misonijnik Apr 30, 2026
3b22909
Collapse XSS sink negatives via grouped pattern-not-inside + metavari…
misonijnik May 3, 2026
7530e66
Collapse Spring XSS Branch 2 (response writer) into pattern-inside + …
misonijnik May 3, 2026
77b5634
Fix Spring XSS FNs: @ExceptionHandler source coverage + simpler sink …
misonijnik May 3, 2026
b99879b
Re-enable forward-compatible ResponseEntity content-type sanitizers
misonijnik May 3, 2026
79ac406
Trim verbose comments in XSS sink rules
misonijnik May 3, 2026
961bc8e
Cover DeferredResult<$T> XSS sink shape
misonijnik May 3, 2026
b82d8f5
Cover setHeader/addHeader 2-arg via metavar-inside-quotes form
misonijnik May 3, 2026
1149863
Merge DeferredResult sink branch into Branch 1
misonijnik May 3, 2026
c5665bd
Pair return-type shapes with their dedicated sink statements
misonijnik May 3, 2026
f1a34ec
Tighten Spring XSS Branch 1 to body-typed return types only
misonijnik May 3, 2026
2f57a76
Use NOTE severity for XSS sink rules and example
misonijnik May 3, 2026
46b27b8
Remove docs/specs and docs/superpowers
misonijnik May 3, 2026
ee769f9
Rename potential-xss rule IDs to response-injection
misonijnik May 3, 2026
9a1352a
Extend servlet HTML-response XSS sink to broad sink shapes
misonijnik May 3, 2026
ea89d37
Use metavariable-pattern for setContentType safe-CT exclusion
misonijnik May 3, 2026
7393aca
Drop chained-writer alternatives from Spring XSS HTML-response Branch 2
misonijnik May 3, 2026
7365b9a
Reformat sendError sink as block scalar in spring-xss-sinks
misonijnik May 3, 2026
2fe2774
Drop @ExceptionHandler coverage from spring XSS rules and tests
misonijnik May 3, 2026
5b6b7e8
Disable known-engine-FP negative samples in spring/servlet XSS tests
misonijnik May 3, 2026
fead47d
Remove issue 98-103 sample tests
misonijnik May 4, 2026
a67651e
XSS rules: tighten Spring sink with type-arg discrimination and @Rest…
misonijnik May 4, 2026
1088eba
Fix XSS rule parse errors, add builder-chain HTML branch
misonijnik May 4, 2026
e3faa19
clean: Remove unrelated changes
misonijnik May 4, 2026
dafaf6a
XSS rules: scope @ResponseBody exclusion to String return shape
misonijnik May 4, 2026
4c0d8e3
ci: Bump OWASP analyzer expected trace count to 3835
misonijnik May 4, 2026
a881551
XSS rules: cover raw `ResponseEntity` return shape in Spring sink
misonijnik May 5, 2026
7340b62
XSS rules: drop @ResponseBody exclusion on Spring String return sink
misonijnik May 5, 2026
87d8f35
XSS rules: subtract parameterized returns from raw ResponseEntity sink
misonijnik May 5, 2026
9acc2da
XSS rules: hoist raw ResponseEntity branch to top-level patterns
misonijnik May 6, 2026
fe3d2e2
XSS rules: nest raw ResponseEntity branch under shared Branch 1 scope
misonijnik May 6, 2026
8324328
wip: Test the rule
misonijnik May 6, 2026
8d99d4a
wip: extend Spring XSS sink coverage (Resource family, raw, async)
misonijnik May 7, 2026
9b624a8
XSS rules: drop ResponseEntity<?> / wrapper wildcard branches
misonijnik May 7, 2026
36cbb8d
XSS rules: drop dead Branch 1d (raw ResponseEntity)
misonijnik May 7, 2026
564aa92
XSS rules: trim comments and consolidate Spring XSS tests into the ru…
misonijnik May 8, 2026
4c3c09e
XSS rules: restore raw ResponseEntity positive test
misonijnik May 8, 2026
f1c16e9
XSS rules: re-engineering
misonijnik May 8, 2026
e35063f
XSS rules: fix Branch 1c scoping and rule-load typo
misonijnik May 8, 2026
fb2cd55
XSS rules: restore response-injection-in-servlet-app firing
misonijnik May 8, 2026
9aafe1f
XSS rules: subtract non-HTML builder-chain content types
misonijnik May 12, 2026
5879faa
XSS rules: catch ResponseEntity.ok(body) convenience overload
misonijnik May 12, 2026
e32b1fb
engine test: pattern-not-inside ignored for chained constrained-arity…
misonijnik May 12, 2026
0256f78
issue 98: tighten engine test to wire metavariables through positive …
misonijnik May 12, 2026
ad77cd9
issue 98: narrow scope to metavariable-pattern-narrowed pattern-not-i…
misonijnik May 12, 2026
52d3e2e
issue 98: drop the Builder type qualifier from pattern-inside
misonijnik May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-analyzer-owasp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ concurrency:
cancel-in-progress: true

env:
EXPECTED_TRACES: 3011
EXPECTED_TRACES: 3835

jobs:
owasp:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package issues.i98;

public class Builder {
public void cfg(String key, String value) { }

public void sink(String taint) { }

public static String source() { return "oops"; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package issues;

import base.RuleSample;
import base.RuleSet;
import issues.i98.Builder;

/**
* Engine bug: a `pattern-not-inside` whose discriminator-arg metavariable is
* narrowed by a sibling `metavariable-pattern` is silently ignored.
*
* The rule (issue98.yaml):
* - binds `$X` via `pattern-inside: Builder $X = new Builder(); ...`
* - binds `$UNTRUSTED` via the positive sink `$X.sink($UNTRUSTED)`
* - subtracts via `pattern-not-inside: $X.cfg("Content-Type", $V)` plus
* `metavariable-pattern: $V matches "application/json"`
*
* Every metavariable in the pattern-not-inside is bound: `$X` from
* `pattern-inside`, `$V` constrained by `metavariable-pattern`. With the
* literal-arg form `pattern-not-inside: $X.cfg("Content-Type",
* "application/json")` (no metavariable-pattern) the engine correctly
* subtracts the match — this test passes. Replacing the literal arg with a
* metavariable + `metavariable-pattern` constraint (the same shape the XSS
* rule uses for `$CT_SAFE`) loses the subtraction.
*
* Expected:
* - PositiveStmtNoCfg fires (no `.cfg` call in scope).
* - NegativeStmtWithCfg is subtracted (`.cfg("Content-Type",
* "application/json")` matches the pattern-not-inside, and
* `metavariable-pattern` accepts the literal).
*
* Actual: NegativeStmtWithCfg also fires.
*
* Motivation: matches the `(HttpServletResponse $R).setContentType($CT_SAFE); …`
* + `metavariable-pattern: $CT_SAFE ∈ {non-HTML strings}` subtractor in
* `servlet-xss-html-response-sinks.yaml` that fails to clear the
* `SafeChainedWriterJsonServlet` / `SafeOutputStreamOctetServlet` FPs.
*/
@RuleSet("issues/issue98.yaml")
public abstract class issue98 implements RuleSample {
/** No discriminator call in scope — sink should fire. */
static class PositiveStmtNoCfg extends issue98 {
@Override
public void entrypoint() {
Builder b = new Builder();
b.sink(Builder.source());
}
}

/**
* Discriminator `b.cfg("Content-Type", "application/json")` is present in
* the method body. The pattern-not-inside + metavariable-pattern pair
* should subtract this match — but does not. Disabled until the engine
* honors metavariable-pattern-narrowed arguments inside
* pattern-not-inside.
*/
static class NegativeStmtWithCfg extends issue98 {
@Override
public void entrypoint() {
Builder b = new Builder();
b.cfg("Content-Type", "application/json");
b.sink(Builder.source());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
rules:
- id: i98
message: >
pattern-not-inside whose discriminator-arg metavariable is narrowed by
a sibling metavariable-pattern is silently ignored. Replacing the
metavariable with the equivalent literal makes the subtraction work.
See issue98.java for the demonstration and the motivating XSS rule
context (`servlet-xss-html-response-sinks.yaml` subtractor on
`(HttpServletResponse $R).setContentType($CT_SAFE); ...` plus
`metavariable-pattern: $CT_SAFE ∈ {non-HTML strings}`).
severity: ERROR
languages:
- java
mode: taint
pattern-sources:
- pattern: source()
pattern-sinks:
- patterns:
- pattern-inside: |
$X = new Builder();
...
- patterns:
- pattern-not-inside: $X.cfg("Content-Type", $V)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is important to use "$V" in quotes

- metavariable-pattern:
metavariable: $V
patterns:
- pattern-either:
- pattern: '"application/json"'
- pattern: $X.sink($UNTRUSTED)
- focus-metavariable: $UNTRUSTED
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import issues.issue94
import issues.issue95
import issues.issue96
import issues.issue97
import issues.issue98
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.TestInstance
Expand Down Expand Up @@ -105,6 +106,10 @@ class IssuesTest : SampleBasedTest() {
@Test
fun `issue 97`() = runTest<issue97>()

@Test
@Disabled // todo: pattern-not-inside ignored when its discriminator metavariable is narrowed by a sibling metavariable-pattern
fun `issue 98`() = runTest<issue98>()

@AfterAll
fun close() {
closeRunner()
Expand Down
108 changes: 108 additions & 0 deletions rules/ruleset/java/lib/generic/servlet-xss-html-response-sinks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
rules:
- id: java-servlet-xss-html-response-sink
options:
lib: true
severity: NOTE
message: Direct write of unvalidated user input into a response without safe content type
metadata:
provenance:
- https://github.com/github/codeql/blob/main/java/ql/lib/semmle/code/java/security/XSS.qll
languages:
- java
mode: taint

# Servlet defaults Content-Type to text/html — writer/OutputStream is XSS
# unless setContentType pins non-HTML. Spring sibling for raw HttpServlet handlers.

pattern-sanitizers:
- patterns:
- pattern-either:
- pattern: Encode.forHtml(..., $UNTRUSTED, ...)
- pattern: (PolicyFactory $POLICY).sanitize(..., $UNTRUSTED, ...)
- pattern: (AntiSamy $AS).scan(..., $UNTRUSTED, ...)
- pattern: JSoup.clean(..., $UNTRUSTED, ...)
- pattern: HtmlUtils.htmlEscape(..., $UNTRUSTED, ...)
- pattern: org.apache.commons.lang.StringEscapeUtils.escapeHtml(..., $UNTRUSTED, ...)
- pattern: org.apache.commons.text.StringEscapeUtils.escapeHtml3(..., $UNTRUSTED, ...)
- pattern: org.apache.commons.text.StringEscapeUtils.escapeHtml4(..., $UNTRUSTED, ...)
- pattern: org.owasp.esapi.ESAPI.encoder().encodeForHTML(..., $UNTRUSTED, ...)
- focus-metavariable: $UNTRUSTED

pattern-sinks:
- patterns:
- patterns:
- pattern-either:
- pattern-inside: |
$RETURNTYPE $ENTRYPOINT(..., javax.servlet.http.HttpServletResponse $RESPONSE, ...) {
...
}
- pattern-inside: |
$RETURNTYPE $ENTRYPOINT(..., jakarta.servlet.http.HttpServletResponse $RESPONSE, ...) {
...
}
- metavariable-pattern:
metavariable: $ENTRYPOINT
pattern-either:
- pattern: doDelete
- pattern: doGet
- pattern: doPost
- pattern: doPut
- pattern: doTrace
- pattern: _jspService
- pattern-either:
- patterns:
- pattern-either:
- pattern-inside: |
$W = (javax.servlet.http.HttpServletResponse $RESPONSE).getWriter(...);
...
- pattern-inside: |
$W = (jakarta.servlet.http.HttpServletResponse $RESPONSE).getWriter(...);
...
- pattern: |
$W.$WRITE(..., $UNTRUSTED, ...);
- patterns:
- pattern-either:
- pattern-inside: |
$S = (javax.servlet.http.HttpServletResponse $RESPONSE).getOutputStream(...);
...
- pattern-inside: |
$S = (jakarta.servlet.http.HttpServletResponse $RESPONSE).getOutputStream(...);
...
- pattern: |
$S.$WRITE(..., $UNTRUSTED, ...);
- pattern: (HttpServletResponse $RESPONSE).sendError($CODE, $UNTRUSTED)
- pattern: (jakarta.servlet.jsp.JspWriter $W).$WRITE(..., $UNTRUSTED, ...)
- pattern: (javax.servlet.jsp.JspWriter $W).$WRITE(..., $UNTRUSTED, ...)

# $CT_SAFE OUTSIDE quotes binds to the whole string-literal expression;
# bare `application/json` can't be used — engine parses `/` `-` as Java operators.
- patterns:
- pattern-not-inside: |
(HttpServletResponse $RESPONSE).setContentType($CT_SAFE);
...
- metavariable-pattern:
metavariable: $CT_SAFE
patterns:
- pattern-either:
- pattern: '"application/json"'
- pattern: '"application/json;charset=UTF-8"'
- pattern: '"text/plain"'
- pattern: '"application/pdf"'
- pattern: '"application/octet-stream"'
- pattern: '"application/xml"'
- pattern: '"text/xml"'
- pattern: '"image/png"'
- pattern: '"image/jpeg"'
- pattern: '"image/gif"'
# MediaType.X_VALUE constants — dev analyzer constant-folds; kept
# explicit for analyzer-regression / older-build robustness.
- pattern: MediaType.APPLICATION_JSON_VALUE
- pattern: MediaType.TEXT_PLAIN_VALUE
- pattern: MediaType.APPLICATION_PDF_VALUE
- pattern: MediaType.APPLICATION_OCTET_STREAM_VALUE
- pattern: MediaType.APPLICATION_XML_VALUE
- pattern: MediaType.TEXT_XML_VALUE
- pattern: MediaType.IMAGE_PNG_VALUE
- pattern: MediaType.IMAGE_JPEG_VALUE
- pattern: MediaType.IMAGE_GIF_VALUE
- focus-metavariable: $UNTRUSTED
81 changes: 55 additions & 26 deletions rules/ruleset/java/lib/generic/servlet-xss-sinks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,63 @@ rules:
- java
mode: taint
pattern-sanitizers:
- pattern-either:
- pattern: Encode.forHtml(...)
- pattern: (PolicyFactory $POLICY).sanitize(...)
- pattern: (AntiSamy $AS).scan(...)
- pattern: JSoup.clean(...)
- pattern: HtmlUtils.htmlEscape(...)
- pattern: org.apache.commons.lang.StringEscapeUtils.escapeHtml(...)
- pattern: org.apache.commons.text.StringEscapeUtils.escapeHtml3(...)
- pattern: org.apache.commons.text.StringEscapeUtils.escapeHtml4(...)
- pattern: org.owasp.esapi.ESAPI.encoder().encodeForHTML(...)
- patterns:
- pattern-either:
- pattern: Encode.forHtml(..., $UNTRUSTED, ...)
- pattern: (PolicyFactory $POLICY).sanitize(..., $UNTRUSTED, ...)
- pattern: (AntiSamy $AS).scan(..., $UNTRUSTED, ...)
- pattern: JSoup.clean(..., $UNTRUSTED, ...)
- pattern: HtmlUtils.htmlEscape(..., $UNTRUSTED, ...)
- pattern: org.apache.commons.lang.StringEscapeUtils.escapeHtml(..., $UNTRUSTED, ...)
- pattern: org.apache.commons.text.StringEscapeUtils.escapeHtml3(..., $UNTRUSTED, ...)
- pattern: org.apache.commons.text.StringEscapeUtils.escapeHtml4(..., $UNTRUSTED, ...)
- pattern: org.owasp.esapi.ESAPI.encoder().encodeForHTML(..., $UNTRUSTED, ...)
- focus-metavariable: $UNTRUSTED

pattern-sinks:
- patterns:
- pattern-either:
- pattern: |
(HttpServletResponse $RESPONSE).getWriter(...).$WRITE(..., $UNTRUSTED, ...)
- pattern: |
(HttpServletResponse $RESPONSE).getOutputStream(...).$WRITE(..., $UNTRUSTED, ...)
- pattern: |
(HttpServletResponse $RESPONSE).sendError($CODE, $UNTRUSTED)
- pattern: |
(java.io.PrintWriter $WRITER).$WRITE(..., $UNTRUSTED, ...)
- pattern: |
(PrintWriter $WRITER).$WRITE(..., $UNTRUSTED, ...)
- pattern: |
(javax.servlet.ServletOutputStream $WRITER).$WRITE(..., $UNTRUSTED, ...)
- pattern: |
(ServletOutputStream $WRITER).$WRITE(..., $UNTRUSTED, ...)
- pattern: |
(jakarta.servlet.jsp.JspWriter $WRITER).$WRITE(..., $UNTRUSTED, ...)
- patterns:
- patterns:
- pattern-either:
- pattern-inside: |
$RETURNTYPE $ENTRYPOINT(..., javax.servlet.http.HttpServletResponse $RESPONSE, ...) {
...
}
- pattern-inside: |
$RETURNTYPE $ENTRYPOINT(..., jakarta.servlet.http.HttpServletResponse $RESPONSE, ...) {
...
}
- metavariable-pattern:
metavariable: $ENTRYPOINT
pattern-either:
- pattern: doDelete
- pattern: doGet
- pattern: doPost
- pattern: doPut
- pattern: doTrace
- pattern: _jspService
- patterns:
- pattern-either:
- pattern-inside: |
$W = (javax.servlet.http.HttpServletResponse $RESPONSE).getWriter(...);
...
- pattern-inside: |
$W = (jakarta.servlet.http.HttpServletResponse $RESPONSE).getWriter(...);
...
- pattern: |
$W.$WRITE(..., $UNTRUSTED, ...);
- patterns:
- pattern-either:
- pattern-inside: |
$S = (javax.servlet.http.HttpServletResponse $RESPONSE).getOutputStream(...);
...
- pattern-inside: |
$S = (jakarta.servlet.http.HttpServletResponse $RESPONSE).getOutputStream(...);
...
- pattern: |
$S.$WRITE(..., $UNTRUSTED, ...);
- pattern: (HttpServletResponse $RESPONSE).sendError($CODE, $UNTRUSTED)
- pattern: (jakarta.servlet.jsp.JspWriter $W).$WRITE(..., $UNTRUSTED, ...)
- pattern: (javax.servlet.jsp.JspWriter $W).$WRITE(..., $UNTRUSTED, ...)
- focus-metavariable: $UNTRUSTED
Loading
Loading