Skip to content

feat(security): add OWASP Java Encoder and expose via XssWebAPI viewtool#35318

Merged
dsilvam merged 10 commits into
mainfrom
feat/owasp-java-encoder-24120
Jun 5, 2026
Merged

feat(security): add OWASP Java Encoder and expose via XssWebAPI viewtool#35318
dsilvam merged 10 commits into
mainfrom
feat/owasp-java-encoder-24120

Conversation

@mbiuki
Copy link
Copy Markdown
Member

@mbiuki mbiuki commented Apr 14, 2026

Summary

Closes #24120

Integrates the OWASP Java Encoder (1.3.1) into dotCMS core as the standard context-aware output encoding library for XSS prevention, replacing legacy StringEscapeUtils and UtilMethods.encodeURL() calls.

Changes

  • bom/application/pom.xml + dotCMS/pom.xml — add org.owasp.encoder:encoder:1.3.1 under the Security section
  • Xss.java — replace StringEscapeUtils.escapeHtml() with Encode.forHtml(); replace UtilMethods.encodeURL() with Encode.forUriComponent(); add new context-specific helpers:
    • encodeForHTML(String) — HTML body content
    • encodeForHTMLAttribute(String) — quoted HTML attribute values
    • encodeForJavaScript(String) — JavaScript string literals
    • encodeForCSS(String) — CSS strings/identifiers
  • VelocityRequestWrapper.java — replace UtilMethods.htmlifyString() with Xss.encodeForHTML() in getParameter() for standards-compliant encoding of XSS-detected parameters
  • XssWebAPI.java — expose all OWASP encoder contexts to Velocity templates via $xsstool:
    • $xsstool.encodeForHTML(value)
    • $xsstool.encodeForHTMLAttribute(value)
    • $xsstool.encodeForJavaScript(value)
    • $xsstool.encodeForURL(value)
    • $xsstool.encodeForCSS(value)
    • Legacy strip, escape, escapeHTMLAttrib methods kept and marked @Deprecated

Usage in Velocity templates

## HTML body context
<p>$xsstool.encodeForHTML($request.getParameter("name"))</p>

## URL context
<a href="/search?q=$xsstool.encodeForURL($request.getParameter("q"))">Search</a>

## JavaScript context
<script>var msg = "$xsstool.encodeForJavaScript($message)";</script>

Test plan

  • ./mvnw install -pl :dotcms-core --am -DskipTests — BUILD SUCCESS
  • Verify encoding methods work in a live Velocity template for each context (HTML, URL, JS, CSS)
  • Regression: existing $xsstool.escape() / $xsstool.strip() / $xsstool.hasXss() calls continue to work

🤖 Generated with Claude Code

…ool (fixes #24120)

Integrates the OWASP Java Encoder (1.3.1) into dotCMS core as the
standard context-aware output encoding library for XSS prevention.

Changes:
- bom/application/pom.xml, dotCMS/pom.xml: add org.owasp.encoder:encoder:1.3.1
- Xss.java: replace StringEscapeUtils.escapeHtml() with Encode.forHtml();
  replace UtilMethods.encodeURL() with Encode.forUriComponent(); add new
  context-specific helpers: encodeForHTML, encodeForHTMLAttribute,
  encodeForJavaScript, encodeForCSS
- VelocityRequestWrapper.java: replace htmlifyString() with Xss.encodeForHTML()
  in getParameter() for standards-compliant output encoding
- XssWebAPI.java: expose all OWASP encoder contexts to Velocity templates via
  $xsstool — encodeForHTML, encodeForHTMLAttribute, encodeForJavaScript,
  encodeForURL, encodeForCSS; legacy strip/escape methods kept and deprecated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Apr 14, 2026
@mbiuki mbiuki requested review from DeanGonzalez and wezell April 14, 2026 20:21
@mbiuki mbiuki self-assigned this Apr 14, 2026
@mbiuki mbiuki added OKR : Security & Privacy Owned by Mehdi Team : Security Issues related to security and privacy labels Apr 14, 2026
…4120)

Covers all 5 encoding contexts (HTML, HTML attribute, JavaScript, URL, CSS),
null-safety, legacy methods, and XSS detection helpers. 22 tests, all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mbiuki
Copy link
Copy Markdown
Member Author

mbiuki commented Apr 14, 2026

Test Results

./mvnw test -pl :dotcms-core -Dtest=XssTest -Ddocker.skip=true

