Skip to content

Move authoring UI inline JS handlers to data attributes - #168

Open
snoopdave wants to merge 2 commits into
masterfrom
authoring-ui-event-handlers
Open

Move authoring UI inline JS handlers to data attributes#168
snoopdave wants to merge 2 commits into
masterfrom
authoring-ui-event-handlers

Conversation

@snoopdave

Copy link
Copy Markdown
Contributor

Several authoring pages embed values in inline JavaScript string literals and
then write them into the page with jQuery .html(). This refactor moves those
values into data attributes and writes them through text APIs, so JSPs stop
hand-concatenating markup and data.

What changed

  • Move server/user-controlled values out of inline JavaScript string literals
    into double-quoted, HTML-escaped data-* attributes.
  • Bind delegated event listeners that read values as text and assign them via
    textContent, .text(), .val(), or DOM constructors.
  • Replace the affected .html() writes with a text API.
  • Use double-quoted attributes for weblog-content values in the media views.
  • Cover templates, bookmarks/folders, categories, media views/chooser/success,
    entry lists, and entry editing, including the category iterator and
    delete-modal cases.
  • Render theme descriptions as text and make ThemeDataServlet return valid
    JSON.

Tests

  • Values containing apostrophes, quotes, angle brackets, backslashes, newlines,
    and </script> remain text through each path.
  • Cross-user authoring cases confirm one author's values stay text in another
    author or administrator session.
  • A source audit confirms no affected value remains in an inline literal, a
    single-quoted attribute, or a .html() sink.

The editor screens built handler arguments by interpolating template values
directly into inline onclick/onchange attributes. Those values now travel in
data-* attributes and are read back through delegated listeners, which matches
how the rest of the UI binds behaviour and keeps markup and data separate.

Writes that placed those values into the DOM now use text APIs rather than
html(), and ThemeDataServlet escapes its JSON output so theme metadata cannot
produce a malformed document.

Adds AuthoringUiSinkAuditTest to keep the editor templates on this pattern.
The media gallery rendered the media file name into single-quoted HTML
attributes; those attributes are now double-quoted so the value stays data.
The sink-audit test description now states the rule it enforces, and the
repeated per-page comment is collapsed to one line.

Claude-Session: https://claude.ai/code/session_01A1fhY1E2PCFU6UAPXu2WtV

@mraible mraible left a comment

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.

The .html().text() swaps and the quote flips are the right direction, but the same sink family is still open in a few places the new audit doesn't reach, so I don't think this closes the authoring-UI XSS class yet. Inline comments for the in-diff ones; three are outside the diff:

  • Bookmarks.jsp:134 still emits <a href='<s:property value="#bookmark.url" />'>. escapeHtml4 leaves ' alone, so a bookmark URL like x' onmouseover='alert(1) breaks out of the attribute (stored XSS against every member who opens the Bookmarks page). Same pattern this PR fixed in MediaFileView.jsp / MediaFileImageChooser.jsp; flip it to double quotes here too.
  • Comments.jsp:279 has the same single-quoted href='<s:property value="#comment.url" />' with a commenter-supplied URL, which is stored XSS against whoever moderates comments. Either include it here or drop the "authoring UI sinks are closed" framing from the description.
  • CreateWeblog.jsp:118 still does $('#themedescription').html(data.description) from the same ThemeDataServlet response that ThemeEdit.jsp now writes with .text().

function confirmDeleteFolder() {
$('#boomarks_delete_folder_folderId').val($('#bookmarks_folderId:first').val());
$('#deleteBlogrollName').html('<s:property value="%{folder.name}"/>');
$('#deleteBlogrollName').text('<s:property value="%{folder.name}"/>');

@mraible mraible Aug 31, 2026

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.

<s:property> HTML-escapes with escapeHtml4, which doesn't touch ', so a folder named Matt's Links renders as .text('Matt's Links') and the SyntaxError takes out every function in this <script> block (delete/rename buttons stop working). And now that it's .text(), Tom & Jerry displays as Tom &amp; Jerry. Use <s:property value="%{folder.name}" escapeJavaScript="true" escapeHtml="false"/> here, or read the name from a data- attribute on the trigger element instead of inlining it in JS.

pw.print("\", ");
pw.print("\"description\" : \"");
pw.print(theme.getDescription());
pw.print(StringEscapeUtils.escapeJson(theme.getDescription()));

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.

CreateWeblog.jsp:118 consumes this same field with $('#themedescription').html(data.description), so a theme description is inert on Theme Edit but still rendered as markup on Create Weblog. Worth switching that call to .text() in this PR, since the audit test only scans jsps/editor and won't notice it.

$('#category-name').html(name);
$('#category-name').text(name);
if ( inUse ) {
$('#category-in-use').css('display','block');

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.

Pre-existing, but since this function is being reworked: lines 294/297 toggle #category-emtpy while the element is #category-empty (line 266), so the "no entries in this category" message never shows.

url: "<s:url value='themedata'/>",
data: {theme: themeId}, success: function (data) {
$('#themeDescription').html(data.description);
$('#themeDescription').text(data.description);

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.

Behaviour change worth a line in the description: a shared theme whose theme.xml <description> carries markup now renders it as literal text. The bundled themes are plain text, so I think that's fine, just disclose it.

public class AuthoringUiSinkAuditTest {

private static final Path EDITOR_JSP_DIR =
Paths.get("src", "main", "webapp", "WEB-INF", "jsps", "editor");

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.

Two things here. This is cwd-relative, so it only runs from app/; surefire already sets project.build.directory for this module (see ApplicationResourcesTest), so deriving the path from that (or basedir) lets it run from an IDE rooted at the repo.

More importantly, the patterns give false assurance: SCRIPT_VAR_LITERAL only matches var x = '<s:property, HANDLER_LITERAL only matches a double-quoted handler attribute with a single-quoted inner literal, and HTML_WRITE ignores .append/innerHTML/.attr('src'). A single-quoted <s:property> as a call argument (.text('<s:property .../>'), previewImage('<s:property .../>')) or a single-quoted attribute (href='<s:property .../>') isn't caught, and both remaining Bookmarks.jsp sinks pass this test green.


<%-- Source data for the "move entries to" select, carried as escaped
attributes rather than generated JavaScript literals. --%>
<div id="category-option-data" style="display:none">

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.

Nit: the .category-edit-link anchors above already carry data-category-id / data-category-name for every category, so populateCategorySelect could read those instead of maintaining a second serialised copy.

}
}

private static String flatten(String source) {

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.

Nit: HANDLER_LITERAL uses \s* and [^"]*, both of which already span newlines, so flatten() / flattenSource don't change any match and can go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants