Skip to content

Conversation

@Patrykb0802
Copy link
Contributor

@Patrykb0802 Patrykb0802 commented Oct 20, 2025

Summary by CodeRabbit

  • New Features

    • Added enhanced XSS protection to all data displays and form inputs across the platform.
    • Implemented new EscapedTextarea component for safer text input handling.
  • Bug Fixes

    • Fixed improper HTML entity display in forms, lists, and dialogs throughout the application.
    • Corrected handling of special characters in event names, descriptions, and various configuration fields.
  • Tests

    • Added comprehensive test coverage for XSS protection utilities and converters.

✏️ Tip: You can customize this high-level summary in your review settings.

Patrykb0802 and others added 9 commits August 20, 2025 16:45
…dEvents.jsp, eventHandlers.jsp, mailingLists.jsp, emailRecipients.js, common.js)
- Created universal converter XssBeanConverter;
- Change annotation name from AllowHtml to NoEscape;
- Corrected escaped pointlinks;
- Created class XssBeanConverterUtils;
- Added test: XssBeanConverterUtilsTest and class: TestGetExtendedName for test;
- Moved NoEscape and XssBeanConverter to org.scada_lts.web.dwr.security;
- Added converted RecipientListEntryBean and IntValuePair in dwr.xml;
@github-actions
Copy link

github-actions bot commented Oct 20, 2025

Java Script Mocha Unit Test Results

268 tests   268 ✅  3s ⏱️
 70 suites    0 💤
  1 files      0 ❌

Results for commit 8f90b7d.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Oct 20, 2025

Java JUnit Test Results

3 476 tests   3 476 ✅  52s ⏱️
  136 suites      0 💤
  136 files        0 ❌

Results for commit 8f90b7d.

♻️ This comment has been updated with latest results.

@Limraj Limraj added this to the 2.8.1 milestone Oct 24, 2025
@JsonRemoteProperty
private String name;
@JsonRemoteProperty
@JsonRemoteProperty @NoEscape
Copy link
Collaborator

Choose a reason for hiding this comment

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

I added this annotation here, but it probably shouldn't be here, because it's a structure representing js scripts executed on the server side, using the rhino engine, it's not a script executed on the client/browser side.

Patrykb0802 and others added 20 commits October 30, 2025 14:33
…lists:

- Fixed double escaped alias in: MaintenanceEventVO, ScheduledEventVO (use NoEscape annotation);
- Fixed escaped after save event handler;
…lists:

- Removed use NoEscape annotation in: MaintenanceEventVO.alias, ScheduledEventVO.alias
- Unescaped in getDescription string fields from: MaintenanceEventVO, ScheduledEventVO because returned LocalizableMessage object escaped by converter;
- Corrected maintenanceEvents.jsp, scheduledEvents.jsp use unescapeHtml;
…lists:

- Corrected: MaintenanceEventVO.getDescription, ScheduledEventVO.getDescription;
…ds_in_Event_handlers_Users_profiles_watch_lists

#3206 Fixed escaping fields in Event handlers, Users profiles, watch lists
…scaping_fields_in_mailing_lists_maintenance_events_point_links_publishers
…nt links, publishers:

- Added convert definitions for UserEntryJson, AddressEntryJson;
- Fixed escape add address;
…gation_Guidance_System_settings

#3197 CVE-2021-26829 Mitigation Guidance [System settings]
… into fix/#3209_Fixed_escaping_fields_in_mailing_lists_maintenance_events_point_links_publishers
…nt links, publishers:

- Corrected all publisher types;
…ds_in_mailing_lists_maintenance_events_point_links_publishers

#3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers
…nt links, publishers:

- Corrected editPachube.jsp, editPersistent.jsp;
…ds_in_mailing_lists_maintenance_events_point_links_publishers

#3209 Fixed escaping fields in mailing lists, maintenance events, poi…
 - Fixed displaying of Link component as well as scripts on scripts list in button (script) component in graphical views
 - Fixed displaying script names in scripting view
}

