feat: Add customization URL parameter#5992
Conversation
✅ Deploy Preview for ohif-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Viewers
|
||||||||||||||||||||||||||||
| Project |
Viewers
|
| Branch Review |
feat/customization-url-parameter
|
| Run status |
|
| Run duration | 02m 18s |
| 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 query parameter that dynamically loads JavaScript customization modules at runtime without rebuilding OHIF, including depth-first dependency resolution viarequires, per-page-session deduplication, and a veterinary overlay example.requires()/applyWindowUrlCustomizations()API (CustomizationService.ts): validates, resolves, and imports URL-based customization modules in dependency order; extension default modules are deduplicated acrossinit()calls via_extensionCustomizationModuleAppliedto avoid repeated console warnings.preserveQueryParametersrefactor (preserveQueryParameters.ts,WorkList.tsx): all preserved keys are now stored as arrays andqs.stringifygainsarrayFormat: 'repeat'so URLs remain well-formed; a previously-flagged duplicate-key issue when the service returns keys that overlap with the built-inpreserveKeyslist still needs a deduplication step.formatValueutility (formatValue.js, overlays): prevents[object Object]rendering of complex DICOM attribute values (PN objects, unknown types) by returningnullfor unrecognized inputs, improving overlay display correctness.Confidence Score: 4/5
The core URL-loading pipeline is well-tested and safe for the happy path; the outstanding deduplication bug in getPreserveKeys means overlapping custom keys can be doubled in worklist navigation URLs.
The getPreserveKeys function concatenates the built-in preserveKeys array with whatever the service returns without deduplicating overlapping entries, causing URLSearchParams.append to fire twice for overlapping keys. The validation, resolve, import, and dependency-ordering logic is solid and covered by unit and integration tests.
platform/app/src/utils/preserveQueryParameters.ts needs a deduplication pass in getPreserveKeys; platform/core/src/services/CustomizationService/resolve.ts warrants a guard or note for absolute PUBLIC_URL deployments.
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 (8): Last reviewed commit: "fix: PR comments" | Re-trigger Greptile