Skip to content

Improved: Sanitize all widget xml resource loading - #1586

Open
Krishnauprit18 wants to merge 1 commit into
apache:trunkfrom
Krishnauprit18:secure-widget-resource-loading
Open

Improved: Sanitize all widget xml resource loading#1586
Krishnauprit18 wants to merge 1 commit into
apache:trunkfrom
Krishnauprit18:secure-widget-resource-loading

Conversation

@Krishnauprit18

Copy link
Copy Markdown
Contributor

This PR builds upon PR #1552 by preserving WidgetSecureLocation architecture and introducing two Defense-in-Depth security add-ons in UtilValidate:

  1. Protocol-Layer Rejection (file:/): Extends isUrlInStringAndDoesNotStartByComponentProtocol to explicitly reject file:/ schemes at the entry point, preventing single-slash URL bypasses.

  2. Default-Deny Policy: Enforces an explicit default-deny check when allowFilePaths in security.properties is unconfigured or blank.

Copilot AI lite review requested due to automatic review settings August 7, 2026 11:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a centralized sanitizer (WidgetSecureLocation) intended to harden widget XML resource loading by only allowing component:// locations (without traversal) or explicitly permitted filesystem paths via security.properties.

Changes:

  • Added WidgetSecureLocation and integrated it into multiple widget factories before resolving/loading XML resources.
  • Added a new default-deny allowFilePaths property and implemented UtilValidate.isAllowedPath(...) to enforce it.
  • Updated one screen include to use component://... instead of a relative path.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
framework/widget/src/main/java/org/apache/ofbiz/widget/model/WidgetSecureLocation.java Adds a centralized location sanitizer for widget resource loading.
framework/widget/src/main/java/org/apache/ofbiz/widget/model/TreeFactory.java Uses sanitizer before resolving/loading tree XML resources.
framework/widget/src/main/java/org/apache/ofbiz/widget/model/ScreenFactory.java Uses sanitizer for referenced screen includes.
framework/widget/src/main/java/org/apache/ofbiz/widget/model/MenuFactory.java Uses sanitizer before resolving/loading menu XML resources.
framework/widget/src/main/java/org/apache/ofbiz/widget/model/GridFactory.java Uses sanitizer before resolving/loading grid XML resources.
framework/widget/src/main/java/org/apache/ofbiz/widget/model/FormFactory.java Uses sanitizer before resolving/loading form XML resources.
framework/security/config/security.properties Introduces allowFilePaths configuration knob (default blank).
framework/base/src/main/java/org/apache/ofbiz/base/util/UtilValidate.java Adds isAllowedPath and changes URL detection behavior used by loaders/sanitizer.
applications/commonext/widget/ofbizsetup/ProfileScreens.xml Migrates include-screen locations to component://....
Suppressed comments (4)

framework/widget/src/main/java/org/apache/ofbiz/widget/model/TreeFactory.java:65

  • After resolving a sanitized component:// location, treeFileUrl.toString() is usually file:/.... With the current isUrlInStringAndDoesNotStartByComponentProtocol behavior, that check can reject valid local file URLs and abort tree loading. Also, pass the sanitized (normalized) location through to the model to avoid cache/model inconsistencies.
            if (treeFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(treeFileUrl.toString())) {
                throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
            }
            Document treeFileDoc = UtilXml.readXmlDocument(treeFileUrl, true, true);
            modelTreeMap = readTreeDocument(treeFileDoc, delegator, dispatcher, resourceName);

framework/widget/src/main/java/org/apache/ofbiz/widget/model/MenuFactory.java:127

  • menuFileUrl.toString() for resolved component:// resources is typically file:/..., so using isUrlInStringAndDoesNotStartByComponentProtocol(...) here can reject valid local URLs. Use isUrlInString(...) (or only validate the original string before resolving) and propagate the sanitized location to downstream model parsing.
            if (menuFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(menuFileUrl.toString())) {
                throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
            }
            Document menuFileDoc = UtilXml.readXmlDocument(menuFileUrl, true, true);
            modelMenuMap = readMenuDocument(menuFileDoc, resourceName, visualTheme);

framework/widget/src/main/java/org/apache/ofbiz/widget/model/GridFactory.java:87

  • For sanitized component:// locations, gridFileUrl.toString() is generally file:/.... Using isUrlInStringAndDoesNotStartByComponentProtocol(...) here can therefore reject valid local URLs and abort grid loading. Also prefer using the sanitized/normalized location consistently in error messages and model creation.
            URL gridFileUrl = FlexibleLocation.resolveLocation(sanitizedLocation);
            if (gridFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(gridFileUrl.toString())) {
                throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
            }
            Document gridFileDoc = UtilXml.readXmlDocument(gridFileUrl, true, true);

framework/widget/src/main/java/org/apache/ofbiz/widget/model/FormFactory.java:85

  • For sanitized component:// locations, formFileUrl.toString() is generally file:/.... Using isUrlInStringAndDoesNotStartByComponentProtocol(...) can reject valid local URLs and abort form loading. Also use the sanitized/normalized location consistently in messages and model creation to avoid inconsistencies.
            URL formFileUrl = FlexibleLocation.resolveLocation(sanitizedLocation);
            if (formFileUrl == null || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(formFileUrl.toString())) {
                throw new IllegalArgumentException("Could not resolve location to URL: " + resourceName);
            }
            Document formFileDoc = UtilXml.readXmlDocument(formFileUrl, true, true);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 645 to 649
if (isEmpty(s) || s.startsWith("component://")) {
return false;
}
return s.indexOf("://") != -1;
return s.indexOf("://") != -1 || s.startsWith("file:/");
}
Comment on lines +663 to +684
private static Pattern initAllowedPathPattern() {
return Pattern.compile(UtilProperties.getPropertyValue("security", "allowFilePaths", ""));
}

/**
* isAllowedPath takes a String representing a filePath, normalizes it and checks it if allowed
* @param rawPathString
* @return true if it's an allowed path, false otherwise
*/
public static boolean isAllowedPath(String rawPathString) {
String allowPatternStr = UtilProperties.getPropertyValue("security", "allowFilePaths", "");
if (isEmpty(allowPatternStr)) {
return false;
}
if (allowedPathsPattern == null) {
allowedPathsPattern = initAllowedPathPattern();
}
return UtilValidate.isNotEmpty(rawPathString)
&& allowedPathsPattern.matcher(Paths.get(rawPathString)
.normalize().toString())
.matches();
}
Comment on lines +311 to +312
#-- RegExp for ofbiz to allow some file access denied by default like function to UtilValidate::isAllowedPath
allowFilePaths=
Comment on lines +30 to +46
public static String sanitize(String location) {
if (UtilValidate.isEmpty(location) || UtilValidate.isUrlInStringAndDoesNotStartByComponentProtocol(location)) {
Debug.logWarning(String.format("Unable to sanitize location: [%s]", location), MODULE);
return null;
}
if (location.startsWith(COMPO_TYPE) && location.length() > 12) {
if (location.indexOf("..") > 0) {
Debug.logWarning(String.format("For security raison traversal sequence '..' is not allowed : [%s]", location), MODULE);
return null;
}
return COMPO_TYPE + Paths.get(location.substring(12)).normalize();
}

return UtilValidate.isAllowedPath(location)
? location
: null;
}
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.

2 participants