feat: Add customization URL parameter#5992
Conversation
❌ Deploy Preview for ohif-dev failed. Why did it fail? →
|
Viewers
|
||||||||||||||||||||||||||||
| Project |
Viewers
|
| Branch Review |
feat/customization-url-parameter
|
| Run status |
|
| Run duration | 02m 23s |
| Commit |
|
| Committer | Bill Wallace |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
37
|
| View all changes introduced in this branch ↗︎ | |
sedghi
left a comment
There was a problem hiding this comment.
I’m not fully convinced the added value of this PR justifies the new security surface yet. This introduces URL-driven runtime JavaScript loading via ?customization=, which means a shared link can change viewer behavior and, depending on deployment config, potentially load executable code into the same browser context as OHIF. The validation does block obvious arbitrary URLs and path traversal, so this is not an immediate “any URL can execute code” issue. But the security boundary becomes the configured customization prefix and whoever can publish files there. If that directory/CDN is writable by the wrong party, this could become XSS-equivalent: token/session access, DICOM metadata exposure, UI manipulation, report tampering, or authenticated API abuse from the victim’s browser.
My current view is that this level of runtime configurability may be better kept in downstream forks or deployment-specific builds, where the deploying team can own the threat model and hosting controls explicitly. I’m not sure it should become a default upstream capability.
If the CDN is writable by the wrong party, it doesn't matter what you do, they can replace the entire OHIF source control. At that point you are completely open. What about adding a user configuration option to specifically and manually add customization prefixes rather than allowing it to be done via customization? That way we can default to one customization deploy somewhere that we control for the demonstration deployments, making note that is intended for demo purposes only, and same-host http /customization/ prefix path options so that we can deploy with a fixed deployment? It is clear the advisory board wants SOMETHING that allows dynamic loading. I agree it needs to be controlled, but it also has to be external to the build process of OHIF, otherwise we will never meet the goals of allowing OHIF to be customized by non-developers. Some other things we could consider:
That value of this is clearly extremely high given how many people on the meeting wanted something better. The only question becomes how to make it reasonably safe. It isn't fully safe, but neither is OHIF in the current configuration. |
Context
In order to allow custom versions of OHIF to be defined/added without having to rebuild OHIF, it is necessary to have a customization framework that can load dynamic modules. This has been added as a customization= parameter.
Changes & Results
Added a customization handler for the customization= parameter
Added a requires= export in the loaded global customizations to allow customizations to depend on other ones, eg veterinary depends on veterinaryOverlay
Add an example customization to test with, the start of a veterinary example.
Testing
Open the horse example with customization=veterinary in the URL
You should see additional overlays added, without having to rebuild.
Checklist
PR
semantic-release format and guidelines.
Code
etc.)
Public Documentation Updates
additions or removals.
Tested Environment
Greptile Summary
This PR adds a
?customization=URL parameter that lets operators load runtime JavaScript customization modules (global overlay overrides, etc.) without a full rebuild. The feature includes allowlist-based URL resolution, depth-firstrequiresdependency chaining, page-lifetime deduplication, and aformatValueutility that fixes[object Object]rendering for DICOM PersonName attributes.CustomizationService.requires,validate.ts,resolve.ts): validates and resolves?customization=nameentries against configured prefixes, depth-first imports dependencies declared viarequires, and applies theirglobalpayloads toCustomizationScope.Global. Already-loaded modules are skipped for the page lifetime; the loader runs once at bootstrap, not on SPA navigation.initdeduplication: Extension default/global modules are now merged at most once per page session via_extensionCustomizationModuleApplied, preventing repeated immutability-helper merges on mode transitions.formatValuefixes the PersonName[object Object]display bug;preserveQueryParameterscarries thecustomizationkey across navigation witharrayFormat: 'repeat'to keepqs.stringifyoutput well-formed.Confidence Score: 4/5
Safe to merge with awareness of the open duplicate-key issue in preserveQueryParameters that can cause doubled query-string values during worklist navigation.
The core customization loader is well-tested with good security controls. The main remaining concern is in preserveQueryParameters where getPreserveKeys concatenates base and custom keys without deduplication, which can cause doubled query-string values during navigation.
platform/app/src/utils/preserveQueryParameters.ts and platform/core/src/utils/formatValue.js
Important Files Changed
Comments Outside Diff (1)
platform/core/src/services/CustomizationService/CustomizationService.ts, line 789-799 (link)_applyLoadedUrlCustomizationModulesonly appliespayload.global. If an author writes a URL customization module that contains noglobalkey, the module is imported and validated successfully but nothing is written to the service — with no warning. At a minimum a diagnostic should be logged whenpayload.globalis absent so misconfigured modules are not silently ignored.Prompt To Fix With AI
platform/core/src/services/CustomizationService/CustomizationService.ts, line 654-668 (link)_collectUrlDependencyFromValuewill attempt to URL-load any non-ohif.*customizationfield referenceWhen a loaded module contains entries like
{ customization: 'corn.overlayItem' }(referencing a cornerstone customization type),_urlDependencyToRequestonly skips names matching^ohif\.[…]$. All other dot-namespaced extension customization identifiers pass validation (normalized to/default/corn.overlayItem) and trigger a network import attempt. In non-strict mode this fails silently with a warning and a wasted request for every such reference.Prompt To Fix With AI
platform/core/src/services/CustomizationService/CustomizationService.ts, line 510-522 (link)applyWindowUrlCustomizationsis called once inappInit.js. Because_urlCustomizationLoadedis never cleared, if the user navigates to a URL with a different?customization=parameter during client-side routing, previously-loaded customizations remain applied and new ones are not picked up. This may be intentional, but it is a non-obvious behavioral limit worth documenting — especially since the companionpreserveQueryParameterschange explicitly preserves thecustomizationkey across navigations.Prompt To Fix With AI
platform/app/src/routes/WorkList/WorkList.tsx, line 203-208 (link)preserveQueryStringsnow returns arrays;qs.stringifywithoutarrayFormatwill produce broken URLspreserveQueryStringsnow stores every preserved key as an array (e.g.,{ configUrl: ['foo.js'] }), even when there is only one value.qs.stringifywith default options usesarrayFormat: 'indices', serialising that asconfigUrl[0]=foo.jsinstead ofconfigUrl=foo.js. Any consumer — the DICOM viewer, mode entry, or external tools — that parsesconfigUrlfrom the worklist navigation URL as a plain string key will either get nothing or a key namedconfigUrl[0]. Single-value preserved keys (configUrl,multimonitor,screenNumber,hangingProtocolId) were always strings before this PR; making them arrays without also specifying{ arrayFormat: 'repeat' }(or equivalent) on everyqs.stringifycall is a regression.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (9): Last reviewed commit: "Merge remote-tracking branch 'origin/mas..." | Re-trigger Greptile