Skip to content

Improved: Sanitize all widget xml resource loading - #1586

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

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;
}
@Krishnauprit18
Krishnauprit18 force-pushed the secure-widget-resource-loading branch from f204150 to 78eed22 Compare August 7, 2026 12:56
ashishvijaywargiya pushed a commit that referenced this pull request Aug 14, 2026
…cation resolution (#1624)

This PR builds on [PR
#1586](Krishnauprit18:secure-widget-resource-loading) and keeps the
WidgetSecureLocation approach, adding two related changes in
RequestHandler:

1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate
last view names against ControllerConfig before they are stored in the
session, so view navigation falls back cleanly.

2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters
dynamic resource and location parameters (*Location, *Screen, *Template,
*Uri) during session persistence and view-last attribute restoration.

Thank you Krishna for the contribution.
ashishvijaywargiya added a commit that referenced this pull request Aug 14, 2026
…owlist (#1650)

Add WidgetSecureLocation as a single gatekeeper for screen, form, grid,
menu, and tree resource locations. Reject any file: scheme location
regardless of letter case, reject '..' traversal inside component://
locations, and deny non-component locations by default unless allowed
via the new security.allowFilePaths pattern.

Also stop anonymous JSON request bodies from overriding request
attributes that already exist as trusted ServletContext attributes,
which is how a request-controlled value could shadow a webapp's
configured decorator location.

Adds unit tests for the new checks.

Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for
your help.

PR from @nmalin - #1552

PR from @Krishnauprit18 - #1586
ashishvijaywargiya pushed a commit to ashishvijaywargiya/ofbiz-framework that referenced this pull request Aug 14, 2026
…cation resolution (apache#1624)

This PR builds on [PR
apache#1586](Krishnauprit18:secure-widget-resource-loading) and keeps the
WidgetSecureLocation approach, adding two related changes in
RequestHandler:

1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate
last view names against ControllerConfig before they are stored in the
session, so view navigation falls back cleanly.

2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters
dynamic resource and location parameters (*Location, *Screen, *Template,
*Uri) during session persistence and view-last attribute restoration.

Thank you Krishna for the contribution.

(cherry picked from commit 9ca27f4)
ashishvijaywargiya added a commit to ashishvijaywargiya/ofbiz-framework that referenced this pull request Aug 14, 2026
…owlist (apache#1650)

Add WidgetSecureLocation as a single gatekeeper for screen, form, grid,
menu, and tree resource locations. Reject any file: scheme location
regardless of letter case, reject '..' traversal inside component://
locations, and deny non-component locations by default unless allowed
via the new security.allowFilePaths pattern.

Also stop anonymous JSON request bodies from overriding request
attributes that already exist as trusted ServletContext attributes,
which is how a request-controlled value could shadow a webapp's
configured decorator location.

Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for
your help.

PR from @nmalin - apache#1552

PR from @Krishnauprit18 - apache#1586

(cherry picked from commit 4afb9c9)
ashishvijaywargiya pushed a commit to ashishvijaywargiya/ofbiz-framework that referenced this pull request Aug 14, 2026
…cation resolution (apache#1624)

This PR builds on [PR
apache#1586](Krishnauprit18:secure-widget-resource-loading) and keeps the
WidgetSecureLocation approach, adding two related changes in
RequestHandler:

1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate
last view names against ControllerConfig before they are stored in the
session, so view navigation falls back cleanly.

2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters
dynamic resource and location parameters (*Location, *Screen, *Template,
*Uri) during session persistence and view-last attribute restoration.

Thank you Krishna for the contribution.

(cherry picked from commit 9ca27f4)
ashishvijaywargiya added a commit to ashishvijaywargiya/ofbiz-framework that referenced this pull request Aug 14, 2026
…owlist (apache#1650)

Add WidgetSecureLocation as a single gatekeeper for screen, form, grid,
menu, and tree resource locations. Reject any file: scheme location
regardless of letter case, reject '..' traversal inside component://
locations, and deny non-component locations by default unless allowed
via the new security.allowFilePaths pattern.

Also stop anonymous JSON request bodies from overriding request
attributes that already exist as trusted ServletContext attributes,
which is how a request-controlled value could shadow a webapp's
configured decorator location.

Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for
your help.

PR from @nmalin - apache#1552

PR from @Krishnauprit18 - apache#1586

(cherry picked from commit 4afb9c9)
ashishvijaywargiya added a commit that referenced this pull request Aug 14, 2026
…cation resolution (#1624) (#1653)

This PR builds on [PR
#1586](Krishnauprit18:secure-widget-resource-loading) and keeps the
WidgetSecureLocation approach, adding two related changes in
RequestHandler:

1. View state persistence check (_LAST_VIEW_NAME_): Validates candidate
last view names against ControllerConfig before they are stored in the
session, so view navigation falls back cleanly.

2. Parameter-level attribute filtering (_LAST_VIEW_PARAMS_): Filters
dynamic resource and location parameters (*Location, *Screen, *Template,
*Uri) during session persistence and view-last attribute restoration.

Thank you Krishna for the contribution.

(cherry picked from commit 9ca27f4)

Co-authored-by: Krishna Uprit <125099508+Krishnauprit18@users.noreply.github.com>
ashishvijaywargiya added a commit to ashishvijaywargiya/ofbiz-framework that referenced this pull request Aug 14, 2026
…owlist (apache#1650)

Add WidgetSecureLocation as a single gatekeeper for screen, form, grid,
menu, and tree resource locations. Reject any file: scheme location
regardless of letter case, reject '..' traversal inside component://
locations, and deny non-component locations by default unless allowed
via the new security.allowFilePaths pattern.

Also stop anonymous JSON request bodies from overriding request
attributes that already exist as trusted ServletContext attributes,
which is how a request-controlled value could shadow a webapp's
configured decorator location.

Thank you Krishna Uprit(@Krishnauprit18) and Nicolas Malin(@nmalin) for
your help.

PR from @nmalin - apache#1552

PR from @Krishnauprit18 - apache#1586

(cherry picked from commit 4afb9c9)
@ashishvijaywargiya

Copy link
Copy Markdown
Contributor

Hello @Krishnauprit18,

Your changes have been merged using this PR - #1654

Thank you for your kind support.

Please close this PR whenever you can.

Thanks,
Ashish

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