Running com.liferay.util.XssTest
Tests run: 22, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.658 s
BUILD SUCCESS

Tests covered

Context Test
HTML encodeForHTML_encodesScriptTag
HTML encodeForHTML_encodesAmpersandAndQuotes
HTML encodeForHTML_returnsEmptyStringForNull
HTML encodeForHTML_passesThroughPlainText
HTML Attribute encodeForHTMLAttribute_encodesDoubleQuote
HTML Attribute encodeForHTMLAttribute_returnsEmptyStringForNull
JavaScript encodeForJavaScript_encodesScriptBreakout
JavaScript encodeForJavaScript_encodesBackslash
JavaScript encodeForJavaScript_returnsEmptyStringForNull
URL encodeForURL_encodesSpaceAndSpecialChars
URL encodeForURL_encodesScriptPayload
URL encodeForURL_returnsEmptyStringForNull
URL encodeForURL_preservesUnreservedCharacters
CSS encodeForCSS_encodesQuotesAndParens
CSS encodeForCSS_returnsEmptyStringForNull
Legacy escapeHTMLAttrib_encodesHtmlEntities
Legacy escapeHTMLAttrib_returnsEmptyStringForNull
Legacy unEscapeHTMLAttrib_decodesHtmlEntities
Legacy unEscapeHTMLAttrib_returnsEmptyStringForNull
Detection URLHasXSS_detectsScriptTag
Detection URLHasXSS_returnsFalseForCleanInput
Detection URLHasXSS_returnsFalseForNull

@mbiuki mbiuki requested a review from fmontes April 14, 2026 20:26
@mbiuki mbiuki moved this to In Progress in dotCMS - Product Planning Apr 14, 2026
Copy link
Copy Markdown
Member

@wezell wezell left a comment

Choose a reason for hiding this comment

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

Also need to add the Viewtool to this pr - these files here:

https://github.com/dotcms-community/com.dotcms.owasp.encoder/tree/main/src/main/java/com/dotcms/owasp/encoder

and need to add the viewtool to the toolbox.xml so it can be used in velocity.

- Add $encode Velocity viewtool (OwaspEncoderTool) exposing full OWASP
  Java Encoder API: forHtml, forHtmlContent, forHtmlAttribute,
  forHtmlUnquotedAttribute, forCssString, forCssUrl, forUriComponent,
  forJavaScript, forJavaScriptAttribute, forJavaScriptBlock,
  forJavaScriptSource, forXml*, forCDATA, plus URL safety helpers
  (validateUrl, urlHasXSS, cleanUrl). Registered as $encode in toolbox.xml.
- Wrap VelocityRequestWrapper XSS encoding in
  USE_OWASP_ENCODING_FOR_XSS_PARAMS config flag (default true) so it
  can be reverted to legacy htmlifyString if needed.

Closes #24120

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mbiuki
Copy link
Copy Markdown
Member Author

mbiuki commented Apr 14, 2026

Addressed both review comments in the latest commit:

1. Config flag for VelocityRequestWrapper
Wrapped the encoding call in Config.getBooleanProperty("USE_OWASP_ENCODING_FOR_XSS_PARAMS", true) — defaults to OWASP encoding, set to false to revert to legacy UtilMethods.htmlifyString.

2. OwaspEncoderTool viewtool
Added dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/OwaspEncoderTool.java — a full port of the community plugin's OwaspEncoderTool, covering all encoding contexts from the OWASP Java Encoder library:

Method Context
forHtml / forHtmlContent / forHtmlAttribute / forHtmlUnquotedAttribute HTML
forCssString / forCssUrl CSS
forUriComponent / forUri (deprecated) URI
forJavaScript / forJavaScriptAttribute / forJavaScriptBlock / forJavaScriptSource JavaScript
forXml / forXmlContent / forXmlAttribute / forXmlComment / forCDATA XML
forJava Java strings
validateUrl / urlHasXSS / cleanUrl URL safety helpers

Registered in toolbox.xml as $encode at application scope. Example Velocity usage:

<p>$encode.forHtml($request.getParameter("name"))</p>
<a href="/search?q=$encode.forUriComponent($q)">Go</a>
<script>var msg = "$encode.forJavaScript($message)";</script>