public String createLinkContent() {
String safeHref = escapeHtml(link == null ? "" : link);
Copy link
Collaborator

Choose a reason for hiding this comment

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

String escapedHref = link == null ? "" : escapeHtml(link);


public String createLinkContent() {
String safeHref = escapeHtml(link == null ? "" : link);
String safeText = escapeHtml(text == null ? "" : text);
Copy link
Collaborator

Choose a reason for hiding this comment

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

String escapedText = text == null ? "" : escapeHtml(text);

private void createLink() {
String content = createLinkContent();
setContent(content);
setContent(createLinkContent());
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the reason for this change?

I would go back to how it was, if there is no need to change something, then we probably won't touch it.

@Patrykb0802 Patrykb0802 requested a review from Limraj December 1, 2025 10:11
@Limraj Limraj modified the milestones: 2.8.1, 2.8.2 Dec 23, 2025
@Patrykb0802
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

This pull request implements comprehensive XSS (Cross-Site Scripting) protection across the application by introducing a centralized DWR converter system, security utilities for escaping/unescaping HTML entities, new Vue.js components for safe text handling, and updates to JSP views to properly display previously escaped content.

Changes

Cohort / File(s) Summary
DWR Converter Infrastructure
WebContent/WEB-INF/dwr.xml, src/org/scada_lts/web/security/dwr/XssBeanConverter.java, src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java, src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java, src/org/scada_lts/web/security/dwr/NoEscape.java, src/org/scada_lts/web/security/dwr/ScadaMarshallException.java
Consolidated DWR converters into four new generic XSS-safe converters (xssBean, xssLocalizableMessageString, protocolVersionEnum, opcUaDataTypeEnum). Replaces multiple specific converters across 100+ class mappings. Adds reflection-based utility for recursive object escaping/unescaping with field-level @NoEscape annotation support.
Deleted DWR Converters
src/com/serotonin/mango/web/dwr/XssDataPointBeanConverter.java, src/com/serotonin/mango/web/dwr/XssDataPointVoConverter.java
Removes specialized DataPoint converters now handled by generic XssBeanConverter.
Backend Security & Utilities
src/org/scada_lts/web/security/XssProtectUtils.java, src/org/scada_lts/web/security/EmptyInstance.java, src/org/scada_lts/utils/PathSecureUtils.java
Adds unescapeHtml() public method, introduces centralized escape/unescape mappings for newline/whitespace entities, and hardens path validation with null-safety checks.
Data Models - EmptyInstance Implementation
src/com/serotonin/mango/vo/DataPointVO.java, src/com/serotonin/mango/vo/event/EventTypeVO.java, src/com/serotonin/mango/web/dwr/beans/DataPointBean.java
Implements EmptyInstance interface with newInstanceEmpty() method for creating default instances. DataPointBean adds VO-based constructor and anonymous PointLocatorVO stub creation.
Data Models - Unescaping in Descriptions
src/com/serotonin/mango/vo/event/MaintenanceEventVO.java, src/com/serotonin/mango/vo/event/ScheduledEventVO.java
Applies unescapeHtml() to fields (alias, activeCron, inactiveCron, dataSourceName) when constructing LocalizableMessage descriptions.
Additional Services
src/com/serotonin/mango/vo/report/ReportVO.java, src/org/scada_lts/mango/service/SystemSettingsService.java, src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java, src/br/org/scadabr/view/component/LinkComponent.java, src/com/serotonin/mango/web/dwr/PointLinksDwr.java
Adds path validation for report filenames, validates resource paths before storage/retrieval, wraps profile VO conversions in stream pipeline, introduces null-safe HTML escaping, removes unused import.
Vue.js Utilities & Components
scadalts-ui/src/utils/common.js, scadalts-ui/src/components/common/EscapedTextarea.vue
Adds unescapeVueHtml() for Vue-specific HTML entity unescaping with iterative refinement. Creates reusable EscapedTextarea component with props for value/rows/autoGrow, computed decoded property, and validate delegation.
Vue.js Views
scadalts-ui/src/apps/App.vue, scadalts-ui/src/components/datasources/MetaDataSource/point.vue, scadalts-ui/src/views/DataObjects/Scripting/index.vue, scadalts-ui/src/views/System/SystemSettings/index.vue
Replaces v-text-field/v-textarea with EscapedTextarea component in script/settings forms. Shifts from mustache interpolation to v-text binding with unescapeVueHtml() for safe rendering. Adds decodeName() utility for display.
JSP Views - HTML Unescaping on Display
WebContent/WEB-INF/jsp/compoundEvents.jsp, WebContent/WEB-INF/jsp/eventHandlers.jsp, WebContent/WEB-INF/jsp/scheduledEvents.jsp, WebContent/WEB-INF/jsp/scripting.jsp, WebContent/WEB-INF/jsp/usersProfiles.jsp
Applies unescapeHtml() wrapper to field assignments when populating UI from server objects, ensuring proper display of HTML-escaped entity values.
JSP Views - Data Source & Receiver Configuration
WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp, WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp
Wraps field names (parameterName, binary0Value, fieldName, timeOverrideName, updateStatement) with unescapeHtml() during form initialization.
JSP Views - Complex List Handling
WebContent/WEB-INF/jsp/mailingLists.jsp, WebContent/WEB-INF/jsp/maintenanceEvents.jsp, WebContent/WEB-INF/jsp/pointLinks.jsp
Converts static key/value pairs in dwr.util.addOptions() to dynamic accessor functions with unescaping; maps response arrays with escaping wrappers before population.
JSP Views - Publisher & Point Configuration
WebContent/WEB-INF/jsp/publisherEdit.jsp, WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp, WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp, WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp, WebContent/WEB-INF/jsp/publisherList.jsp
Switches from direct EL substitution to <c:out> wrapper for form values; applies unescapeHtml() on display and escapeHtml() on storage of point/header/parameter values; wraps display values in spans with escaping.
JSP Views - Miscellaneous
WebContent/WEB-INF/jsp/include/settingsEditor.jsp, WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp, WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp, WebContent/WEB-INF/jsp/reports.jsp, WebContent/WEB-INF/jsp/users.jsp, WebContent/WEB-INF/jsp/watchList.jsp, WebContent/WEB-INF/tags/page.tag
Refactors loop iteration with proper scoping; adds htmlEscape="false" to bindings; introduces input sanitization and validation for new node creation; applies encodeURIComponent() for export URLs; switches innerHTML assignments; wraps header output in <c:out>.
Test Coverage
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java, test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java, test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java, test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java
Adds parameterized test suites covering object escaping/unescaping, exception handling for invalid types, and verification that conversions produce new instances.
Build Configuration
build.gradle
Updates test task to replace CSS validator test suite path and adds new XSS converter utilities test suite.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Browser/Vue
    participant DWRConv as DWR Converter<br/>(XssBeanConverter)
    participant Utils as XssBeanConverterUtils
    participant Backend as Backend Service
    participant DB as Database

    rect rgb(200, 220, 255)
    note over Client,DB: Outbound Data Flow (Server → Client)
    Backend->>DWRConv: convertOutbound(DataObject)
    DWRConv->>Utils: convertObjectEscaped(DataObject)
    Utils->>Utils: Recursively escape all<br/>string fields
    Utils-->>DWRConv: Escaped DataObject
    DWRConv-->>Client: JSON with escaped HTML
    Client->>Client: unescapeHtml/unescapeVueHtml<br/>for display
    end

    rect rgb(220, 255, 220)
    note over Client,DB: Inbound Data Flow (Client → Server)
    Client->>DWRConv: submitForm(JSON data)
    DWRConv->>Utils: convertObjectUnescaped(JSONData)
    Utils->>Utils: Recursively unescape<br/>string fields
    Utils-->>DWRConv: Unescaped DataObject
    DWRConv-->>Backend: convertInbound(DataObject)
    Backend->>DB: store(DataObject)
    DB-->>Backend: Success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The review requires understanding of the new reflection-based XssBeanConverterUtils with complex field traversal logic (~218 lines), verification of DWR configuration consolidation across 100+ class mappings, assessment of consistent unescaping patterns across 25+ JSP files, and validation of Vue.js component integration with safe rendering. The heterogeneous nature of changes across multiple systems (DWR, Java backend, frontend, JSP) and non-trivial logic density necessitate careful analysis per subsystem rather than pattern-based review.

Poem

🐰 Whiskers twitch with XSS delight,
Escaping HTML left and right!
Safe strings hop through Vue and DWR,
No injected scripts shall occur!
Security hops, the code's more bright,
Unescaped display, sanitized might! 🛡️

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix/#3167 fixed escaping fields' directly addresses the main objective of the PR, which is to fix escaping field issues referenced in issue #3167.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java (2)

24-26: Remove redundant .map(UsersProfileVO::new) call.

Since selectProfiles now creates fresh UsersProfileVO instances (lines 42-45), the additional .map(UsersProfileVO::new) on line 25 creates a redundant second copy of each object.

♻️ Remove the redundant mapping
 public Optional<UsersProfileVO> selectProfileById(int profileId) {
-    return selectProfiles(0, Integer.MAX_VALUE).stream().filter(a -> a.getId() == profileId).map(UsersProfileVO::new).findAny();
+    return selectProfiles(0, Integer.MAX_VALUE).stream().filter(a -> a.getId() == profileId).findAny();
 }

28-31: Remove redundant .map(UsersProfileVO::new) call.

Similar to selectProfileById, this method now performs double-wrapping since selectProfiles already creates fresh instances.

♻️ Remove the redundant mapping
 public Optional<UsersProfileVO> selectProfileByXid(String profileXid) {
-    return selectProfiles(0, Integer.MAX_VALUE).stream().filter(a -> a.getXid().equals(profileXid)).map(UsersProfileVO::new).findAny();
+    return selectProfiles(0, Integer.MAX_VALUE).stream().filter(a -> a.getXid().equals(profileXid)).findAny();
 }
WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp (1)

52-68: Add description field to DataPointVO DWR converter include list for consistency.

The name field (line 52) is included in the DataPointVO XssBean converter configuration (dwr.xml line 160), so using htmlEscape="false" correctly prevents double-escaping when Spring processes the bound value.

However, the description field (line 62) is not in the DWR converter include list. This means the backend XssBeanConverter never escapes this field, making htmlEscape="false" unnecessary and creating an inconsistent escaping approach between the two fields. Both fields should either:

  1. Be included in the converter's include list with htmlEscape="false" to prevent double-escaping, or
  2. Be excluded from the converter and rely solely on <c:out> for escaping

Since name is already included in the converter, add description to maintain uniform backend escaping of all user-facing text fields.

src/org/scada_lts/mango/service/SystemSettingsService.java (1)

633-657: Fix null/empty handling before validatePath(...) to avoid NPE + preserve “clear to default” behavior

Both saveResourceGraphicsPathMisc and saveResourceUploadsPathMisc can throw on null input (validation runs before the null check). Also, empty string now always fails early, even though the subsequent branch suggests it should be allowed.

Proposed fix (apply similarly to uploads)
     public void saveResourceGraphicsPathMisc(String webResourceGraphicsPath, DwrResponseI18n response) {
-        boolean validated = PathSecureUtils.ValidationPaths.validatePath(webResourceGraphicsPath, a -> true);
-        if(!validated) {
-            response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, "validate.invalidValue");
-        } else if (webResourceGraphicsPath != null && (StringUtils.isEmpty(webResourceGraphicsPath)
+        if (webResourceGraphicsPath == null) {
+            response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, "validate.invalidValue");
+            return;
+        }
+        // Preserve existing "empty means default" behavior
+        if (StringUtils.isEmpty(webResourceGraphicsPath)) {
+            saveResourceGraphicsPathMisc(webResourceGraphicsPath);
+            return;
+        }
+
+        boolean validated = PathSecureUtils.ValidationPaths.validatePath(webResourceGraphicsPath, a -> true);
+        if(!validated) {
+            response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, "validate.invalidValue");
+        } else if (webResourceGraphicsPath != null && (StringUtils.isEmpty(webResourceGraphicsPath)
                 || (webResourceGraphicsPath.endsWith("graphics")
                 || webResourceGraphicsPath.endsWith("graphics" + File.separator)))) {
             saveResourceGraphicsPathMisc(webResourceGraphicsPath);
         } else {
             response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, "systemsettings.webresource.graphics.path.wrong", File.separator);
         }
     }
WebContent/WEB-INF/jsp/maintenanceEvents.jsp (1)

71-108: Add null-safety checks to unescapeHtml calls for potentially null fields.

The unescapeHtml function converts null/undefined to the strings "null"/"undefined" rather than handling them gracefully. Fields like xid, alias, activeCron, and inactiveCron can be null when a new MaintenanceEventVO is created, causing incorrect string values to be displayed.

Proposed fix
-            $set("xid", unescapeHtml(me.xid));
+            $set("xid", unescapeHtml(me.xid || ""));
 ...
-            $set("alias", unescapeHtml(me.alias));
+            $set("alias", unescapeHtml(me.alias || ""));
 ...
-            $set("activeCron", unescapeHtml(me.activeCron));
+            $set("activeCron", unescapeHtml(me.activeCron || ""));
 ...
-            $set("inactiveCron", unescapeHtml(me.inactiveCron));
+            $set("inactiveCron", unescapeHtml(me.inactiveCron || ""));
WebContent/WEB-INF/jsp/scheduledEvents.jsp (1)

74-104: Use empty string fallback for unescapeHtml calls to avoid setting form fields to literal "null"/"undefined" strings.
If se.xid, se.alias, se.activeCron, or se.inactiveCron are null/undefined, unescapeHtml silently converts them to the strings "null" or "undefined", which will then populate the form elements with unwanted text instead of empty values.

Proposed patch
-            $set("xid", unescapeHtml(se.xid));
-            $set("alias", unescapeHtml(se.alias));
+            $set("xid", unescapeHtml(se.xid || ""));
+            $set("alias", unescapeHtml(se.alias || ""));
 ...
-            $set("activeCron", unescapeHtml(se.activeCron));
+            $set("activeCron", unescapeHtml(se.activeCron || ""));
 ...
-            $set("inactiveCron", unescapeHtml(se.inactiveCron));
+            $set("inactiveCron", unescapeHtml(se.inactiveCron || ""));
WebContent/WEB-INF/jsp/eventHandlers.jsp (1)

516-535: Use textContent instead of innerHTML to prevent XSS when writing unescaped content to the DOM.

The call to unescapeHtml(handler.message) reverses HTML entity escaping (e.g., &lt;script&gt;<script>). When this decoded value is passed to $set, which delegates to dwr.util.setValue using innerHTML, the browser interprets the decoded content as executable code. Prefer textContent or innerText to safely insert plain text.

Suggested fix
-            else
-                $set(handler.id +"Msg", unescapeHtml(handler.message));
+            else {
+                var el = $(handler.id + "Msg");
+                if (el) el.textContent = unescapeHtml((handler && handler.message) || "");
+            }
scadalts-ui/src/views/DataObjects/Scripting/index.vue (1)

420-432: Avoid mixed escaping rules: script is unescaped on save but name isn’t.
This is a common source of double-escaping regressions (especially if backend converters now escape outbound). Consider unescaping name in the payload as well (or unescaping nothing if EscapedTextarea already returns raw).

Proposed fix (if `EscapedTextarea` stores escaped values)
 const payload = {
   ...this.scriptForm,
+  name: unescapeVueHtml(this.scriptForm.name || ''),
   script: unescapeVueHtml(this.scriptForm.script || ''),
 };
🤖 Fix all issues with AI agents
In @src/com/serotonin/mango/vo/report/ReportVO.java:
- Around line 877-878: Avoid NPE and encoded-semicolon bypass by treating null
names and decoding URL-encoded input before validation: replace the direct use
of name in the validateFilename and contains checks with a safe, decoded value
(e.g., safeName = name == null ? "" : name; decodedName =
URLDecoder.decode(safeName, StandardCharsets.UTF_8)); use
validateFilename(decodedName + ".csv") and check decodedName for ';' (and
optionally check for the encoded form "%3B" case-insensitively) before calling
response.addContextualMessage("name", "validate.invalidValue"); ensure you never
concatenate a literal "null" into the filename and handle
UnsupportedEncodingException if not using StandardCharsets.

In @src/org/scada_lts/utils/PathSecureUtils.java:
- Around line 58-65: normalizePath currently returns null on failure which
causes a NullPointerException at call sites like UploadBackgroundFileUtilsTest
where .toString() is invoked without a null check; change the API to return
Optional<Path> instead: update normalizePath(String) to return Optional<Path>,
wrap successful result in Optional.of(path) and return Optional.empty() on
exception (logging the error), then update all callers (notably
UploadBackgroundFileUtilsTest) to consume the Optional safely (e.g., use
map(Path::toString).orElseThrow(...) or orElse/ifPresent) so no call invokes
methods on a possibly-null Path.

In @src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java:
- Around line 26-36: convertObjectEscaped and convertObjectUnescaped (and
similar blocks) can NPE or throw for simple types; update doConvertIf usage to
first return null for null input, return simple types unchanged, and apply
escape/unescape only for Strings. Specifically, in
convertObjectEscaped/convertObjectUnescaped and the other occurrences around
doConvertIf/isSimpleJavaType/doConvert/convertObject, add a null check and a
simple-type branch that returns the original value (but for instance-of String,
call escapeIfString/unescapeIfString), ensuring doConvert is only invoked for
complex types.
- Around line 68-85: The conversion loop currently swallows conversion failures
by returning null (causing the original unescaped value to be used) and/or only
logging; change the logic so any exception or failed conversion during
convert.apply(...) results in throwing a ScadaMarshallException (wrap underlying
exception and include stacktrace) instead of falling back silently — update the
try/catch in the loop around field.setAccessible(...) / convert.apply(...) to
rethrow new ScadaMarshallException(originObject.getClass(), e) (or throw a
RuntimeException wrapping that) and remove the silent-null fallback; apply the
same change to the other similar blocks referenced (lines ~148-151, 165-168,
182-185, 197-200) and ensure NoEscape handling remains only for successfully
converted non-null results.
- Around line 60-88: doConvert does only shallow conversion — nested bean fields
and collection/map/array elements are left unprocessed; implement deep
conversion by recursively applying the conversion to complex values: for
non-primitive/non-String objects, call doConvert (or convertObject) on the
nested object, and for arrays/Collections/Maps iterate their elements and
convert each element/value, creating new containers as needed. Add cycle
detection using an IdentityHashMap<Object, Object> visited map passed through
recursive calls to avoid infinite loops and reuse created instances. Preserve
NoEscape handling on fields and ensure field.set uses the converted nested
instance when available; apply the same recursive logic to the similar code at
the other location referenced (lines 108-131).
- Around line 96-106: newInstanceEmpty is brittle: stop assuming specific
constructor signatures and avoid converting Map keys. Update newInstanceEmpty to
(1) handle Collection and Map types explicitly by creating safe empty instances
(e.g., ArrayList for Collection, HashMap/LinkedHashMap for Map) instead of
calling getConstructor(Collection/Map.class); (2) keep the existing
EmptyInstance and LocalizableMessage branches, and for other types attempt a
no-arg constructor via getDeclaredConstructor().setAccessible(true) but if
instantiation fails return a safe fallback (the original object or a documented
empty placeholder) rather than null; and (3) when converting maps, do not
transform keys — use the original key when putting into the output map (avoid
out.put(convert.apply(key), ...)), only convert values. Reference:
newInstanceEmpty, EmptyInstance, LocalizableMessage, and the map conversion site
that uses out.put(convert.apply(key), ...).

In @src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java:
- Around line 18-29: convertInbound currently takes the result of
StringConverter.convertInbound (a String) into convertObjectUnescaped which
rejects simple types and throws ScadaMarshallException; modify convertInbound so
after calling super.convertInbound(paramType, iv, inctx) you check if the
returned 'converted' is a String and, if so, unescape it directly (e.g. call
XssProtectUtils.unescapeHtml on converted) instead of calling
convertObjectUnescaped; for non-String types continue to call
convertObjectUnescaped(converted).

In @WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp:
- Around line 827-832: The dialog uses an undefined UI string and an
out-of-scope error variable: add a new message key (e.g., messages.folderNotAdd
or messages.pointHierarchySLTS.folderNotAdd) to the messages object and to i18n
properties, and replace the out-of-scope errorThrown reference in the dialog
HTML with a defined value (either a new messages key like
messages.errorThrown/messageGeneric or the actual error parameter from the AJAX
error callback); ensure the dialog construction in the branch that checks
titleNewNode uses only defined message keys/variables.
- Around line 820-825: The endpoint handlers for
/pointHierarchy/new/{newParentId}/{newTitle} and the edit counterpart accept
newTitle raw and lack server-side validation; update the controller/service that
handles these routes to validate and sanitize newTitle (enforce non-empty and
max length, verify/normalize character encoding, reject or strip dangerous
characters, and apply XSS escaping via XssProtectUtils.escapeHtml() before
persisting), implement these checks where the TODO at line 161 indicates
incomplete validation, and return clear 4xx responses on validation failure
instead of persisting invalid input.

In @WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp:
- Around line 214-218: The renderer that returns "<span>" + data.pointType +
"</span>" (the anonymous function producing the pointType cell) does not escape
untrusted content; change it to use the existing escapeHtml helper (same as used
for data.pointName and data.parameterName) so the output becomes "<span>" +
escapeHtml(data.pointType) + "</span>" to prevent XSS.
- Line 38: The code constructs objects with type:
unescapeHtml(list[i].dataTypeMessage) and later renders data.pointType directly
into the DOM, creating an XSS risk; update the renderer that returns "<span>" +
data.pointType + "</span>" to instead call escapeHtml(data.pointType) (i.e.,
wrap data.pointType with escapeHtml) so the value is safely escaped before
insertion; apply the same fix to the equivalent renderers in editPersistent.jsp
and editPachube.jsp that output data.pointType.

In @WebContent/WEB-INF/jsp/reports.jsp:
- Around line 160-169: The inline onclick handlers embed ri.name directly which
can break the JS string or enable XSS; change the rendering to stop injecting
raw ri.name into the onclick and instead put the values into data attributes on
the <img> (e.g., data-name and data-id) and update the handlers to read from the
element (create helper functions like exportDataFromElement,
exportEventDataFromElement, exportUserCommentsFromElement that call
exportData/exportEventData/exportUserComments after decoding the data-name), or
alternatively properly escape ri.name for JS string context before embedding it.
- Around line 338-340: The updateReport function assigns untrusted name to DOM
via $("r"+ id +"Name").innerHTML which enables XSS; change the assignment to use
textContent (i.e., $("r"+ id +"Name").textContent = name) or run name through an
HTML-escaping sanitizer before assigning to innerHTML so the value is rendered
as plain text rather than executable HTML.

In @WebContent/WEB-INF/jsp/users.jsp:
- Line 326: The line using $("u"+ user.id +"Username").innerHTML =
user.username; introduces an XSS risk by injecting raw HTML; change to assign
the username as plain text (use textContent or equivalent) so the DOM receives
untrusted data safely, i.e., replace the innerHTML assignment with a text-safe
assignment for the element referenced by $("u"+ user.id +"Username"); if the
value must contain trusted HTML, explicitly sanitize or unescape it before
inserting (perform sanitization at the client using a vetted sanitizer or
confirm backend-provided HTML is safe) and document why innerHTML is necessary
when touching the user.username insertion.
🟡 Minor comments (6)
WebContent/WEB-INF/jsp/scripting.jsp-106-106 (1)

106-106: Inconsistent HTML handling for script name display.

Line 106 assigns se.name to innerHTML, but line 122 shows that script names are HTML-escaped on the server (requiring unescapeHtml() for editing). This will cause HTML entities to display literally in the script list.

Suggested fix

Use textContent for safe text assignment (recommended):

-        $("se"+ se.id +"Name").innerHTML = se.name;
+        $("se"+ se.id +"Name").textContent = se.name;

Or unescape if HTML rendering is needed:

-        $("se"+ se.id +"Name").innerHTML = se.name;
+        $("se"+ se.id +"Name").innerHTML = unescapeHtml(se.name);
WebContent/WEB-INF/jsp/watchList.jsp-154-157 (1)

154-157: Use unescapeHtml() when assigning point.value to innerHTML at line 157.

Line 123 explicitly unescapes point values with unescapeHtml() for display, but line 157 assigns point.value directly to innerHTML without unescaping. This inconsistency causes HTML entities in point names to render literally instead of being decoded properly in the tree node title.

Change line 157 from:

spanNode.innerHTML = point.value;

to:

spanNode.innerHTML = unescapeHtml(point.value);
src/org/scada_lts/web/security/dwr/NoEscape.java-5-9 (1)

5-9: Fix javadoc inconsistency.

The javadoc mentions "field or method parameter", but the @Target annotation only includes ElementType.FIELD. Either update the javadoc to remove "or method parameter" or add ElementType.PARAMETER to the @Target if method parameters should also be supported.

📝 Proposed fix
 /**
- * Marks a field or method parameter as safe to carry raw HTML.
+ * Marks a field as safe to carry raw HTML.
  * Use sparingly: only for server-generated HTML fragments you really intend to render via innerHTML.
  */
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java-30-42 (1)

30-42: Test method names reference wrong exception type.

The method names mention IllegalArgumentException but the tests expect ScadaMarshallException:

  • when_convertObjectEscaped_for_object_with_type_no_source_then_IllegalArgumentException
  • when_convertObjectUnescaped_for_object_with_type_no_source_then_IllegalArgumentException

This creates confusion between the method name and actual behavior.

Suggested fix
-    public void when_convertObjectEscaped_for_object_with_type_no_source_then_IllegalArgumentException() throws ScadaMarshallException {
+    public void when_convertObjectEscaped_for_simple_type_then_ScadaMarshallException() throws ScadaMarshallException {

-    public void when_convertObjectUnescaped_for_object_with_type_no_source_then_IllegalArgumentException() throws ScadaMarshallException {
+    public void when_convertObjectUnescaped_for_simple_type_then_ScadaMarshallException() throws ScadaMarshallException {
WebContent/WEB-INF/jsp/mailingLists.jsp-232-232 (1)

232-232: Potential double-escaping issue with referenceAddress mutation.

The addressEntry object is added to editingMailingList.entries on line 231, then its referenceAddress is escaped on line 232. Since JavaScript objects are passed by reference, this mutation affects the entry in the array. When createRecipList() (lines 146-150) sends entries back to the server, the escaped value is transmitted. If the server escapes again on save, this could cause double-escaping on subsequent edits.

Consider escaping only for display purposes:

Suggested fix
         editingMailingList.entries[editingMailingList.entries.length] = addressEntry;
-        addressEntry.referenceAddress = escapeHtml(addressEntry.referenceAddress);
-        appendAddressEntry(addressEntry);
+        appendAddressEntry({...addressEntry, referenceAddress: escapeHtml(addressEntry.referenceAddress)});

Or pass the escaped value only to the display function without mutating the original entry.

Committable suggestion skipped: line range outside the PR's diff.

scadalts-ui/src/views/DataObjects/Scripting/index.vue-30-33 (1)

30-33: decodeName() is fine for safe display, but ensure search/sort semantics are intentional.
You’re unescaping only for the rendered cell; filtering/sorting still uses the (possibly escaped) underlying item.name, which can feel inconsistent for users searching for </& content. If you want “what you see is what you search”, consider normalizing the search source too.

Also applies to: 453-455

🧹 Nitpick comments (9)
src/org/scada_lts/mango/service/SystemSettingsService.java (1)

471-483: Consider whether validatePath(path, a -> true) provides enough assurance

Because the predicate always returns true, validatePath is only doing normalization/string checks (not existence). If you intended existence checks, pass Files::exists; if not, consider simplifying validatePath’s signature to avoid implying existence validation.

Also applies to: 485-497

src/org/scada_lts/web/security/EmptyInstance.java (1)

3-5: Consider using generics for type safety.

The return type Object requires callers to cast, which can fail at runtime. Consider using a generic type parameter for better type safety.

♻️ Proposed refactor with generics
-public interface EmptyInstance {
-    Object newInstanceEmpty();
+public interface EmptyInstance<T> {
+    T newInstanceEmpty();
 }

This would allow implementations like:

public class DataPointVO implements EmptyInstance<DataPointVO> {
    @Override
    public DataPointVO newInstanceEmpty() {
        return new DataPointVO(-1, -1, -1);
    }
}
WebContent/WEB-INF/jsp/maintenanceEvents.jsp (1)

55-60: Nice: unescaping option labels before addOptions improves UX; just harden against missing data.

Optional hardening
-        	var ds = response.data.dataSources.map(function(o) {
-                return { key: o.key, value: unescapeHtml(o.value) };
-        	});
+        	var ds = (response.data && response.data.dataSources ? response.data.dataSources : []).map(function(o) {
+                return { key: o.key, value: unescapeHtml((o && o.value) || "") };
+        	});
         	dwr.util.addOptions("dataSourceId", ds, "key", "value");
WebContent/WEB-INF/jsp/pointLinks.jsp (1)

29-44: Standardize to let and add optional null-safety guards.

The codebase already uses ES6 syntax (let, arrow functions) across 26+ JSP files, so ES5 rewrite is not necessary. However, lines 31–32 mix let with the var declarations above (line 26–27). For consistency, use let throughout. Additionally, add defensive null checks for p.value as optional hardening:

Suggested refactor
-            let srcOpts = response.sourcePoints.map(p => ({ key: p.key, value: unescapeHtml(p.value) }));
-            let tgtOpts = response.targetPoints.map(p => ({ key: p.key, value: unescapeHtml(p.value) }));
+            let srcOpts = (response.sourcePoints || []).map(p => ({
+                key: p.key,
+                value: unescapeHtml((p && p.value) || "")
+            }));
+            let tgtOpts = (response.targetPoints || []).map(p => ({
+                key: p.key,
+                value: unescapeHtml((p && p.value) || "")
+            }));
scadalts-ui/src/utils/common.js (1)

66-83: Consider handling null/undefined inputs explicitly.

String(null) returns "null" and String(undefined) returns "undefined", which may not be the intended behavior when unescaping. Consider adding a guard:

💡 Suggested improvement
 export function unescapeVueHtml(str) {
+  if (str == null) return '';
   let s = String(str);
src/org/scada_lts/web/security/dwr/ScadaMarshallException.java (1)

5-17: Consider adding serialVersionUID.

Since exceptions are Serializable, adding an explicit serialVersionUID ensures consistent serialization across JVM versions.

💡 Suggested improvement
 public class ScadaMarshallException extends MarshallException {
+    private static final long serialVersionUID = 1L;
 
     public ScadaMarshallException(Class paramType) {
src/com/serotonin/mango/web/dwr/beans/DataPointBean.java (1)

98-153: EmptyInstance implementation for XSS converter support.

The newInstanceEmpty() method correctly implements the EmptyInstance interface to support the XSS bean conversion mechanism. The anonymous PointLocatorVO provides stub implementations returning safe defaults (null, 0, false).

Consider extracting the anonymous PointLocatorVO to a private static nested class or a separate EmptyPointLocatorVO class if this pattern is reused elsewhere, as it would improve readability and testability.

test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java (1)

24-28: Unused type parameter in constructor.

The type parameter is passed to the constructor but never stored or used. It appears in the test name via @Parameterized.Parameters(name = "{index}: value: {0}, type: {1}") for display purposes only.

If the type is only needed for test naming, consider removing it from the constructor signature, or document why it's passed but unused:

-    public XssBeanConverterUtilsExceptionTest(Object value, Class<?> type) {
+    public XssBeanConverterUtilsExceptionTest(Object value, Class<?> typeForDisplay) {
         this.value = value;
+        // typeForDisplay is used only in @Parameters name template
     }
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java (1)

13-59: Make assertions field-specific instead of relying on VO equals().
This will keep the test robust even if equals() changes (or isn’t implemented). Also assert result type for clarity.

Example adjustment
 Object result = XssBeanConverterUtils.convertObjectEscaped(objectUnescaped);
-Assert.assertEquals(objectEscaped, result);
+Assert.assertTrue(result instanceof ContextualizedScriptVO);
+Assert.assertEquals(objectEscaped.getScript(), ((ContextualizedScriptVO) result).getScript());
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee78bfe and 8f90b7d.

📒 Files selected for processing (54)
  • WebContent/WEB-INF/dwr.xml
  • WebContent/WEB-INF/jsp/compoundEvents.jsp
  • WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp
  • WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp
  • WebContent/WEB-INF/jsp/eventHandlers.jsp
  • WebContent/WEB-INF/jsp/include/settingsEditor.jsp
  • WebContent/WEB-INF/jsp/mailingLists.jsp
  • WebContent/WEB-INF/jsp/maintenanceEvents.jsp
  • WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp
  • WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp
  • WebContent/WEB-INF/jsp/pointLinks.jsp
  • WebContent/WEB-INF/jsp/publisherEdit.jsp
  • WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp
  • WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp
  • WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp
  • WebContent/WEB-INF/jsp/publisherList.jsp
  • WebContent/WEB-INF/jsp/reports.jsp
  • WebContent/WEB-INF/jsp/scheduledEvents.jsp
  • WebContent/WEB-INF/jsp/scripting.jsp
  • WebContent/WEB-INF/jsp/users.jsp
  • WebContent/WEB-INF/jsp/usersProfiles.jsp
  • WebContent/WEB-INF/jsp/watchList.jsp
  • WebContent/WEB-INF/tags/page.tag
  • build.gradle
  • scadalts-ui/src/apps/App.vue
  • scadalts-ui/src/components/common/EscapedTextarea.vue
  • scadalts-ui/src/components/datasources/MetaDataSource/point.vue
  • scadalts-ui/src/utils/common.js
  • scadalts-ui/src/views/DataObjects/Scripting/index.vue
  • scadalts-ui/src/views/System/SystemSettings/index.vue
  • src/br/org/scadabr/view/component/LinkComponent.java
  • src/com/serotonin/mango/vo/DataPointVO.java
  • src/com/serotonin/mango/vo/event/EventTypeVO.java
  • src/com/serotonin/mango/vo/event/MaintenanceEventVO.java
  • src/com/serotonin/mango/vo/event/ScheduledEventVO.java
  • src/com/serotonin/mango/vo/report/ReportVO.java
  • src/com/serotonin/mango/web/dwr/PointLinksDwr.java
  • src/com/serotonin/mango/web/dwr/XssDataPointBeanConverter.java
  • src/com/serotonin/mango/web/dwr/XssDataPointVoConverter.java
  • src/com/serotonin/mango/web/dwr/beans/DataPointBean.java
  • src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java
  • src/org/scada_lts/mango/service/SystemSettingsService.java
  • src/org/scada_lts/utils/PathSecureUtils.java
  • src/org/scada_lts/web/security/EmptyInstance.java
  • src/org/scada_lts/web/security/XssProtectUtils.java
  • src/org/scada_lts/web/security/dwr/NoEscape.java
  • src/org/scada_lts/web/security/dwr/ScadaMarshallException.java
  • src/org/scada_lts/web/security/dwr/XssBeanConverter.java
  • src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java
  • src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java
  • test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java
  • test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java
  • test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java
  • test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java
💤 Files with no reviewable changes (3)
  • src/com/serotonin/mango/web/dwr/XssDataPointBeanConverter.java
  • src/com/serotonin/mango/web/dwr/PointLinksDwr.java
  • src/com/serotonin/mango/web/dwr/XssDataPointVoConverter.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-06T22:12:13.613Z
Learnt from: Limraj
Repo: SCADA-LTS/Scada-LTS PR: 3239
File: WebContent/WEB-INF/jsp/eventHandlers.jsp:275-282
Timestamp: 2026-01-06T22:12:13.613Z
Learning: In WebContent/WEB-INF/jsp/eventHandlers.jsp, when editing an existing event handler in showHandlerEdit(), targetPoint being null/undefined is considered an invalid state (data integrity violation) because if a handler exists with a targetPointId, the corresponding point is guaranteed to exist in allPoints. The maintainer has asserted this architectural invariant.

Applied to files:

  • WebContent/WEB-INF/jsp/pointLinks.jsp
  • WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp
  • WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp
  • WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp
  • WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp
  • WebContent/WEB-INF/jsp/eventHandlers.jsp
  • WebContent/WEB-INF/jsp/compoundEvents.jsp
  • WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp
  • WebContent/WEB-INF/jsp/include/settingsEditor.jsp
🧬 Code graph analysis (13)
src/com/serotonin/mango/vo/report/ReportVO.java (1)
src/org/scada_lts/utils/PathSecureUtils.java (2)
  • PathSecureUtils (25-325)
  • ValidationPaths (257-324)
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java (2)
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java (1)
  • RunWith (34-136)
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java (1)
  • RunWith (7-44)
src/org/scada_lts/web/security/dwr/XssBeanConverter.java (1)
src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java (1)
  • XssBeanConverterUtils (20-218)
src/br/org/scadabr/view/component/LinkComponent.java (1)
scadalts-ui/src/utils/common.js (1)
  • escapeHtml (85-90)
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java (1)
src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java (1)
  • XssBeanConverterUtils (20-218)
src/com/serotonin/mango/vo/event/ScheduledEventVO.java (2)
src/org/scada_lts/web/security/XssProtectUtils.java (1)
  • XssProtectUtils (8-54)
scadalts-ui/src/utils/common.js (1)
  • unescapeHtml (60-64)
src/org/scada_lts/utils/PathSecureUtils.java (1)
src/com/serotonin/mango/util/LoggingUtils.java (1)
  • LoggingUtils (34-300)
src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java (2)
src/com/serotonin/mango/util/LoggingUtils.java (1)
  • LoggingUtils (34-300)
src/org/scada_lts/web/security/XssProtectUtils.java (1)
  • XssProtectUtils (8-54)
src/org/scada_lts/mango/service/SystemSettingsService.java (3)
src/org/scada_lts/utils/PathSecureUtils.java (2)
  • PathSecureUtils (25-325)
  • ValidationPaths (257-324)
src/org/scada_lts/utils/SystemSettingsUtils.java (1)
  • SystemSettingsUtils (25-817)
src/org/scada_lts/dao/SystemSettingsDAO.java (1)
  • SystemSettingsDAO (50-496)
src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java (1)
src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java (1)
  • XssBeanConverterUtils (20-218)
src/com/serotonin/mango/vo/event/MaintenanceEventVO.java (2)
src/org/scada_lts/web/security/XssProtectUtils.java (1)
  • XssProtectUtils (8-54)
scadalts-ui/src/utils/common.js (1)
  • unescapeHtml (60-64)
src/org/scada_lts/web/security/XssProtectUtils.java (1)
scadalts-ui/src/utils/common.js (2)
  • escapeHtml (85-90)
  • unescapeHtml (60-64)
test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java (1)
src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java (1)
  • XssBeanConverterUtils (20-218)
🔇 Additional comments (48)
src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java (1)

41-46: Good consistency improvement, but creates redundancy in callers.

The change makes selectProfiles consistent with selectUserProfileByUserId (lines 35-37) and ensures fresh VO instances are returned, which aligns with the XSS protection work in this PR. However, this introduces redundant wrapping in selectProfileById and selectProfileByXid (already flagged above).

WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp (1)

818-844: Improved error handling and user experience.

The new implementation properly handles the user flow by:

  • Disabling controls and showing a spinner during the operation
  • Validating input before making the AJAX call (early return pattern)
  • Re-enabling controls on both success and error paths

This is a good improvement over the previous immediate AJAX call approach.

src/org/scada_lts/utils/PathSecureUtils.java (1)

73-76: LGTM: null-guards added to contain failures from normalizePath

The added normalizedPath == null checks in normalizePath(Path, ...), getAbsoluteResourcePath, and the path != null guard in image-path collection correctly prevent downstream NPEs and avoid adding invalid paths.

Also applies to: 192-203, 210-226

WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp (1)

129-131: LGTM! Proper HTML unescaping for form field population.

The use of unescapeHtml() is appropriate here. Server-side data is HTML-escaped for storage, so unescaping when populating editable form fields ensures users see the original values they entered.

WebContent/WEB-INF/jsp/watchList.jsp (2)

123-123: LGTM! Proper unescaping for point names.

The unescapeHtml() call correctly reverses server-side HTML escaping, and the value is later safely assigned via textContent (line 350), which prevents XSS.


661-664: LGTM! Proper output escaping with c:out.

The use of <c:out> for JSP EL expressions in HTML attributes correctly prevents XSS injection.

WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp (1)

31-31: LGTM! Correct round-trip HTML escaping.

Line 31 correctly unescapes server data when loading, and line 79 re-escapes when rendering to HTML. This pattern prevents both double-escaping (which would show HTML entities to users) and XSS injection.

Also applies to: 79-79

WebContent/WEB-INF/jsp/scripting.jsp (1)

121-123: LGTM! Proper HTML unescaping for form fields.

The use of unescapeHtml() correctly reverses server-side HTML escaping when populating editable form fields.

WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp (1)

185-187: LGTM! Proper HTML unescaping for form field population.

The selective use of unescapeHtml() for string fields (parameterName, binary0Value) while leaving the numeric field (dataTypeId) untouched is correct and consistent with the XSS protection framework.

WebContent/WEB-INF/tags/page.tag (2)

33-35: LGTM: Proper XSS protection in page title.

Using <c:out> correctly escapes the instanceDescriptionHeader value, preventing potential XSS attacks through malicious instance descriptions.


223-225: LGTM: Consistent XSS protection in link text.

The same escaping pattern is correctly applied to the visible link text, maintaining consistency with the title section.

src/org/scada_lts/web/security/XssProtectUtils.java (3)

10-23: LGTM: Well-organized centralized entity mapping.

The static map provides a clean, maintainable way to handle newline and whitespace character escaping. The non-overlapping entity codes ensure that iteration order doesn't affect correctness.


27-31: LGTM: Correct escaping order maintained.

The refactored implementation correctly applies standard HTML escaping before custom newline/whitespace escaping, ensuring proper handling of special characters.


33-53: LGTM: Correct bidirectional escape/unescape implementation.

The new unescapeHtml method properly reverses the escaping order (custom entities first, then standard HTML), ensuring round-trip correctness. The map-driven approach maintains consistency between escaping and unescaping logic.

src/br/org/scadabr/view/component/LinkComponent.java (1)

52-60: LGTM: Proper XSS protection for link component.

The implementation correctly escapes both the href attribute and link text before HTML construction, with proper null handling. This prevents XSS attacks through malicious link or text content.

WebContent/WEB-INF/jsp/include/settingsEditor.jsp (1)

157-164: No action needed. The unescapeHtml function is correctly available in scope.

The function is defined in WebContent/resources/common.js (lines 1314-1318) and is loaded globally via page.tag (line 96) before any JSP that includes settingsEditor.jsp runs. The inline script comment at line 74 appropriately documents this dependency.

src/com/serotonin/mango/vo/event/ScheduledEventVO.java (1)

143-200: Verify that LocalizableMessage.getLocalizedMessage() properly escapes parameters when rendering.

The code unescapes alias, activeCron, and inactiveCron before passing them as parameters to LocalizableMessage. This pattern assumes LocalizableMessage.getLocalizedMessage() escapes these parameters during rendering. If it does not, unescaped user-controlled values could be rendered unsafely, reintroducing XSS vulnerabilities.

This approach is used consistently in both ScheduledEventVO and MaintenanceEventVO. The security of this pattern depends entirely on the external seroUtils.jar library properly escaping parameters during message localization and rendering.

WebContent/WEB-INF/jsp/usersProfiles.jsp (1)

151-151: LGTM!

The unescaping is correctly applied before populating the input field. Since HTML input fields automatically handle text content safely, unescaping the server-escaped value here is the right approach.

WebContent/WEB-INF/jsp/compoundEvents.jsp (1)

99-99: LGTM!

Correctly unescaping the HTML-escaped backend value before populating the input field. This follows the established pattern and maintains security.

WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp (2)

30-30: LGTM!

Correctly unescaping the HTML-escaped value when building the allPoints array. This ensures the data is in its original form for processing and display in select options.


80-80: Good security pattern: Escaping before innerHTML assignment.

This demonstrates the correct approach: explicitly escaping user-controlled content before rendering via innerHTML. This prevents XSS while allowing safe HTML structure in the table cell.

WebContent/WEB-INF/jsp/eventHandlers.jsp (2)

284-290: Alias unescape for an input field is reasonable here.
(Just ensure unescapeHtml is null-safe as suggested elsewhere.)


223-240: Verify handler data structure and references. The review identifies unescapeHtml(handler.message) as vulnerable, but this field doesn't exist in the EventHandlerVO serialization; only alias, id, handlerType, and disabled are present in the handler response. Confirm whether this is dead code, a bug, or whether the intended field was handler.alias (which is used correctly elsewhere with $set).

Likely an incorrect or invalid review comment.

build.gradle (1)

186-252: Both test suite classes resolve correctly:

  • org.scada_lts.web.beans.validation.css.CssValidatorTestsSuite exists at test/org/scada_lts/web/beans/validation/css/CssValidatorTestsSuite.java
  • org.scada_lts.web.security.dwr.XssBeanConverterUtilsTestsSuite exists at test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java

No issues found with the test filter configuration.

src/org/scada_lts/web/security/dwr/XssBeanConverter.java (1)

9-22: Clean implementation of XSS-safe DWR converter.

The converter correctly escapes outbound data (server→client) and unescapes inbound data (client→server), delegating marshalling to the parent BeanConverter. The exception types are compatible since ScadaMarshallException extends MarshallException.

scadalts-ui/src/views/System/SystemSettings/index.vue (2)

185-201: Appropriate use of EscapedTextarea for XSS-safe input.

Switching to EscapedTextarea for fields that may contain escaped HTML content is consistent with the PR's escaping improvements. The rows, auto-grow, and @focusout bindings are correctly preserved.


299-305: LGTM!

The import and component registration are correctly structured.

WebContent/WEB-INF/jsp/reports.jsp (1)

206-216: Good use of encodeURIComponent for URL path encoding.

Properly encoding the name in URLs prevents path traversal and special character issues in filenames.

src/com/serotonin/mango/vo/event/EventTypeVO.java (1)

34-36: LGTM!

The EmptyInstance implementation enables the XSS converter utilities to instantiate EventTypeVO during object conversion. Using -1 sentinel values is consistent with the existing constructor patterns in this class.

Also applies to: 177-181

WebContent/WEB-INF/jsp/publisherList.jsp (1)

37-39: No action required. The unescapeHtml function is properly available through resources/common.js, which is included globally via the page.tag file used by this JSP. The function is already in use elsewhere in the codebase with no issues.

Likely an incorrect or invalid review comment.

src/com/serotonin/mango/vo/event/MaintenanceEventVO.java (2)

34-34: LGTM!

The static import is appropriate for the unescaping operations added in this file.


310-346: Incorrect assumption: fields are not stored in escaped form.

Verification shows no HTML escaping occurs when persisting these fields to the database. The MaintenanceEventsDwr class passes raw parameters directly to setters (lines 109, 119, 126), and the DAO persists them without escaping. The unescapeHtml() calls in getDescription() lack a corresponding escaping step at storage time, suggesting either:

  • Unnecessary defensive unescaping that masks a real escaping gap, or
  • Missing escaping logic in the persistence layer

The valid concerns remain:

  1. Null handling: unescapeHtml(null) converts to "", not null, which could affect message formatting if null is meaningful.
  2. Performance: getDescription() unescapes on every call; caching after first access could help if this method is called frequently.

Recommend clarifying the escaping strategy: either remove the unescaping (if fields genuinely aren't escaped), or add escaping at the storage layer to match the retrieval logic.

Likely an incorrect or invalid review comment.

WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp (1)

123-123: LGTM!

The escaping of data.key and data.value when rendering table rows correctly prevents XSS attacks.

Also applies to: 167-167

test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java (1)

1-13: LGTM!

The test suite correctly aggregates the three test classes using JUnit 4's @RunWith(Suite.class) pattern. This provides a convenient entry point for running all XssBeanConverterUtils-related tests.

src/com/serotonin/mango/vo/DataPointVO.java (3)

53-53: LGTM!

The import is necessary for implementing the EmptyInstance interface.


66-66: LGTM!

Adding the EmptyInstance interface is consistent with the broader PR pattern of enabling empty-instance creation across VO/Bean types for DWR converter support.


1170-1173: LGTM!

The newInstanceEmpty() implementation correctly returns a new DataPointVO with sentinel values (-1) for the required constructor parameters, providing a valid empty instance for the converter framework.

scadalts-ui/src/components/datasources/MetaDataSource/point.vue (2)

319-328: LGTM - EscapedTextarea integration for XSS protection.

The replacement of v-textarea with EscapedTextarea properly handles HTML-escaped script content. The component maintains all necessary bindings (validation rules, model, ref, events).

Minor note: rows=3 works but :rows="3" would be more explicit for numeric prop binding in Vue.


387-392: LGTM - Proper component import and registration.

The EscapedTextarea component is correctly imported and registered alongside existing components.

WebContent/WEB-INF/jsp/publisherEdit.jsp (2)

98-98: LGTM - Proper XSS protection with <c:out> escaping.

Using <c:out> to escape the publisher name prevents XSS attacks from user-supplied content in form field values.


106-144: Consistent escaping applied across all form fields.

All dynamic values (xid, enabled, cacheWarningSize, changesOnly, sendSnapshot, snapshotSendPeriods, snapshotSendPeriodType) are properly escaped with <c:out>, ensuring consistent XSS protection throughout the form.

scadalts-ui/src/apps/App.vue (2)

21-22: LGTM - Safe unescaping with v-text binding.

Using v-text with unescapeVueHtml() is the correct approach—it decodes HTML entities for display while treating the result as text content (not HTML), preventing XSS. This properly complements the server-side escaping.


88-88: Proper import and method exposure for template usage.

The unescapeVueHtml utility is correctly imported and exposed in methods, making it available for template bindings.

Also applies to: 178-178

WebContent/WEB-INF/jsp/mailingLists.jsp (2)

50-50: LGTM - Proper unescaping for form field display.

The mailing list name is correctly unescaped before setting it in the form field, complementing server-side escaping.


101-104: LGTM - Usernames properly unescaped for dropdown display.

Using function accessors with unescapeHtml() for the username display ensures escaped server data is rendered correctly in the select options.

scadalts-ui/src/components/common/EscapedTextarea.vue (1)

1-51: Well-designed wrapper component for escaped content handling.

The EscapedTextarea component correctly:

  • Decodes HTML entities for display using unescapeVueHtml
  • Emits raw user input (server handles escaping on save)
  • Forwards attributes and non-input listeners to the inner v-textarea
  • Provides a validate() method that delegates to the inner component for form validation support
  • Handles null values gracefully in both directions

The inheritAttrs: false with explicit attribute forwarding is the correct Vue 2.x pattern for transparent wrapper components.

scadalts-ui/src/views/DataObjects/Scripting/index.vue (1)

88-89: Verify EscapedTextarea v-model semantics + prop passthrough (rows, autoGrow) before relying on it.
If EscapedTextarea emits escaped text, you likely need to unescape both name and script before persisting; if it emits raw text, then unescapeVueHtml() on save could corrupt intended literals like &lt;. Please confirm component behavior.

Also applies to: 136-144, 201-207

WebContent/WEB-INF/dwr.xml (1)

23-27: Please verify DWR converter resolution order/precedence with the added xssBean mappings.
If DWR applies the first matching converter (rather than “most specific”), earlier wildcard mappings like converter="bean" match="com.serotonin.mango.vo.event.handlers.*" could prevent the later xssBean entries from ever running. This would reintroduce “some objects escaped, some not” regressions.

Also applies to: 113-115, 141-148, 151-164, 174-184, 192-195, 218-220, 242-262, 267-270

Comment on lines +877 to +878
if(!PathSecureUtils.ValidationPaths.validateFilename(name + ".csv") || name.contains(";"))
response.addContextualMessage("name", "validate.invalidValue");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix potential NPE + encoded semicolon bypass in report name validation

name.contains(";") will throw when name is null (and name + ".csv" can become "null.csv" and pass). Also, %3B bypasses the semicolon check since only the decoded form contains ;.

Proposed fix
     public void validateRun(DwrResponseI18n response, User user) {
         if (StringUtils.isEmpty(name))
             response.addContextualMessage("name", "reports.validate.required");
         if (StringUtils.isLengthGreaterThan(name, 100))
             response.addContextualMessage("name", "reports.validate.longerThan100");
-        if(!PathSecureUtils.ValidationPaths.validateFilename(name + ".csv") || name.contains(";"))
-            response.addContextualMessage("name", "validate.invalidValue");
+        if (!StringUtils.isEmpty(name)) {
+            try {
+                String decodedName = PathSecureUtils.decodePath(name);
+                if (!PathSecureUtils.ValidationPaths.validateFilename(name + ".csv") || decodedName.contains(";")) {
+                    response.addContextualMessage("name", "validate.invalidValue");
+                }
+            } catch (Exception ex) {
+                // treat decode/validation failures as invalid input
+                response.addContextualMessage("name", "validate.invalidValue");
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(!PathSecureUtils.ValidationPaths.validateFilename(name + ".csv") || name.contains(";"))
response.addContextualMessage("name", "validate.invalidValue");
public void validateRun(DwrResponseI18n response, User user) {
if (StringUtils.isEmpty(name))
response.addContextualMessage("name", "reports.validate.required");
if (StringUtils.isLengthGreaterThan(name, 100))
response.addContextualMessage("name", "reports.validate.longerThan100");
if (!StringUtils.isEmpty(name)) {
try {
String decodedName = PathSecureUtils.decodePath(name);
if (!PathSecureUtils.ValidationPaths.validateFilename(name + ".csv") || decodedName.contains(";")) {
response.addContextualMessage("name", "validate.invalidValue");
}
} catch (Exception ex) {
// treat decode/validation failures as invalid input
response.addContextualMessage("name", "validate.invalidValue");
}
}
}
🤖 Prompt for AI Agents
In @src/com/serotonin/mango/vo/report/ReportVO.java around lines 877 - 878,
Avoid NPE and encoded-semicolon bypass by treating null names and decoding
URL-encoded input before validation: replace the direct use of name in the
validateFilename and contains checks with a safe, decoded value (e.g., safeName
= name == null ? "" : name; decodedName = URLDecoder.decode(safeName,
StandardCharsets.UTF_8)); use validateFilename(decodedName + ".csv") and check
decodedName for ';' (and optionally check for the encoded form "%3B"
case-insensitively) before calling response.addContextualMessage("name",
"validate.invalidValue"); ensure you never concatenate a literal "null" into the
filename and handle UnsupportedEncodingException if not using StandardCharsets.

Comment on lines 58 to 65
public static Path normalizePath(String path) {
return Paths.get(normalizeSeparator(path)).toFile().getAbsoluteFile().toPath().normalize();
try {
return Paths.get(normalizeSeparator(path)).toFile().getAbsoluteFile().toPath().normalize();
} catch (Exception exception) {
LOG.error(LoggingUtils.exceptionInfo(exception));
return null;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n --type=java -C2 'PathSecureUtils\.normalizePath\s*\('

Repository: SCADA-LTS/Scada-LTS

Length of output: 1213


🏁 Script executed:

sed -n '100,115n' test/org/scada_lts/utils/UploadBackgroundFileUtilsTest.java

Repository: SCADA-LTS/Scada-LTS

Length of output: 45


🏁 Script executed:

cat -n test/org/scada_lts/utils/UploadBackgroundFileUtilsTest.java | sed -n '100,115p'

Repository: SCADA-LTS/Scada-LTS

Length of output: 707


🏁 Script executed:

# Let's search for any other potential call sites more broadly
rg -n --type=java 'normalizePath' | grep -v 'public static Path normalizePath' | grep -v '//'

Repository: SCADA-LTS/Scada-LTS

Length of output: 1150


Unsafe null handling confirmed in test file call site

normalizePath(String) returns null on error, but UploadBackgroundFileUtilsTest.java:106 calls .toString() on the result without null-checking, which will throw NullPointerException if the path normalization fails. The method at PathSecureUtils.java:193 does handle the null return safely with an explicit check. Either fix the test site to null-check, or refactor to use Optional<Path> to enforce null-safety at compile time.

🤖 Prompt for AI Agents
In @src/org/scada_lts/utils/PathSecureUtils.java around lines 58 - 65,
normalizePath currently returns null on failure which causes a
NullPointerException at call sites like UploadBackgroundFileUtilsTest where
.toString() is invoked without a null check; change the API to return
Optional<Path> instead: update normalizePath(String) to return Optional<Path>,
wrap successful result in Optional.of(path) and return Optional.empty() on
exception (logging the error), then update all callers (notably
UploadBackgroundFileUtilsTest) to consume the Optional safely (e.g., use
map(Path::toString).orElseThrow(...) or orElse/ifPresent) so no call invokes
methods on a possibly-null Path.

Comment on lines +26 to +36
public static Object convertObjectEscaped(Object value) throws ScadaMarshallException {
return doConvertIf(value, isSimpleJavaType().negate(),
XssBeanConverterUtils::doConvert,
object -> convertObject(object, XssBeanConverterUtils::escapeIfString));
}

public static Object convertObjectUnescaped(Object value) throws ScadaMarshallException {
return doConvertIf(value, isSimpleJavaType().negate(),
XssBeanConverterUtils::doConvert,
object -> convertObject(object, XssBeanConverterUtils::unescapeIfString));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Handle null and “simple” types without throwing (current code can NPE / break callers).
convertObjectEscaped/Unescaped(null) will NPE, and passing a String/primitive wrapper throws ScadaMarshallException (but some call sites appear to want string unescape). Safer behavior: null -> null, simple types -> return as-is, String -> escape/unescape.

Proposed fix
 public static Object convertObjectEscaped(Object value) throws ScadaMarshallException {
-    return doConvertIf(value, isSimpleJavaType().negate(),
-            XssBeanConverterUtils::doConvert,
-            object -> convertObject(object, XssBeanConverterUtils::escapeIfString));
+    if (value == null) return null;
+    if (value instanceof String) return XssProtectUtils.escapeHtml((String) value);
+    if (isSimpleJavaType().test(value)) return value;
+    return doConvertIf(value, o -> true,
+            XssBeanConverterUtils::doConvert,
+            object -> convertObject(object, XssBeanConverterUtils::escapeIfString));
 }
 
 public static Object convertObjectUnescaped(Object value) throws ScadaMarshallException {
-    return doConvertIf(value, isSimpleJavaType().negate(),
-            XssBeanConverterUtils::doConvert,
-            object -> convertObject(object, XssBeanConverterUtils::unescapeIfString));
+    if (value == null) return null;
+    if (value instanceof String) return XssProtectUtils.unescapeHtml((String) value);
+    if (isSimpleJavaType().test(value)) return value;
+    return doConvertIf(value, o -> true,
+            XssBeanConverterUtils::doConvert,
+            object -> convertObject(object, XssBeanConverterUtils::unescapeIfString));
 }

Also applies to: 38-53, 55-58

🤖 Prompt for AI Agents
In @src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java around lines
26 - 36, convertObjectEscaped and convertObjectUnescaped (and similar blocks)
can NPE or throw for simple types; update doConvertIf usage to first return null
for null input, return simple types unchanged, and apply escape/unescape only
for Strings. Specifically, in convertObjectEscaped/convertObjectUnescaped and
the other occurrences around
doConvertIf/isSimpleJavaType/doConvert/convertObject, add a null check and a
simple-type branch that returns the original value (but for instance-of String,
call escapeIfString/unescapeIfString), ensuring doConvert is only invoked for
complex types.

Comment on lines +60 to +88
private static Object doConvert(Object originObject, Function<Object, Object> convert) {
Object object;
try {
object = newInstanceEmpty(originObject);
} catch (Exception e) {
throw new RuntimeException(new ScadaMarshallException(originObject.getClass(), e));
}
List<Field> fields = getAllFields(originObject);
for(Field field: fields) {
if(!isStatic(field.getModifiers())) {
try {
field.setAccessible(true);
Object value = field.get(originObject);
if(value != null) {
Object converted = convert.apply(value);
if(converted != null && !field.isAnnotationPresent(NoEscape.class))
field.set(object, converted);
else
field.set(object, value);
}
} catch (Exception e) {
throw new RuntimeException(new ScadaMarshallException(originObject.getClass(), e));
} finally {
field.setAccessible(false);
}
}
}
return object;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Conversion is shallow (nested beans won’t be escaped/unescaped) — likely defeats the goal for many VO graphs.
For any field/collection element that is a complex object (not String/collection/map/array), convertObject() returns it unchanged, so its internal String fields remain unprocessed. If you intended deep conversion, you’ll need recursion + cycle detection (e.g., IdentityHashMap visited).

Also applies to: 108-131

🤖 Prompt for AI Agents
In @src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java around lines
60 - 88, doConvert does only shallow conversion — nested bean fields and
collection/map/array elements are left unprocessed; implement deep conversion by
recursively applying the conversion to complex values: for
non-primitive/non-String objects, call doConvert (or convertObject) on the
nested object, and for arrays/Collections/Maps iterate their elements and
convert each element/value, creating new containers as needed. Add cycle
detection using an IdentityHashMap<Object, Object> visited map passed through
recursive calls to avoid infinite loops and reuse created instances. Preserve
NoEscape handling on fields and ensure field.set uses the converted nested
instance when available; apply the same recursive logic to the similar code at
the other location referenced (lines 108-131).

Comment on lines +68 to +85
for(Field field: fields) {
if(!isStatic(field.getModifiers())) {
try {
field.setAccessible(true);
Object value = field.get(originObject);
if(value != null) {
Object converted = convert.apply(value);
if(converted != null && !field.isAnnotationPresent(NoEscape.class))
field.set(object, converted);
else
field.set(object, value);
}
} catch (Exception e) {
throw new RuntimeException(new ScadaMarshallException(originObject.getClass(), e));
} finally {
field.setAccessible(false);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t swallow conversion failures (returning null hides security gaps).
Returning null on conversion errors means fields fall back to original (unescaped) values with only a log line (without stacktrace). For XSS protection utilities, “fail open” is risky; prefer throwing ScadaMarshallException (or at least logging with stacktrace + explicit fallback choice).

Also applies to: 148-151, 165-168, 182-185, 197-200

🤖 Prompt for AI Agents
In @src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java around lines
68 - 85, The conversion loop currently swallows conversion failures by returning
null (causing the original unescaped value to be used) and/or only logging;
change the logic so any exception or failed conversion during convert.apply(...)
results in throwing a ScadaMarshallException (wrap underlying exception and
include stacktrace) instead of falling back silently — update the try/catch in
the loop around field.setAccessible(...) / convert.apply(...) to rethrow new
ScadaMarshallException(originObject.getClass(), e) (or throw a RuntimeException
wrapping that) and remove the silent-null fallback; apply the same change to the
other similar blocks referenced (lines ~148-151, 165-168, 182-185, 197-200) and
ensure NoEscape handling remains only for successfully converted non-null
results.

for (var i=0; i<list.length; i++)
allPoints[allPoints.length] = {
id: list[i].id, name: list[i].extendedName, enabled: list[i].enabled, type: list[i].dataTypeMessage};
id: list[i].id, name: unescapeHtml(list[i].extendedName), enabled: list[i].enabled, type: unescapeHtml(list[i].dataTypeMessage)};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find PublisherEditDwr.java and examine the initSender method
fd -type f -name '*PublisherEditDwr.java' --exec cat -n {} \;

Repository: SCADA-LTS/Scada-LTS

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Also examine the editHttpSender.jsp file, particularly around the lines mentioned (38, 43, 48, 53, 70, 214-218)
fd -type f -name 'editHttpSender.jsp' --exec wc -l {} \;

Repository: SCADA-LTS/Scada-LTS

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Find the JSP file and read the relevant sections
fd -type f -name 'editHttpSender.jsp' --exec cat -n {} \; | head -250

Repository: SCADA-LTS/Scada-LTS

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Find PublisherEditDwr.java
fd PublisherEditDwr.java

Repository: SCADA-LTS/Scada-LTS

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Find editHttpSender.jsp
fd editHttpSender.jsp

Repository: SCADA-LTS/Scada-LTS

Length of output: 118


🏁 Script executed:

#!/bin/bash
# Search for initSender method in Java files
rg -n -A 30 'public.*initSender' --type-list

Repository: SCADA-LTS/Scada-LTS

Length of output: 452


🏁 Script executed:

#!/bin/bash
# Simpler search for initSender
rg -n 'initSender' -A 20

Repository: SCADA-LTS/Scada-LTS

Length of output: 8503


🏁 Script executed:

#!/bin/bash
# Read the entire editHttpSender.jsp file
cat -n WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp

Repository: SCADA-LTS/Scada-LTS

Length of output: 24838


🏁 Script executed:

#!/bin/bash
# Search for DwrResponseI18n to understand escaping
rg -n 'DwrResponseI18n' -A 10 -B 5

Repository: SCADA-LTS/Scada-LTS

Length of output: 50376


🏁 Script executed:

#!/bin/bash
# Check if there's any XssBeanConverter or escaping applied
rg -n 'XssBeanConverter|escapeHtml|unescapeHtml' WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp

Repository: SCADA-LTS/Scada-LTS

Length of output: 1132


🏁 Script executed:

#!/bin/bash
# Check the data flow - where pointType comes from
grep -n "pointType" WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp

Repository: SCADA-LTS/Scada-LTS

Length of output: 191


🏁 Script executed:

#!/bin/bash
# Search for escapeHtml utility to understand its implementation
rg -n "function.*escapeHtml|escapeHtml\s*=" --context 3

Repository: SCADA-LTS/Scada-LTS

Length of output: 7460


Wrap data.pointType with escapeHtml() to prevent XSS.

Line 216 outputs data.pointType in a span element without escaping, creating a cross-site scripting vulnerability. The value comes from list[i].dataTypeMessage which is HTML-escaped by the server (line 38), unescaped for storage in the JavaScript object (line 38: type: unescapeHtml(list[i].dataTypeMessage)), and then rendered directly to the DOM without re-escaping.

Compared to the adjacent pointName field on line 214 which correctly uses escapeHtml(data.pointName), the pointType output must be similarly escaped before DOM insertion.

Fix required at line 216:
// Before:
function(data) { return "<span>" + data.pointType + "</span>"; },

// After:
function(data) { return "<span>" + escapeHtml(data.pointType) + "</span>"; },

Also applies to: editPersistent.jsp line 81, editPachube.jsp line 82 (both render data.pointType without escaping).

🤖 Prompt for AI Agents
In @WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp at line 38, The code
constructs objects with type: unescapeHtml(list[i].dataTypeMessage) and later
renders data.pointType directly into the DOM, creating an XSS risk; update the
renderer that returns "<span>" + data.pointType + "</span>" to instead call
escapeHtml(data.pointType) (i.e., wrap data.pointType with escapeHtml) so the
value is safely escaped before insertion; apply the same fix to the equivalent
renderers in editPersistent.jsp and editPachube.jsp that output data.pointType.

Comment on lines +214 to +218
function(data) { return "<span>" + escapeHtml(data.pointName) + "</span>"; },
function(data) { return "<img src='images/"+ (data.enabled ? "brick_go" : "brick_stop") +".png'/>"; },
function(data) { return data.pointType; },
function(data) { return "<span>" + data.pointType + "</span>"; },
function(data) {
return "<input type='text' value='"+ data.parameterName +"' "+
return "<input type='text' value='"+ escapeHtml(data.parameterName) +"' "+
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing escaping for pointType on Line 216.

Line 214 correctly escapes data.pointName, and Line 218 escapes data.parameterName, but Line 216 renders data.pointType without escaping. If pointType contains user-controlled or untrusted content, this could create an XSS vulnerability.

🔒 Proposed fix
-                 function(data) { return "<span>" + data.pointType + "</span>"; },
+                 function(data) { return "<span>" + escapeHtml(data.pointType) + "</span>"; },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function(data) { return "<span>" + escapeHtml(data.pointName) + "</span>"; },
function(data) { return "<img src='images/"+ (data.enabled ? "brick_go" : "brick_stop") +".png'/>"; },
function(data) { return data.pointType; },
function(data) { return "<span>" + data.pointType + "</span>"; },
function(data) {
return "<input type='text' value='"+ data.parameterName +"' "+
return "<input type='text' value='"+ escapeHtml(data.parameterName) +"' "+
function(data) { return "<span>" + escapeHtml(data.pointName) + "</span>"; },
function(data) { return "<img src='images/"+ (data.enabled ? "brick_go" : "brick_stop") +".png'/>"; },
function(data) { return "<span>" + escapeHtml(data.pointType) + "</span>"; },
function(data) {
return "<input type='text' value='"+ escapeHtml(data.parameterName) +"' "+
🤖 Prompt for AI Agents
In @WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp around lines 214 -
218, The renderer that returns "<span>" + data.pointType + "</span>" (the
anonymous function producing the pointType cell) does not escape untrusted
content; change it to use the existing escapeHtml helper (same as used for
data.pointName and data.parameterName) so the output becomes "<span>" +
escapeHtml(data.pointType) + "</span>" to prevent XSS.

Comment on lines 160 to +169
var result = "<img src='images/bullet_down.png' class='ptr' title='<spring:message code="reports.export"/>' "+
"onclick='exportData(\""+ escapeHtml(ri.name) +"\", "+ ri.id +")'/>";
"onclick='exportData(\""+ ri.name +"\", "+ ri.id +")'/>";

if (ri.includeEvents != <c:out value="<%= ReportVO.EVENTS_NONE %>"/>)
result += "<img src='images/flag_white.png' class='ptr' title='<spring:message code="reports.eventExport"/>' "+
"onclick='exportEventData(\""+ escapeHtml(ri.name) +"\", "+ ri.id +")'/>";
"onclick='exportEventData(\""+ ri.name +"\", "+ ri.id +")'/>";

if (ri.includeUserComments)
result += "<img src='images/comment.png' class='ptr' title='<spring:message code="reports.userCommentExport"/>' "+
"onclick='exportUserComments(\""+ escapeHtml(ri.name) +"\", "+ ri.id +")'/>";
"onclick='exportUserComments(\""+ ri.name +"\", "+ ri.id +")'/>";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Quote characters in ri.name can break onclick handlers or enable XSS.

If ri.name contains " or </script>, the inline JavaScript in onclick will break or become exploitable. Consider escaping the name for JavaScript string context or using a data attribute approach.

💡 Safer approach using data attributes

Instead of embedding the name directly in onclick, store it in a data attribute and retrieve it in the handler:

// In the row rendering function:
function(ri) { 
    var result = "<img src='images/bullet_down.png' class='ptr' title='<spring:message code="reports.export"/>' "+
            "data-name='"+ encodeURIComponent(ri.name) +"' data-id='"+ ri.id +"' onclick='exportDataFromElement(this)'/>";
    // ... rest of the code
}

// New handler function:
function exportDataFromElement(el) {
    var name = decodeURIComponent(el.getAttribute('data-name'));
    var id = el.getAttribute('data-id');
    exportData(name, id);
}
🤖 Prompt for AI Agents
In @WebContent/WEB-INF/jsp/reports.jsp around lines 160 - 169, The inline
onclick handlers embed ri.name directly which can break the JS string or enable
XSS; change the rendering to stop injecting raw ri.name into the onclick and
instead put the values into data attributes on the <img> (e.g., data-name and
data-id) and update the handlers to read from the element (create helper
functions like exportDataFromElement, exportEventDataFromElement,
exportUserCommentsFromElement that call
exportData/exportEventData/exportUserComments after decoding the data-name), or
alternatively properly escape ri.name for JS string context before embedding it.

Comment on lines 338 to 340
function updateReport(id, name) {
let escapedName = escapeHtml(name);
$("r"+ id +"Name").innerHTML = escapedName;
$("r"+ id +"Name").innerHTML = name;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential XSS vulnerability: raw name assigned to innerHTML.

Setting innerHTML directly with an unescaped name allows script injection if the name contains malicious HTML (e.g., <img onerror=alert(1)>). Use textContent instead or escape the value.

🔒 Proposed fix
     function updateReport(id, name) {
-        $("r"+ id +"Name").innerHTML = name;
+        $("r"+ id +"Name").textContent = name;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function updateReport(id, name) {
let escapedName = escapeHtml(name);
$("r"+ id +"Name").innerHTML = escapedName;
$("r"+ id +"Name").innerHTML = name;
}
function updateReport(id, name) {
$("r"+ id +"Name").textContent = name;
}
🤖 Prompt for AI Agents
In @WebContent/WEB-INF/jsp/reports.jsp around lines 338 - 340, The updateReport
function assigns untrusted name to DOM via $("r"+ id +"Name").innerHTML which
enables XSS; change the assignment to use textContent (i.e., $("r"+ id
+"Name").textContent = name) or run name through an HTML-escaping sanitizer
before assigning to innerHTML so the value is rendered as plain text rather than
executable HTML.

function updateUser(response) {
var user = response.data ? response.data.user : response.user;
$("u"+ user.id +"Username").textContent = user.username;
$("u"+ user.id +"Username").innerHTML = user.username;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Potential XSS vulnerability: Using innerHTML without explicit escaping.

Switching from textContent to innerHTML introduces XSS risk. If user.username contains malicious HTML or JavaScript, it will be executed in the browser context.

While the PR introduces XSS protection via backend converters, relying solely on backend escaping for innerHTML is risky:

  • If backend escaping is bypassed or incomplete, this becomes an XSS vector
  • If usernames are intentionally HTML-escaped on backend, innerHTML will display the escape sequences literally (e.g., &lt;script&gt;)
🔒 Recommended fix

Option 1 (preferred): Revert to textContent for safe text rendering

-        $("u"+ user.id +"Username").innerHTML = user.username;
+        $("u"+ user.id +"Username").textContent = user.username;

Option 2: If HTML content is intentional, explicitly escape

-        $("u"+ user.id +"Username").innerHTML = user.username;
+        $("u"+ user.id +"Username").innerHTML = escapeHtml(user.username);

Option 3: If displaying backend-escaped content, unescape first

-        $("u"+ user.id +"Username").innerHTML = user.username;
+        $("u"+ user.id +"Username").textContent = unescapeHtml(user.username);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$("u"+ user.id +"Username").innerHTML = user.username;
$("u"+ user.id +"Username").textContent = user.username;
🤖 Prompt for AI Agents
In @WebContent/WEB-INF/jsp/users.jsp at line 326, The line using $("u"+ user.id
+"Username").innerHTML = user.username; introduces an XSS risk by injecting raw
HTML; change to assign the username as plain text (use textContent or
equivalent) so the DOM receives untrusted data safely, i.e., replace the
innerHTML assignment with a text-safe assignment for the element referenced by
$("u"+ user.id +"Username"); if the value must contain trusted HTML, explicitly
sanitize or unescape it before inserting (perform sanitization at the client
using a vetted sanitizer or confirm backend-provided HTML is safe) and document
why innerHTML is necessary when touching the user.username insertion.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants