mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
Build/runtime hardening and dependency security updates (#286)
* Simplify RSS freshness update to static import * Refine vendor chunking for map stack in Vite build * Patch transitive XML parser vulnerability via npm override * Shim Node child_process for browser bundle warnings * Filter known onnxruntime eval warning in Vite build * test: add loaders XML/WMS parser regression coverage * chore: align fast-xml-parser override with merged dependency set --------- Co-authored-by: Elie Habib <elie.habib@gmail.com>
This commit is contained in:
123
api/loaders-xml-wms-regression.test.mjs
Normal file
123
api/loaders-xml-wms-regression.test.mjs
Normal file
@@ -0,0 +1,123 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import test from 'node:test';
|
||||
import { XMLLoader } from '@loaders.gl/xml';
|
||||
import { WMSCapabilitiesLoader, WMSErrorLoader, _WMSFeatureInfoLoader } from '@loaders.gl/wms';
|
||||
|
||||
const WMS_CAPABILITIES_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<WMS_Capabilities version="1.3.0">
|
||||
<Service>
|
||||
<Name>WMS</Name>
|
||||
<Title>Test Service</Title>
|
||||
<KeywordList>
|
||||
<Keyword>alerts</Keyword>
|
||||
<Keyword>world</Keyword>
|
||||
</KeywordList>
|
||||
</Service>
|
||||
<Capability>
|
||||
<Request>
|
||||
<GetMap>
|
||||
<Format>image/png</Format>
|
||||
<Format>image/jpeg</Format>
|
||||
</GetMap>
|
||||
</Request>
|
||||
<Exception>
|
||||
<Format>application/vnd.ogc.se_xml</Format>
|
||||
</Exception>
|
||||
<Layer>
|
||||
<Title>Root Layer</Title>
|
||||
<CRS>EPSG:4326</CRS>
|
||||
<EX_GeographicBoundingBox>
|
||||
<westBoundLongitude>-180</westBoundLongitude>
|
||||
<eastBoundLongitude>180</eastBoundLongitude>
|
||||
<southBoundLatitude>-90</southBoundLatitude>
|
||||
<northBoundLatitude>90</northBoundLatitude>
|
||||
</EX_GeographicBoundingBox>
|
||||
<Layer queryable="1">
|
||||
<Name>alerts</Name>
|
||||
<Title>Alerts</Title>
|
||||
<BoundingBox CRS="EPSG:4326" minx="-10" miny="-20" maxx="30" maxy="40" />
|
||||
<Dimension name="time" units="ISO8601" default="2024-01-01" nearestValue="1">
|
||||
2024-01-01/2024-12-31/P1D
|
||||
</Dimension>
|
||||
</Layer>
|
||||
</Layer>
|
||||
</Capability>
|
||||
</WMS_Capabilities>`;
|
||||
|
||||
test('XMLLoader keeps namespace stripping + array paths stable', () => {
|
||||
const xml = '<root><ns:Child attr="x">ok</ns:Child><ns:Child attr="y">yo</ns:Child></root>';
|
||||
const parsed = XMLLoader.parseTextSync(xml, {
|
||||
xml: {
|
||||
removeNSPrefix: true,
|
||||
arrayPaths: ['root.Child'],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(parsed, {
|
||||
root: {
|
||||
Child: [
|
||||
{ value: 'ok', attr: 'x' },
|
||||
{ value: 'yo', attr: 'y' },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('WMSCapabilitiesLoader parses core typed fields from XML capabilities', () => {
|
||||
const parsed = WMSCapabilitiesLoader.parseTextSync(WMS_CAPABILITIES_XML);
|
||||
|
||||
assert.equal(parsed.version, '1.3.0');
|
||||
assert.equal(parsed.name, 'WMS');
|
||||
assert.deepEqual(parsed.requests.GetMap.mimeTypes, ['image/png', 'image/jpeg']);
|
||||
|
||||
assert.equal(parsed.layers.length, 1);
|
||||
const rootLayer = parsed.layers[0];
|
||||
assert.deepEqual(rootLayer.geographicBoundingBox, [[-180, -90], [180, 90]]);
|
||||
|
||||
const alertsLayer = rootLayer.layers[0];
|
||||
assert.equal(alertsLayer.name, 'alerts');
|
||||
assert.equal(alertsLayer.queryable, true);
|
||||
assert.deepEqual(alertsLayer.boundingBoxes[0], {
|
||||
crs: 'EPSG:4326',
|
||||
boundingBox: [[-10, -20], [30, 40]],
|
||||
});
|
||||
assert.deepEqual(alertsLayer.dimensions[0], {
|
||||
name: 'time',
|
||||
units: 'ISO8601',
|
||||
extent: '2024-01-01/2024-12-31/P1D',
|
||||
defaultValue: '2024-01-01',
|
||||
nearestValue: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('WMSErrorLoader extracts namespaced error text and honors throw options', () => {
|
||||
const namespacedErrorXml =
|
||||
'<?xml version="1.0"?><ogc:ServiceExceptionReport><ogc:ServiceException code="LayerNotDefined">Bad layer</ogc:ServiceException></ogc:ServiceExceptionReport>';
|
||||
|
||||
const defaultMessage = WMSErrorLoader.parseTextSync(namespacedErrorXml);
|
||||
assert.equal(defaultMessage, 'WMS Service error: Bad layer');
|
||||
|
||||
const minimalMessage = WMSErrorLoader.parseTextSync(namespacedErrorXml, {
|
||||
wms: { minimalErrors: true },
|
||||
});
|
||||
assert.equal(minimalMessage, 'Bad layer');
|
||||
|
||||
assert.throws(
|
||||
() => WMSErrorLoader.parseTextSync(namespacedErrorXml, { wms: { throwOnError: true } }),
|
||||
/WMS Service error: Bad layer/
|
||||
);
|
||||
});
|
||||
|
||||
test('WMS feature info parsing remains stable for single and repeated FIELDS nodes', () => {
|
||||
const singleFieldsXml = '<?xml version="1.0"?><FeatureInfoResponse><FIELDS id="1" label="one"/></FeatureInfoResponse>';
|
||||
const manyFieldsXml = '<?xml version="1.0"?><FeatureInfoResponse><FIELDS id="1"/><FIELDS id="2"/></FeatureInfoResponse>';
|
||||
|
||||
const single = _WMSFeatureInfoLoader.parseTextSync(singleFieldsXml);
|
||||
const many = _WMSFeatureInfoLoader.parseTextSync(manyFieldsXml);
|
||||
|
||||
assert.equal(single.features.length, 1);
|
||||
assert.deepEqual(single.features[0]?.attributes, { id: '1', label: 'one' });
|
||||
assert.equal(many.features.length, 2);
|
||||
assert.equal(many.features[0]?.attributes?.id, '1');
|
||||
assert.equal(many.features[1]?.attributes?.id, '2');
|
||||
});
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "world-monitor",
|
||||
"version": "2.5.3",
|
||||
"version": "2.5.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "world-monitor",
|
||||
"version": "2.5.3",
|
||||
"version": "2.5.6",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@deck.gl/aggregation-layers": "^9.2.6",
|
||||
@@ -3008,36 +3008,6 @@
|
||||
"@loaders.gl/core": "^4.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@loaders.gl/xml/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==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@loaders.gl/xml/node_modules/strnum": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
|
||||
"integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@loaders.gl/zip": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@loaders.gl/zip/-/zip-4.3.4.tgz",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"test:e2e:runtime": "VITE_VARIANT=full playwright test e2e/runtime-fetch.spec.ts",
|
||||
"test:e2e": "npm run test:e2e:runtime && npm run test:e2e:full && npm run test:e2e:tech && npm run test:e2e:finance",
|
||||
"test:data": "node --test tests/*.test.mjs",
|
||||
"test:sidecar": "node --test src-tauri/sidecar/local-api-server.test.mjs api/_cors.test.mjs api/youtube/embed.test.mjs api/cyber-threats.test.mjs api/usni-fleet.test.mjs scripts/ais-relay-rss.test.cjs",
|
||||
"test:sidecar": "node --test src-tauri/sidecar/local-api-server.test.mjs api/_cors.test.mjs api/youtube/embed.test.mjs api/cyber-threats.test.mjs api/usni-fleet.test.mjs scripts/ais-relay-rss.test.cjs api/loaders-xml-wms-regression.test.mjs",
|
||||
"test:e2e:visual:full": "VITE_VARIANT=full playwright test -g \"matches golden screenshots per layer and zoom\"",
|
||||
"test:e2e:visual:tech": "VITE_VARIANT=tech playwright test -g \"matches golden screenshots per layer and zoom\"",
|
||||
"test:e2e:visual": "npm run test:e2e:visual:full && npm run test:e2e:visual:tech",
|
||||
@@ -82,5 +82,8 @@
|
||||
"posthog-js": "^1.352.0",
|
||||
"topojson-client": "^3.1.0",
|
||||
"youtubei.js": "^16.0.1"
|
||||
},
|
||||
"overrides": {
|
||||
"fast-xml-parser": "^5.3.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { chunkArray, fetchWithProxy } from '@/utils';
|
||||
import { classifyByKeyword, classifyWithAI } from './threat-classifier';
|
||||
import { inferGeoHubsFromTitle } from './geo-hub-index';
|
||||
import { getPersistentCache, setPersistentCache } from './persistent-cache';
|
||||
import { dataFreshness } from './data-freshness';
|
||||
import { ingestHeadlines } from './trending-keywords';
|
||||
import { getCurrentLanguage } from './i18n';
|
||||
|
||||
@@ -316,9 +317,7 @@ export async function fetchCategoryFeeds(
|
||||
}
|
||||
|
||||
if (totalItems > 0) {
|
||||
import('./data-freshness').then(({ dataFreshness }) => {
|
||||
dataFreshness.recordUpdate('rss', totalItems);
|
||||
});
|
||||
dataFreshness.recordUpdate('rss', totalItems);
|
||||
}
|
||||
|
||||
return ensureSortedDescending();
|
||||
|
||||
14
src/shims/child-process-proxy.ts
Normal file
14
src/shims/child-process-proxy.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Browser shim for @loaders.gl/worker-utils ChildProcessProxy.
|
||||
* loaders.gl exposes this Node-only utility from its root index, which can
|
||||
* trigger bundler warnings in browser builds even when not used at runtime.
|
||||
*/
|
||||
export default class ChildProcessProxy {
|
||||
async start(): Promise<{}> {
|
||||
throw new Error('ChildProcessProxy is not available in browser environments.');
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {}
|
||||
|
||||
async exit(_statusCode: number = 0): Promise<void> {}
|
||||
}
|
||||
9
src/shims/child-process.ts
Normal file
9
src/shims/child-process.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Browser shim for Node's `child_process` module.
|
||||
* Some transitive dependencies reference it even in browser bundles.
|
||||
*/
|
||||
export function spawn(): never {
|
||||
throw new Error('child_process.spawn is not available in browser environments.');
|
||||
}
|
||||
|
||||
export default { spawn };
|
||||
@@ -625,10 +625,32 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
child_process: resolve(__dirname, 'src/shims/child-process.ts'),
|
||||
'node:child_process': resolve(__dirname, 'src/shims/child-process.ts'),
|
||||
'@loaders.gl/worker-utils/dist/lib/process-utils/child-process-proxy.js': resolve(
|
||||
__dirname,
|
||||
'src/shims/child-process-proxy.ts'
|
||||
),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
// Geospatial bundles (maplibre/deck) are expected to be large even when split.
|
||||
// Raise warning threshold to reduce noisy false alarms in CI.
|
||||
chunkSizeWarningLimit: 1200,
|
||||
rollupOptions: {
|
||||
onwarn(warning, warn) {
|
||||
// onnxruntime-web ships a minified browser bundle that intentionally uses eval.
|
||||
// Keep build logs focused by filtering this known third-party warning only.
|
||||
if (
|
||||
warning.code === 'EVAL'
|
||||
&& typeof warning.id === 'string'
|
||||
&& warning.id.includes('/onnxruntime-web/dist/ort-web.min.js')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
warn(warning);
|
||||
},
|
||||
input: {
|
||||
main: resolve(__dirname, 'index.html'),
|
||||
settings: resolve(__dirname, 'settings.html'),
|
||||
@@ -637,11 +659,23 @@ export default defineConfig({
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.includes('/@xenova/transformers/') || id.includes('/onnxruntime-web/')) {
|
||||
return 'ml';
|
||||
if (id.includes('/@xenova/transformers/')) {
|
||||
return 'transformers';
|
||||
}
|
||||
if (id.includes('/@deck.gl/') || id.includes('/maplibre-gl/') || id.includes('/h3-js/')) {
|
||||
return 'map';
|
||||
if (id.includes('/onnxruntime-web/')) {
|
||||
return 'onnxruntime';
|
||||
}
|
||||
if (id.includes('/maplibre-gl/')) {
|
||||
return 'maplibre';
|
||||
}
|
||||
if (
|
||||
id.includes('/@deck.gl/')
|
||||
|| id.includes('/@luma.gl/')
|
||||
|| id.includes('/@loaders.gl/')
|
||||
|| id.includes('/@math.gl/')
|
||||
|| id.includes('/h3-js/')
|
||||
) {
|
||||
return 'deck-stack';
|
||||
}
|
||||
if (id.includes('/d3/')) {
|
||||
return 'd3';
|
||||
|
||||
Reference in New Issue
Block a user