From 119d46f3e53d217862898a31601cb8e7954e1edb Mon Sep 17 00:00:00 2001 From: Jose Costa Teixeira Date: Thu, 7 May 2026 05:32:04 +0100 Subject: [PATCH 1/3] Update docker.yml --- .github/workflows/docker.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2320273d..07e3aba0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,6 +3,8 @@ name: Docker Build on: push: branches: [ main ] + workflow_dispatch: + jobs: docker: @@ -32,4 +34,4 @@ jobs: platforms: linux/amd64,linux/arm64 tags: | ghcr.io/${{ env.REPO_LC }}:cibuild - ghcr.io/${{ env.REPO_LC }}:cibuild-${{ github.sha }} \ No newline at end of file + ghcr.io/${{ env.REPO_LC }}:cibuild-${{ github.sha }} From 15365ee46d3695243b657ba180520ec50a1cfedf Mon Sep 17 00:00:00 2001 From: Jose Costa Teixeira <16153168+costateixeira@users.noreply.github.com> Date: Thu, 14 May 2026 19:59:08 +0200 Subject: [PATCH 2/3] Cache compiled regexes to avoid re2-wasm WASM heap exhaustion re2-wasm has a fixed 16 MB WASM linear-memory heap with no real free(): every `new RE2(pattern, flags)` permanently consumes a few KB regardless of whether the JS object is later GC'd. After enough compiles the heap is full and any further call aborts with `Cannot enlarge memory arrays to size 16781312 bytes (OOM)`. Reproducer: compiling the same trivial pattern 2965 times in a loop is enough to OOM on a fresh Node process. In production tx.fhir.org hit this via repeated $validate-code calls against the IPS lab-observations ValueSet, whose LOINC `CLASS regex` filter is recompiled per call. Cache compiled RE2 objects by (pattern, flags). Same pattern -> single compile -> single leak. Does not fix the underlying leak; a proper fix is to replace re2-wasm with the native `re2` package, which has real free() semantics. --- library/regex-utilities.js | 20 ++++++++++++++--- test-scripts/repro-re2-wasm-leak.js | 33 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 test-scripts/repro-re2-wasm-leak.js diff --git a/library/regex-utilities.js b/library/regex-utilities.js index 8a0920b9..c289e4d5 100644 --- a/library/regex-utilities.js +++ b/library/regex-utilities.js @@ -1,13 +1,27 @@ -const { RE2 } = require('re2-wasm'); +const { RE2 } = require('re2-wasm'); class RegExUtilities { + constructor() { + this._cache = new Map(); + } + compile(pattern, flags) { - // RE2 requires the unicode flag; add it if not already present + // re2-wasm has a fixed 16 MB WASM heap with no real free(): every + // new RE2(...) permanently consumes a few KB. Cache by (pattern, flags) + // so the same regex compiles at most once. + // TODO: replace re2-wasm with native re2 to eliminate the underlying leak. const re2Flags = flags && flags.includes('u') ? flags : (flags || '') + 'u'; - return new RE2(pattern, re2Flags); + const key = pattern + '|' + re2Flags; + let compiled = this._cache.get(key); + if (!compiled) { + compiled = new RE2(pattern, re2Flags); + this._cache.set(key, compiled); + } + return compiled; } } module.exports = new RegExUtilities(); + \ No newline at end of file diff --git a/test-scripts/repro-re2-wasm-leak.js b/test-scripts/repro-re2-wasm-leak.js new file mode 100644 index 00000000..4742e2fe --- /dev/null +++ b/test-scripts/repro-re2-wasm-leak.js @@ -0,0 +1,33 @@ +// Manual reproducer for the re2-wasm WASM heap leak. +// See library/regex-utilities.js for the workaround. +// +// node test-scripts/repro-re2-wasm-leak.js # same-pattern stress +// node test-scripts/repro-re2-wasm-leak.js --unique # unique-pattern stress +// +// Without the cache, same-pattern OOMs at ~2965 iterations. +// With the cache, same-pattern runs indefinitely. +// Unique-pattern still OOMs (each pattern is a real compile and the underlying +// re2-wasm heap leak still applies). A proper fix is to replace re2-wasm with +// the native `re2` package. + +const re = require('../library/regex-utilities'); + +const mode = process.argv.includes('--unique') ? 'unique' : 'same'; +const basePattern = + 'CYTO|HL7\\.CYTOGEN|HL7\\.GENETICS|^PATH(\\..*)?|^MOLPATH(\\..*)?|NR STATS|H&P\\.HX\\.LAB|CHALSKIN|LABORDERS'; + +console.log(`mode: ${mode}-pattern`); + +let i = 0; +try { + for (;;) { + const pattern = mode === 'unique' ? basePattern + `|UNIQUE${i}` : basePattern; + re.compile(pattern); + i++; + if (i % 100 === 0) console.log(`iter ${i}`); + } +} catch (e) { + console.error(`OOMed at iteration ${i}: ${e.message}`); + process.exit(1); +} + \ No newline at end of file From 7e7052b002a80d99872388f54a400333853b268f Mon Sep 17 00:00:00 2001 From: Jose Costa Teixeira <16153168+costateixeira@users.noreply.github.com> Date: Thu, 14 May 2026 20:00:56 +0200 Subject: [PATCH 3/3] fix known vulnerabilities --- package-lock.json | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index a74cd253..5e1c1e85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "fhirsmith", - "version": "0.9.4-SNAPSHOT", + "version": "0.9.4", "license": "BSD-3", "dependencies": { "axios": "^1.13.4", @@ -2097,7 +2097,6 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", - "optional": true, "dependencies": { "debug": "4" }, @@ -2283,13 +2282,14 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, @@ -4151,9 +4151,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", - "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "funding": [ { "type": "github", @@ -4162,7 +4162,8 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.1.3" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, "node_modules/fast-xml-parser": { @@ -4918,7 +4919,6 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", - "optional": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -9740,6 +9740,21 @@ "dev": true, "license": "MIT" }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/xmldoc": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-0.4.0.tgz",