Covers every encoding context exposed by the viewtool:
forHtml, forHtmlContent, forHtmlAttribute, forHtmlUnquotedAttribute,
forCssString, forCssUrl, forUriComponent, forJavaScript and its
block/attribute/source variants, forXml family, forCDATA, forJava,
plus validateUrl / urlHasXSS / cleanUrl URL-safety helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mbiuki
Copy link
Copy Markdown
Member Author

mbiuki commented Apr 14, 2026

Added OwaspEncoderToolTest.java — 47 unit tests, all green:

Tests run: 47, Failures: 0, Errors: 0, Skipped: 0

Coverage per context:

Context Tests
forHtml encodes <script>, &, null → "", plain text passthrough
forHtmlContent encodes angle brackets, null → ""
forHtmlAttribute encodes " and ', null → ""
forHtmlUnquotedAttribute encodes spaces, null → ""
forCssString / forCssUrl encodes single quotes, null → ""
forUriComponent encodes space/&/<, preserves unreserved chars, null → ""
forJavaScript encodes ' and \, null → ""
forJavaScriptAttribute encodes ', null → ""
forJavaScriptBlock encodes </script> breakout, null → ""
forJavaScriptSource encodes \, null → ""
forXml / forXmlContent / forXmlAttribute / forXmlComment / forCDATA null → ""
forJava encodes \, null → ""
validateUrl accepts valid https, rejects javascript:, null, malformed
urlHasXSS clean URL → false, XSS in param → true, invalid/null → false
cleanUrl valid URL → encoded URL, javascript:/null → null

Two tests were corrected during development: forJavaScriptBlock and forJavaScriptSource don't encode single quotes (they are safe in those contexts — no HTML parser), so they now test the actual attack vectors for their respective contexts.

@mbiuki
Copy link
Copy Markdown
Member Author

mbiuki commented Apr 15, 2026

@wezell Both changes from your review are now in place — config flag for the XSS encoding and the OwaspEncoderTool viewtool with 47 unit tests. Could you take another look when you get a chance?

Comment thread dotCMS/src/main/webapp/WEB-INF/toolbox.xml Outdated
wezell
wezell previously requested changes Apr 15, 2026
Copy link
Copy Markdown
Member

@wezell wezell left a comment

Choose a reason for hiding this comment

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

minor change needed - we should name the velocity tool $owasp

Comment thread dotCMS/src/main/java/com/liferay/util/Xss.java Outdated
mbiuki added 3 commits June 4, 2026 13:19
- toolbox.xml: rename velocity tool key from `encode` to `owasp`
  (per @wezell on toolbox.xml:211)
- VelocityRequestWrapper: wrap USE_OWASP_ENCODING_FOR_XSS_PARAMS lookup
  in a Lazy<Boolean> instead of calling Config.getBooleanProperty on
  every request (per @jsanca on line 86)
- Xss.java: remove dead `excludeList` field + `buildExcludeList()` method
  (per @jsanca on line 48; never read or called externally)
…er-24120

# Conflicts:
#	dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/VelocityRequestWrapper.java
#	dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/XssWebAPI.java
#	dotCMS/src/main/java/com/liferay/util/Xss.java
#	dotCMS/src/main/webapp/WEB-INF/toolbox.xml
#	dotCMS/src/test/java/com/dotcms/rendering/velocity/viewtools/OwaspEncoderToolTest.java
@mbiuki
Copy link
Copy Markdown
Member Author

mbiuki commented Jun 4, 2026

Addressed the outstanding review feedback and merged latest main to resolve conflicts.

@wezell

  • ✅ Renamed the velocity tool key from encodeowasp in dotCMS/src/main/webapp/WEB-INF/toolbox.xml. Templates can now use $owasp.forHtml(...) as suggested.
  • ✅ Viewtool (OwaspEncoderTool) was already added in an earlier push.

@jsanca

  • dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/VelocityRequestWrapper.java: wrapped the USE_OWASP_ENCODING_FOR_XSS_PARAMS config lookup in a Lazy<Boolean> holder so the property is read once and reused, instead of hitting Config.getBooleanProperty(...) on every request. Followed the existing dotCMS convention (io.vavr.Lazy, same pattern as ResponseMetaDataWebInterceptor.java:38 and similar).
  • dotCMS/src/main/java/com/liferay/util/Xss.java: removed the dead private static Set<String> excludeList = null; field and the never-called buildExcludeList() method (only self-referenced within the file, no external callers). Also dropped the now-unused java.util.HashSet / java.util.Set imports.

