diff --git a/package-lock.json b/package-lock.json index c2d19db2..aa525366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "azure-iot-explorer", - "version": "0.15.13", + "version": "0.15.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "azure-iot-explorer", - "version": "0.15.13", + "version": "0.15.14", "license": "MIT", "dependencies": { "@azure/core-amqp": "^3.2.0", @@ -44,8 +44,8 @@ "react-router-dom": "^5.2.0", "react-toastify": "^4.4.0", "redux-saga": "^1.1.3", - "request-filtering-agent": "^3.2.0", "semver": "^6.3.1", + "ssrf-req-filter": "^1.1.1", "typescript-fsa": "^3.0.0-beta-2", "typescript-fsa-reducers": "^1.0.0", "uuid": "^3.3.3", @@ -86,7 +86,6 @@ "enzyme-adapter-react-16": "^1.15.1", "enzyme-to-json": "^3.3.5", "html-webpack-plugin": "^5.5.0", - "is-svg": "^4.4.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-plugin-context": "^2.9.0", @@ -10696,25 +10695,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.1.1" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -12592,22 +12572,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-svg": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.4.0.tgz", - "integrity": "sha512-v+AgVwiK5DsGtT9ng+m4mClp6zDAmwrW8nZi6Gg15qzvBnRWWdfWA1TGaXyCDnWq5g5asofIgMVl3PjKxvk1ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-xml-parser": "^4.1.3" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-symbol": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", @@ -19044,27 +19008,6 @@ "entities": "^2.0.0" } }, - "node_modules/request-filtering-agent": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/request-filtering-agent/-/request-filtering-agent-3.2.0.tgz", - "integrity": "sha512-tKPrKdsmTFuGG1/pBEpzTB66mDZ2lZLW8kjW4N6jj4QjnxUTKrIfv5p2zuJRfztOos86jRPD41lRaGjh+1QqDw==", - "license": "MIT", - "dependencies": { - "ipaddr.js": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/request-filtering-agent/node_modules/ipaddr.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", - "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -20379,6 +20322,24 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/ssrf-req-filter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ssrf-req-filter/-/ssrf-req-filter-1.1.1.tgz", + "integrity": "sha512-z0duj5tuvr5YetayhTdQWHf5gEncHvY6nL1HoqiFaF0gfK8Gc3VpTlxvAf4DaOVwcoczfqsL97H5TJRfYFtEYg==", + "license": "MIT", + "dependencies": { + "ipaddr.js": "^2.2.0" + } + }, + "node_modules/ssrf-req-filter/node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -20684,19 +20645,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/style-loader": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", diff --git a/package.json b/package.json index 0055606c..532cb73e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azure-iot-explorer", - "version": "0.15.13", + "version": "0.15.14", "description": "This project welcomes contributions and suggestions. Most contributions require you to agree to a\r Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\r the rights to use your contribution. For details, visit https://cla.microsoft.com.", "main": "host/electron.js", "build": { @@ -111,14 +111,15 @@ "react-router-dom": "^5.2.0", "react-toastify": "^4.4.0", "redux-saga": "^1.1.3", - "request-filtering-agent": "^3.2.0", "semver": "^6.3.1", + "ssrf-req-filter": "^1.1.1", "typescript-fsa": "^3.0.0-beta-2", "typescript-fsa-reducers": "^1.0.0", "uuid": "^3.3.3", "ws": "^8.17.1" }, "devDependencies": { + "@electron/fuses": "^1.8.0", "@redux-saga/testing-utils": "^1.1.3", "@types/async-lock": "^1.1.0", "@types/core-js": "^2.5.0", @@ -148,7 +149,6 @@ "electron": "^22.3.25", "electron-builder": "^26.0.0", "electron-reload": "^2.0.0-alpha.1", - "@electron/fuses": "^1.8.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.1", "enzyme-to-json": "^3.3.5", @@ -214,7 +214,7 @@ ] }, "transformIgnorePatterns": [ - "node_modules/(?!react-movable|request-filtering-agent|cheerio|htmlparser2|dom-serializer|domhandler|domutils|entities|css-select|css-what|boolbase|nth-check|parse5|parse5-htmlparser2-tree-adapter|@azure/event-hubs|@azure/core-amqp|@azure/core-auth|@azure/core-rest-pipeline|@azure/core-tracing|@azure/core-util|@azure/abort-controller|@azure/logger|@typespec/ts-http-runtime)" + "node_modules/(?!react-movable|cheerio|htmlparser2|dom-serializer|domhandler|domutils|entities|css-select|css-what|boolbase|nth-check|parse5|parse5-htmlparser2-tree-adapter|@azure/event-hubs|@azure/core-amqp|@azure/core-auth|@azure/core-rest-pipeline|@azure/core-tracing|@azure/core-util|@azure/abort-controller|@azure/logger|@typespec/ts-http-runtime)" ], "testRegex": "(\\.|/)(spec)\\.(tsx?)$", "moduleNameMapper": { diff --git a/public/handlers/dataPlaneHandler.ts b/public/handlers/dataPlaneHandler.ts index 65db2f1e..c7c39645 100644 --- a/public/handlers/dataPlaneHandler.ts +++ b/public/handlers/dataPlaneHandler.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License **********************************************************/ import fetch, { Response } from 'node-fetch'; +import * as ssrfFilter from 'ssrf-req-filter'; import { validateAzureIoTHostname, sanitizeHeaders, @@ -14,22 +15,6 @@ import { DataPlaneRequest, DataPlaneResponse } from '../interfaces/deviceInterfa const DEVICE_STATUS_HEADER = 'x-ms-command-statuscode'; const SERVER_ERROR = 500; -// Cache the dynamically imported module -let requestFilteringAgent: any = null; // tslint:disable-line:no-any - -/** - * Dynamically import request-filtering-agent (ESM module) - * Uses Function constructor to prevent TypeScript from converting to require() - */ -const getRequestFilteringAgent = async () => { - if (!requestFilteringAgent) { - // Use Function constructor to create a true dynamic import that won't be transformed by TypeScript - const dynamicImport = new Function('specifier', 'return import(specifier)'); - requestFilteringAgent = await dynamicImport('request-filtering-agent'); - } - return requestFilteringAgent; -}; - /** * Handle data plane request via IPC * This replaces the Express route handler @@ -98,9 +83,6 @@ export const generateDataPlaneRequestBody = async (request: DataPlaneRequest) => const url = `https://${hostname}/${encodeURIComponent(path)}${queryString}`; - // Dynamically import ESM module for SSRF protection - const { useAgent } = await getRequestFilteringAgent(); - return { url, request: { @@ -109,9 +91,9 @@ export const generateDataPlaneRequestBody = async (request: DataPlaneRequest) => method: request.httpMethod.toUpperCase(), redirect: 'error' as const, // Block all HTTP redirects (SSRF protection) timeout: 30000, // 30 second timeout - // Use request-filtering-agent for SSRF protection - // Blocks requests to private IPs, loopback, link-local, IMDS, etc. - agent: useAgent(url), + // Use ssrf-req-filter for SSRF protection + // Blocks requests to private IPs, loopback, link-local, etc. + agent: ssrfFilter(url), } }; };