-
Notifications
You must be signed in to change notification settings - Fork 7
Convert OMM to OMM Plugin & Integrate Build Tool Functionality #363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: topic/omm-plugin
Are you sure you want to change the base?
Conversation
serve-instance.js
Outdated
| } | ||
|
|
||
| // Make a request to the target URL | ||
| const response = await fetch(targetUrl, fetchOptions); |
Check failure
Code scanning / CodeQL
Server-side request forgery Critical
URL
user-provided value
The
URL
user-provided value
The
URL
user-provided value
The
URL
user-provided value
The
URL
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
The best way to fix this SSRF is to restrict outbound requests so that only specific, pre-approved hosts or endpoints can be targeted. Do not allow the user to pick an arbitrary URL, especially in the hostname. Instead, maintain a list of allowed hosts or patterns, and use the user's input only to select among these, or to build paths on these hosts in a controlled way. You can safely handle query/path construction for known hosts, but for absolute URLs, you should parse and validate them against your allow-list before permitting any outbound request.
Specifically:
- Before making a request with
fetch(targetUrl, ...), parse the URL (new URL(targetUrl)) and check that the hostname matches an allowed set. - If the user requests a host not on the list, reject the request with an HTTP 400 or 403.
- The allow-list may be defined at the top of the file or as an environment variable, but it must not be influenced by user input.
- Perform this check just before the
fetchcall.
You'll need to:
- Import/use the standard
urlmodule to safely parse hostnames. - Add an allow-list (e.g., an array of allowed hosts like
['example.com', 'my-api.com', 'localhost']), configurable if necessary. - Validate the parsed hostname matches an entry before fetching.
- Respond with an error if not allowed.
-
Copy modified lines R26-R34 -
Copy modified lines R112-R130 -
Copy modified line R151
| @@ -23,6 +23,15 @@ | ||
|
|
||
| // Proxy configuration (matching webpack.dev.js) | ||
| const proxyUrl = process.env.PROXY_URL || 'http://localhost:8080'; | ||
|
|
||
| // SSRF protection: restrict allowed destination hostnames | ||
| // You may configure this list as needed for your deployment | ||
| const ALLOWED_PROXY_HOSTS = [ | ||
| 'localhost', | ||
| '127.0.0.1', | ||
| // Add any other allowed domains, e.g. 'api.mycompany.com', 'dev.example.com' | ||
| // 'example.com' | ||
| ]; | ||
| const apiUrl = process.env.API_URL || ''; | ||
| const proxyHeaders = {}; | ||
|
|
||
| @@ -100,7 +109,25 @@ | ||
|
|
||
| try { | ||
| console.log('Generic URL Proxy to:', targetUrl); | ||
|
|
||
|
|
||
| // SSRF protection: validate target URL host is allowed | ||
| let parsedTargetUrl; | ||
| try { | ||
| parsedTargetUrl = new URL(targetUrl); | ||
| } catch (e) { | ||
| console.error('Invalid target URL:', targetUrl, 'Error:', e); | ||
| return res.status(400).send('Invalid target URL.'); | ||
| } | ||
| const allowed = ALLOWED_PROXY_HOSTS.some((allowedHost) => { | ||
| // Allow both exact match and subdomains for allowed hosts | ||
| return parsedTargetUrl.hostname === allowedHost || | ||
| parsedTargetUrl.hostname.endsWith(`.${allowedHost}`); | ||
| }); | ||
| if (!allowed) { | ||
| console.error('Blocked SSRF attempt to host:', parsedTargetUrl.hostname); | ||
| return res.status(403).send('Proxying to this host is not allowed.'); | ||
| } | ||
|
|
||
| // Prepare headers - exclude headers that shouldn't be forwarded | ||
| const headersToForward = { ...proxyHeaders }; | ||
| const headersToExclude = ['host', 'connection', 'content-length', 'transfer-encoding']; | ||
| @@ -121,7 +148,7 @@ | ||
| fetchOptions.body = req.body; | ||
| } | ||
|
|
||
| // Make a request to the target URL | ||
| // Make a request to the target URL (now validated) | ||
| const response = await fetch(targetUrl, fetchOptions); | ||
|
|
||
| // Forward response headers |
| openmct.install(new RealtimeTelemetryPlugin(vistaTime, config)); | ||
| openmct.install(new TypePlugin()); | ||
| openmct.install(new TaxonomyPlugin(config.taxonomy)); | ||
| openmct.install(new LinkPlugin(config)); |
Check warning
Code scanning / CodeQL
Superfluous trailing arguments Warning
function LinkPlugin
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
To fix the problem, remove the unnecessary argument (config) when calling LinkPlugin in the call to openmct.install within openmctMCWSPlugin. Specifically, edit line 189 to call new LinkPlugin() with no arguments instead of new LinkPlugin(config). This will align the function call with the callee's signature, avoid possible miscommunication about what LinkPlugin is supposed to do, and resolve the flagged issue. No additional imports or methods are necessary, and changes should be limited to this line in plugin.js.
-
Copy modified line R189
| @@ -186,7 +186,7 @@ | ||
| openmct.install(new RealtimeTelemetryPlugin(vistaTime, config)); | ||
| openmct.install(new TypePlugin()); | ||
| openmct.install(new TaxonomyPlugin(config.taxonomy)); | ||
| openmct.install(new LinkPlugin(config)); | ||
| openmct.install(new LinkPlugin()); | ||
| openmct.install(new VenuePlugin(config)); | ||
| openmct.install(FrameWatchViewPlugin(config.tablePerformanceOptions)); | ||
| openmct.install(FrameEventFilterViewPlugin(config.tablePerformanceOptions)); |
serve-instance.js
Outdated
| if (urlMatch) { | ||
| try { | ||
| const decodedUrl = decodeURIComponent(urlMatch[1]); | ||
| console.log('Decoded URL from query:', decodedUrl); |
Check warning
Code scanning / CodeQL
Log injection Medium
user-provided value
Log entry depends on a
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
To fix the problem, we need to ensure that user input (decodedUrl) is sanitized before it is logged. Specifically, we should remove CR (\r) and LF (\n) characters, as recommended. Logging should also clearly label user input, to help identify it as such in logs.
Only the code line logging decodedUrl (line 70 in serve-instance.js) needs to be modified. Before logging, we should sanitize the decoded value, e.g., with .replace(/\r|\n/g, "") to remove all newline-related control characters. No changes elsewhere, and no extra dependency is needed—this can be done directly in the block.
Required:
- Edit the logging statement to sanitize the value.
- Possibly label the user input in logs, e.g., quote or bracket it.
-
Copy modified lines R70-R71
| @@ -67,7 +67,8 @@ | ||
| if (urlMatch) { | ||
| try { | ||
| const decodedUrl = decodeURIComponent(urlMatch[1]); | ||
| console.log('Decoded URL from query:', decodedUrl); | ||
| const sanitizedDecodedUrl = decodedUrl.replace(/[\r\n]/g, ""); | ||
| console.log('Decoded URL from query:', `"${sanitizedDecodedUrl}"`); | ||
|
|
||
| // Handle relative URLs or query strings (like webpack dev server does) | ||
| if (decodedUrl.startsWith('http://') || decodedUrl.startsWith('https://')) { |
serve-instance.js
Outdated
| } | ||
| } catch (e) { | ||
| // Invalid URL encoding | ||
| console.error('Error decoding URL:', e, 'Raw value:', urlMatch[1]); |
Check warning
Code scanning / CodeQL
Log injection Medium
user-provided value
Log entry depends on a
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
To fix this issue, the value logged from user input needs to be sanitized before being included in the log entry. For plain text logs, the recommended practice is to remove newline characters (\n, \r) from the value. This can be done by using String.prototype.replace with a regular expression. The change should be limited to the specific value logged—urlMatch[1] on line 90 in serve-instance.js. The sanitized value should replace the direct use of urlMatch[1] in the error log.
Specifically:
- On line 90, before logging
urlMatch[1], apply.replace(/\r|\n/g, "")to sanitize it. - It is not necessary to add any imports, as this uses standard JavaScript functionality.
- No functional change for non-error flows.
-
Copy modified lines R90-R92
| @@ -87,7 +87,9 @@ | ||
| } | ||
| } catch (e) { | ||
| // Invalid URL encoding | ||
| console.error('Error decoding URL:', e, 'Raw value:', urlMatch[1]); | ||
| // Sanitize the user-provided value to prevent log injection | ||
| const sanitizedUrlMatchVal = urlMatch[1].replace(/\r|\n/g, ""); | ||
| console.error('Error decoding URL:', e, 'Raw value:', sanitizedUrlMatchVal); | ||
| targetUrl = null; | ||
| } | ||
| } |
serve-instance.js
Outdated
| } | ||
|
|
||
| if (!targetUrl) { | ||
| console.error('Failed to extract valid URL from request:', req.originalUrl || req.url); |
Check warning
Code scanning / CodeQL
Log injection Medium
user-provided value
Log entry depends on a
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
To address this log injection issue, sanitize all user-controlled input before including it in logs. In this context, remove or escape any potentially dangerous control characters from req.originalUrl or req.url before logging them. A standard fix for plain text logs is to remove all carriage return (\r) and newline (\n) characters by using String.prototype.replace, ensuring any log entry cannot be split into multiple lines by attacker input. The fix should only affect the relevant log statement(s) to avoid changing existing functionality. The required change involves assigning a sanitized version of the URL to a local variable and logging that variable in place of the raw input. No new dependencies are required.
-
Copy modified lines R97-R99
| @@ -94,7 +94,9 @@ | ||
| } | ||
|
|
||
| if (!targetUrl) { | ||
| console.error('Failed to extract valid URL from request:', req.originalUrl || req.url); | ||
| // Sanitize user input to prevent log injection | ||
| const sanitizedUrl = (req.originalUrl || req.url || '').replace(/[\r\n]/g, ''); | ||
| console.error('Failed to extract valid URL from request:', sanitizedUrl); | ||
| return res.status(400).send('Missing or invalid url query parameter. The url parameter must be a valid HTTP/HTTPS URL.'); | ||
| } | ||
|
|
| // Helper function to replace lodash groupBy | ||
| function groupBy(array, key) { | ||
| return array.reduce((result, item) => { | ||
| const groupKey = typeof key === 'function' ? key(item) : item[key]; |
Check notice
Code scanning / CodeQL
Unneeded defensive code Note
Copilot Autofix
AI 10 days ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
|
|
||
| // Helper function to replace lodash map | ||
| function map(array, key) { | ||
| return array.map((item) => (typeof key === 'function' ? key(item) : item[key])); |
Check notice
Code scanning / CodeQL
Unneeded defensive code Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
To fix the problem, we should simplify the implementation of the map helper function in src/services/dataset/ChannelDictionary.js, specifically lines 37-39. The conditional that checks typeof key === 'function' is unnecessary if callers never supply a function as the key parameter. We can safely remove the conditional, and have the function simply use the property lookup (item[key]). The fix requires editing just the map function definition in this file. No new methods or imports are needed, nor any other changes elsewhere.
-
Copy modified line R38
| @@ -35,7 +35,7 @@ | ||
|
|
||
| // Helper function to replace lodash map | ||
| function map(array, key) { | ||
| return array.map((item) => (typeof key === 'function' ? key(item) : item[key])); | ||
| return array.map((item) => item[key]); | ||
| } | ||
|
|
||
| // Helper function to replace lodash values |
| // Helper function to replace lodash groupBy | ||
| function groupBy(array, key) { | ||
| return array.reduce((result, item) => { | ||
| const groupKey = typeof key === 'function' ? key(item) : item[key]; |
Check notice
Code scanning / CodeQL
Unneeded defensive code Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
To fix this problem, we should simplify the groupBy function by removing the typeof key === 'function' ? key(item) : item[key] check and always using item[key], as key is always a string. This involves editing the groupBy implementation in src/services/dataset/EVRDictionary.js to remove the type test and just access the property via item[key]. No changes to other parts of the code, imports, or extra definitions are required.
-
Copy modified line R18
| @@ -15,7 +15,7 @@ | ||
| // Helper function to replace lodash groupBy | ||
| function groupBy(array, key) { | ||
| return array.reduce((result, item) => { | ||
| const groupKey = typeof key === 'function' ? key(item) : item[key]; | ||
| const groupKey = item[key]; | ||
| if (!result[groupKey]) { | ||
| result[groupKey] = []; | ||
| } |
| // Helper function to replace lodash keyBy | ||
| function keyBy(array, key) { | ||
| return array.reduce((result, item) => { | ||
| const groupKey = typeof key === 'function' ? key(item) : item[key]; |
Check notice
Code scanning / CodeQL
Unneeded defensive code Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
The fix is to remove the unneeded guard typeof key === 'function' in the helper keyBy, as it always evaluates to false in this context. Specifically, change line 47 to directly assign groupKey = item[key]; (dropping the ternary operator). This change makes the code simpler and avoids misleading future maintainers about the function’s allowed polymorphism. The change is entirely local to the keyBy function in src/services/dataset/EVRDictionary.js.
-
Copy modified line R47
| @@ -44,7 +44,7 @@ | ||
| // Helper function to replace lodash keyBy | ||
| function keyBy(array, key) { | ||
| return array.reduce((result, item) => { | ||
| const groupKey = typeof key === 'function' ? key(item) : item[key]; | ||
| const groupKey = item[key]; | ||
| result[groupKey] = item; | ||
| return result; | ||
| }, {}); |
| function uniqBy(array, key) { | ||
| const seen = new Set(); | ||
| return array.filter((item) => { | ||
| const val = typeof key === 'function' ? key(item) : item[key]; |
Check notice
Code scanning / CodeQL
Unneeded defensive code Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 10 days ago
To fix this issue, remove the unnecessary guard in the uniqBy function for the key argument. Currently, the code uses typeof key === 'function' ? key(item) : item[key]; to support either a property string or a function. Since CodeQL has flagged that typeof key === 'function' is always false here, this can be simplified to always use item[key], which removes unneeded complexity without changing the actual behaviour of the function in this codebase. Make the corresponding change on line 57 in src/services/dataset/EVRDictionary.js.
-
Copy modified line R57
| @@ -54,7 +54,7 @@ | ||
| function uniqBy(array, key) { | ||
| const seen = new Set(); | ||
| return array.filter((item) => { | ||
| const val = typeof key === 'function' ? key(item) : item[key]; | ||
| const val = item[key]; | ||
| if (seen.has(val)) { | ||
| return false; | ||
| } |
|
src/historical/HistoricalProvider.js
Outdated
| const valuesByChannelId = _.groupBy(res, 'channel_id'); | ||
| const toFulfill = _.keyBy(requests, 'domainObject.telemetry.channel_id'); | ||
| // CHANNEL LAD PROVIDER | ||
| const channelLADProvider = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should separate each of these pseudo-providers out to their own separate files and makes them ES6 classes. We can then instantiate them into an array further down.
|
|
||
| const objectUrl = URL.createObjectURL(blob); | ||
| const workerInstance = new Worker( | ||
| new URL('./MCWSStreamWorkerScript.js', import.meta.url), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is functionally equivalent to the code it's replacing. The original code imports the script at build time, and encodes its contents into a data URL. The new code defers resolving the web worker script until runtime, requiring the web worker script to be available on the web server.
Is there a reason why we switched?
src/services/mcws/MCWSClient.js
Outdated
|
|
||
| if (this.config.proxyUrl) { | ||
| const params = options.params; | ||
| let isJsonResponse = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes in this file appear unrelated to the other changes?
… into omm-plugin merge remote




closes #360
Convert Open MCT for MCWS from using OpenM CT as a dependency to being a plugin for Open MCT. Also, update Open MCT for MCWS Plugin to work with the Open MCT Configurator build tool.
Changes:
AMMOSPlugins.jsandloader.jsinto the newplugin.jsdefault.yamldevelopment.yamlconfig.js) as plugin optionswebpack.dev.jsconfigurationREADME.mdfor new plugin format