Merge

Resolved 5 merge conflicts with origin/main:

Re-requesting review.

@mbiuki mbiuki requested review from jsanca and wezell June 4, 2026 17:26
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jun 4, 2026

Claude finished @mbiuki's task in 2m 25s —— View job


PR Review — feat(security): add OWASP Java Encoder and expose via XssWebAPI viewtool

  • Gather context
  • Read changed files
  • Review implementation
  • Post findings

Summary

The core integration (dependency, Xss.java encoding methods, XssWebAPI, toolbox.xml key rename) is solid. A few issues worth addressing:


Issues

1. Stale Javadoc in OwaspEncoderTool.java (lines 20–27)

The class-level Javadoc still says $encode after the rename to $owasp:

// Line 20: "$encode" and lines 24-27 use $encode.forHtml(...) etc.
* <p>Registered in {@code toolbox.xml} under the key {@code encode}.
*   <p>$encode.forHtml(...)

After the toolbox key was changed to owasp, templates using $encode.* will silently fail with a null reference in Velocity (no error thrown, just empty output). The doc is actively misleading. Fix this →


2. OwaspEncoderTool.urlHasXSS — NPE risk when URL has no query string (line 73)

url.getQuery() returns null for a URL without a query string (e.g. https://example.com/path). URLEncodedUtils.parse(null, UTF-8) behavior is version-dependent; in older Apache HttpClient versions it throws NullPointerException. Even if it returns an empty list, the code flow should be explicit:

// Current (line 73):
final List<NameValuePair> params =
        URLEncodedUtils.parse(url.getQuery(), StandardCharsets.UTF_8);

// Safer:
final String query = url.getQuery();
if (query == null) return false;
final List<NameValuePair> params = URLEncodedUtils.parse(query, StandardCharsets.UTF_8);

Fix this →


3. Lazy<Boolean> config flag is read-once-forever (design concern)

USE_OWASP_ENCODING_FOR_XSS_PARAMS is a static final Lazy<Boolean> — it reads Config.getBooleanProperty(...) exactly once, on first use, and then never again for the lifetime of the JVM. The intent of adding this config flag was to allow operators to switch off OWASP encoding without a restart, but Lazy defeats that: once evaluated, the value is frozen.

This matches the pattern requested by @jsanca and is consistent with ResponseMetaDataWebInterceptor.java, so the pattern itself is acceptable — but it contradicts the PR description's stated purpose of the flag ("set to false to revert"). Operators would need a restart. If dynamic switching is actually wanted, Lazy is the wrong tool; if read-once is fine, the docs/description should clarify that.


4. $owasp.urlHasXSS and $xsstool.hasXss have incompatible semantics

XssWebAPI.hasXss(value) delegates to Xss.urlHasXSS(value) which is a regex match against the raw string.

OwaspEncoderTool.urlHasXSS(urlToTest) requires urlToTest to be a fully-qualified, valid http/https URL — it returns false for plain strings like <script>alert(1)</script>. A template author comparing $owasp.urlHasXSS and $xsstool.hasXss on the same value will get contradictory results. This is confusing but may be intentional given the different method names. Worth a note in the Javadoc.


5. parallel() stream on a typically-tiny list in urlHasXSS (minor)

Line 74: .stream().parallel().anyMatch(...) over query parameters is a performance anti-pattern — thread pool overhead dominates for lists of a few items. Should be sequential .stream().anyMatch(...).

Fix this →


Clean

  • Xss.java dead code removal (excludeList, buildExcludeList()) is correct and clean.
  • Encoding method wrappers in Xss, XssWebAPI, and OwaspEncoderTool are all straightforward delegations with proper null → "" guards.
  • XssTest (22 tests) and OwaspEncoderToolTest (47 tests) provide good coverage.
  • BOM-only versioning of org.owasp.encoder:encoder:1.3.1 is correct per repo standards.

@dsilvam dsilvam dismissed wezell’s stale review June 5, 2026 11:34

Requests addressed

@dsilvam dsilvam added this pull request to the merge queue Jun 5, 2026
Merged via the queue into main with commit 2821cb9 Jun 5, 2026
62 checks passed
@dsilvam dsilvam deleted the feat/owasp-java-encoder-24120 branch June 5, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code OKR : Security & Privacy Owned by Mehdi Priority : 3 Average Team : Security Issues related to security and privacy

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Add OWASP Java Encoder

5 participants