diff --git a/api/package-lock.json b/api/package-lock.json index 9b58d01..707ed65 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -15,6 +15,7 @@ "@nestjs/swagger": "^11.2.3", "@prisma/adapter-pg": "^7.9.0", "@prisma/client": "^7.9.0", + "brace-expansion": "file:vendor/brace-expansion-compat", "bullmq": "^5.79.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", @@ -2918,14 +2919,8 @@ } }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "resolved": "node_modules/archiver-utils/node_modules/minimatch/vendor/brace-expansion-compat", + "link": true }, "node_modules/archiver-utils/node_modules/glob": { "version": "7.2.3", @@ -2960,6 +2955,7 @@ "node": "*" } }, + "node_modules/archiver-utils/node_modules/minimatch/vendor/brace-expansion-compat": {}, "node_modules/archiver-utils/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -3128,10 +3124,13 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -3259,12 +3258,20 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "resolved": "vendor/brace-expansion-compat", + "link": true + }, + "node_modules/brace-expansion-safe": { + "name": "brace-expansion", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/browser-or-node": { @@ -3811,12 +3818,6 @@ "node": "*" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, "node_modules/concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -6645,6 +6646,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/brace-expansion": { + "resolved": "node_modules/minimatch/vendor/brace-expansion-compat", + "link": true + }, + "node_modules/minimatch/vendor/brace-expansion-compat": { + "dev": true + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -7676,6 +7684,10 @@ "minimatch": "^5.1.0" } }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "resolved": "node_modules/readdir-glob/node_modules/minimatch/vendor/brace-expansion-compat", + "link": true + }, "node_modules/readdir-glob/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -7688,6 +7700,7 @@ "node": ">=10" } }, + "node_modules/readdir-glob/node_modules/minimatch/vendor/brace-expansion-compat": {}, "node_modules/readdirp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", @@ -7816,14 +7829,8 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "resolved": "node_modules/rimraf/node_modules/minimatch/vendor/brace-expansion-compat", + "link": true }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", @@ -7858,6 +7865,7 @@ "node": "*" } }, + "node_modules/rimraf/node_modules/minimatch/vendor/brace-expansion-compat": {}, "node_modules/robust-predicates": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", @@ -8568,15 +8576,8 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "resolved": "node_modules/test-exclude/node_modules/minimatch/vendor/brace-expansion-compat", + "link": true }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", @@ -8613,6 +8614,9 @@ "node": "*" } }, + "node_modules/test-exclude/node_modules/minimatch/vendor/brace-expansion-compat": { + "dev": true + }, "node_modules/through2": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", @@ -9480,14 +9484,8 @@ } }, "node_modules/zip-stream/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "resolved": "node_modules/zip-stream/node_modules/minimatch/vendor/brace-expansion-compat", + "link": true }, "node_modules/zip-stream/node_modules/glob": { "version": "7.2.3", @@ -9521,6 +9519,15 @@ "engines": { "node": "*" } + }, + "node_modules/zip-stream/node_modules/minimatch/vendor/brace-expansion-compat": {}, + "vendor/brace-expansion-compat": { + "name": "brace-expansion", + "version": "5.0.8-compat.1", + "license": "MIT", + "dependencies": { + "brace-expansion-safe": "npm:brace-expansion@5.0.8" + } } } } diff --git a/api/package.json b/api/package.json index df610ee..0221776 100644 --- a/api/package.json +++ b/api/package.json @@ -24,6 +24,7 @@ "bullmq": "^5.79.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", + "brace-expansion": "file:vendor/brace-expansion-compat", "exceljs": "^4.4.0", "ioredis": "^5.11.1", "minio": "^8.0.7", @@ -41,6 +42,7 @@ "typescript": "^6.0.3" }, "overrides": { + "brace-expansion": "$brace-expansion", "exceljs": { "uuid": "11.1.1" }, diff --git a/api/vendor/brace-expansion-compat/README.md b/api/vendor/brace-expansion-compat/README.md new file mode 100644 index 0000000..676e007 --- /dev/null +++ b/api/vendor/brace-expansion-compat/README.md @@ -0,0 +1,6 @@ +# brace-expansion compatibility adapter + +Older `minimatch` releases call `require('brace-expansion')` as a function, +while the security-fixed 5.x package exposes a named `expand` function. +This adapter preserves the legacy callable CommonJS API and delegates all +expansion work to the bounded `brace-expansion@5.0.8` implementation. diff --git a/api/vendor/brace-expansion-compat/index.cjs b/api/vendor/brace-expansion-compat/index.cjs new file mode 100644 index 0000000..53d344b --- /dev/null +++ b/api/vendor/brace-expansion-compat/index.cjs @@ -0,0 +1,11 @@ +'use strict'; + +const boundedBraceExpansion = require('brace-expansion-safe'); + +function expand(pattern, options) { + return boundedBraceExpansion.expand(pattern, options); +} + +Object.assign(expand, boundedBraceExpansion); + +module.exports = expand; diff --git a/api/vendor/brace-expansion-compat/package.json b/api/vendor/brace-expansion-compat/package.json new file mode 100644 index 0000000..62d889a --- /dev/null +++ b/api/vendor/brace-expansion-compat/package.json @@ -0,0 +1,11 @@ +{ + "name": "brace-expansion", + "version": "5.0.8-compat.1", + "private": true, + "description": "CommonJS compatibility adapter for the bounded brace-expansion 5.0.8 implementation", + "main": "index.cjs", + "license": "MIT", + "dependencies": { + "brace-expansion-safe": "npm:brace-expansion@5.0.8" + } +} diff --git a/docs/first-version-development-requirements.md b/docs/first-version-development-requirements.md index 8da57b8..171fa61 100644 --- a/docs/first-version-development-requirements.md +++ b/docs/first-version-development-requirements.md @@ -1677,6 +1677,14 @@ 1. 运营端“系统日志”必须将人员操作审计与协议通讯日志分成两个独立页签。通讯日志至少支持协议、交互方向、事件类型、结果、关键字和时间范围过滤,并展示平台消息号、上游消息号或HTTP请求号、脱敏对象、结果码、耗时和安全详情。 2. CMPP应覆盖客户登录/Submit、供应商SubmitResp、状态报告Deliver、上行Deliver及平台下游投递;HTTP应覆盖客户发送请求和平台回执/上行Webhook。数据库中一条记录必须对应一个真实业务报文,不得把同一报文的“入口收到”和“处理成功”拆成两条记录;处理结果、结果码和耗时写在该报文同一条记录中,失败、重试等后续真实交互另行记录。 3. Gateway收到状态报告或上行后,必须对解包/解码失败及转发NestJS失败输出结构化安全日志;NestJS入口把业务处理结果合并回同一报文记录,以便区分“上游未发”“Gateway未收到”“Gateway转发失败”和“API落库失败”。一条正常短短信的供应商侧完整成功闭环应依次展示四个真实报文:平台→通道 `CMPP_SUBMIT`、通道→平台 `CMPP_SUBMIT_RESP`、通道→平台 `CMPP_DELIVER`、平台→通道 `CMPP_DELIVER_RESP`;箭头只表达报文实际传输方向。 + +## 2026-07-26 依赖安全治理补充 + +- 依赖安全整改不得直接执行 `npm audit fix --force`;必须逐条核对公告的真实利用条件、当前代码调用路径、目标版本兼容性和生产依赖/构建工具边界,并通过干净安装、全量测试和生产构建。 +- 前端不得引入 React Router 实验性 RSC 服务端 API;在官方发布兼容修复版前,必须由自动门禁扫描源码并固定已验证的客户端 SPA 版本。仅因审计建议降级到包含已知 XSS、RCE 或 DoS 公告的旧版本属于禁止操作。 +- PostCSS 必须固定到 `8.5.18` 或更高修复版本。平台不得接受用户 CSS 后交由构建链处理;如未来新增此能力,必须显式禁用不可信 previous source map 自动加载并重新开展威胁建模。 +- ExcelJS 的旧版 `minimatch` 调用接口与安全修复版 `brace-expansion` 5.x 不兼容时,允许使用受测试的本地 CommonJS 兼容适配层;适配层只能转发到官方有长度上限的实现,必须同时验证旧版 minimatch 花括号匹配、Excel 读写和干净 `npm ci`。 +- Prisma CLI 只用于生成、迁移和构建,不属于 API 请求运行路径。其无上游修复版本的中危工具链公告需记录接受条件并持续跟踪,不得为审计数字清零而把 Prisma 7 数据访问栈盲目降级到 6.x。 4. 通讯日志不得保存短信正文、密码、密钥、Token、签名鉴权值或完整HTTP请求体;手机号只保存脱敏值。CMPP心跳不得逐包写入数据库,连接健康仍使用连接状态和聚合指标。 5. 通讯日志写入不能阻塞短信主链路,默认批量异步写入,缓冲区应有上限和溢出告警;热数据默认保留30天,保留期允许通过环境变量配置。 6. 通讯日志方向固定使用“企业应用 → 平台、平台 → 供应商通道、供应商通道 → 平台、平台 → 企业应用”。供应商长短信每个真实 `SUBMIT` 和 `SUBMIT_RESP` 分片各记一条,企业应用每个真实 `SUBMIT_RESP` 也必须记录;内部 `submit-result` 聚合回调不是协议报文,不得重复生成通讯日志。 diff --git a/docs/testing-progress.md b/docs/testing-progress.md index 5764a5f..38caa3c 100644 --- a/docs/testing-progress.md +++ b/docs/testing-progress.md @@ -2451,3 +2451,12 @@ git diff --check - 新增 migration `20260725160000_add_reliable_receipt_delivery_tracking`,仅新增下游逐次投递表、上游回执收件箱及索引/外键,不改写既有短信、提交、回执或投递历史。回滚必须先停止新版本 API/Gateway,再删除两张新表;回滚会丢失新版本产生的逐次投递和待匹配收件箱证据。 - 发布前门禁通过:Prisma format/generate/validate,API 定向 3 suites / 119 tests,API 全量 26 suites / 325 tests,API TypeScript build,前端 TypeScript/Vite生产构建及 Gateway `go test ./...`。API 全量仅保留既有 Redis 不可用容错告警和 `--forceExit` 异步句柄提示;前端保留既有约 1.94 MB 单 chunk 警告。`git diff --check`通过。 - 本节当前为发布前记录;提交、推送、数据库/源码/环境备份、migration、服务重启和预发布只读验收结果在发布完成后追加。未经额外授权不发送真实短信,不改写历史业务记录。 + +## 2026-07-26 npm 依赖安全整改(发布前) + +- 根项目将 PostCSS 从 `8.5.15` 固定到已修复的 `8.5.23`,对应任意 `.map` 文件路径穿越公告已从审计中消失;平台没有接收用户 CSS 并交给 PostCSS 的运行时路径。 +- React Router 保持并精确固定当前最新版 `7.18.1`。实测按审计建议降到 `7.11.0` 会重新引入多条 XSS、开放重定向、反序列化和 DoS 公告,因此未采用。当前唯一剩余 high 为实验性 RSC 模式 CSRF,项目实际使用 `HashRouter` 客户端 SPA;新增 `security:verify` 门禁递归扫描前端源码,禁止引入 RSC 服务端 API。 +- API 将 ExcelJS 间接使用的 `brace-expansion` 替换为官方长度受限的 `5.0.8`,同时增加本地 CommonJS 兼容适配层,使旧版 minimatch 的函数调用方式继续有效。直接覆盖 5.0.8 曾被兼容性测试明确拦截(`expand is not a function`),未把审计清零冒充功能可用。 +- 独立 Linux 临时目录从锁文件执行两次全新 `npm ci --ignore-scripts` 成功:根项目 59 个包、API 726 个包;旧版 minimatch 三条真实依赖链花括号匹配和长度上限均通过。整改后根项目审计只剩同一条不适用 RSC 公告的 2 个依赖节点,API high 从 26 降为 0,仅剩 Prisma CLI→Valibot 的 3 个 moderate;该工具链不处理 API 请求且上游暂无修复版本,不降级 Prisma 7。 +- 功能门禁通过:报备 Excel 专项 1 suite / 7 tests,API 全量 26 suites / 325 tests,API TypeScript build,前端 TypeScript/Vite生产构建,Gateway `go test ./...`、`go vet ./...`,以及依赖安全门禁。标准部署脚本也会在干净安装后、Prisma migration 前强制执行该门禁,失败即停止发布。前端保留既有约 1.94 MB 单 chunk 提示;API 测试保留既有 Redis 容错告警和 `--forceExit` 异步句柄提示。 +- Windows 本地完整 `npm ci` 因另一进程占用两个原生 `.node` 文件而无法清理旧目录,未结束未知会话进程;随后非破坏性 `npm install` 修复本地依赖。锁文件可重建性以隔离 Linux 干净安装结果为准。提交、推送和预发布部署结果待发布后补记。 diff --git a/package-lock.json b/package-lock.json index 1cd563d..c4f250a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "lucide-react": "^1.18.0", "react": "^19.2.7", "react-dom": "^19.2.7", - "react-router-dom": "^7.18.1", + "react-router-dom": "7.18.1", "vite": "^8.0.16", "zustand": "^5.0.14" }, @@ -1062,9 +1062,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -1119,9 +1119,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "funding": [ { "type": "opencollective", @@ -1138,7 +1138,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, diff --git a/package.json b/package.json index 557854c..f74a024 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "start:local": "powershell -NoProfile -ExecutionPolicy Bypass -File tools/start-local.ps1", "start:local:minio": "powershell -NoProfile -ExecutionPolicy Bypass -File tools/start-local.ps1 -OnlyMinio", "prisma:generate": "npm --prefix api run prisma:generate", + "security:verify": "node tools/security/verify-dependency-mitigations.mjs", "spike:contracts": "node tools/spike/validate-gateway-queue-contract.mjs", "spike:gateway": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"$env:Path='C:\\Program Files\\Go\\bin;'+$env:Path; Push-Location gateway; go test ./...; Pop-Location\"", "spike:bullmq": "node api/src/spike/bullmq-link-spike.mjs", @@ -34,7 +35,7 @@ "lucide-react": "^1.18.0", "react": "^19.2.7", "react-dom": "^19.2.7", - "react-router-dom": "^7.18.1", + "react-router-dom": "7.18.1", "vite": "^8.0.16", "zustand": "^5.0.14" }, @@ -43,5 +44,8 @@ "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "typescript": "^6.0.3" + }, + "overrides": { + "postcss": "8.5.23" } } diff --git a/tools/deploy/production-deploy.sh b/tools/deploy/production-deploy.sh index 525fee3..5fea50b 100644 --- a/tools/deploy/production-deploy.sh +++ b/tools/deploy/production-deploy.sh @@ -40,6 +40,9 @@ echo "[deploy] Installing dependencies" npm ci --include=dev npm --prefix api ci --include=dev +echo "[deploy] Verifying dependency security mitigations" +npm run security:verify + echo "[deploy] Generating Prisma client and applying migrations" npm --prefix api run prisma:generate npm --prefix api run prisma:migrate:deploy diff --git a/tools/security/verify-dependency-mitigations.mjs b/tools/security/verify-dependency-mitigations.mjs new file mode 100644 index 0000000..09c196d --- /dev/null +++ b/tools/security/verify-dependency-mitigations.mjs @@ -0,0 +1,73 @@ +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { extname, join, resolve } from 'node:path'; + +const workspaceRoot = resolve(import.meta.dirname, '..', '..'); +const rootLock = JSON.parse(readFileSync(join(workspaceRoot, 'package-lock.json'), 'utf8')); +const apiLock = JSON.parse(readFileSync(join(workspaceRoot, 'api', 'package-lock.json'), 'utf8')); + +assertVersionAtLeast(rootLock.packages['node_modules/postcss']?.version, [8, 5, 18], 'postcss'); +assertEqual(rootLock.packages['node_modules/react-router']?.version, '7.18.1', 'react-router'); +assertEqual(apiLock.packages['node_modules/brace-expansion-safe']?.version, '5.0.8', 'brace-expansion-safe'); +assertEqual( + apiLock.packages['node_modules/brace-expansion']?.resolved, + 'vendor/brace-expansion-compat', + 'brace-expansion compatibility adapter', +); + +const forbiddenRscPatterns = [ + /\bunstable_RSC\w*/u, + /\bRSCRouterConfig\b/u, + /\bRSCStaticRouter\b/u, + /\bcreateCallServer\b/u, + /react-router\/dom\/server/u, +]; +for (const filePath of sourceFiles(join(workspaceRoot, 'src'))) { + const source = readFileSync(filePath, 'utf8'); + for (const pattern of forbiddenRscPatterns) { + if (pattern.test(source)) { + throw new Error(`React Router RSC mitigation violated by ${filePath}: ${pattern}`); + } + } +} + +const apiRequire = createRequire(join(workspaceRoot, 'api', 'package.json')); +const expand = apiRequire('brace-expansion'); +if (typeof expand !== 'function' || expand.EXPANSION_MAX_LENGTH !== 4_000_000) { + throw new Error('brace-expansion compatibility adapter is not using the bounded 5.0.8 implementation'); +} +assertEqual(expand('{a,b}{1,2}').join(','), 'a1,a2,b1,b2', 'brace-expansion legacy API'); + +for (const relativePath of [ + 'node_modules/archiver-utils/node_modules/minimatch', + 'node_modules/readdir-glob/node_modules/minimatch', + 'node_modules/rimraf/node_modules/minimatch', +]) { + const loaded = apiRequire(join(workspaceRoot, 'api', relativePath)); + const match = typeof loaded === 'function' ? loaded : loaded.minimatch; + if (typeof match !== 'function' || !match('file-a.xlsx', 'file-{a,b}.xlsx')) { + throw new Error(`Legacy minimatch compatibility failed for ${relativePath}`); + } +} + +console.log('Dependency mitigations verified: PostCSS patched, React Router RSC unused, brace expansion bounded and compatible.'); + +function sourceFiles(directory) { + return readdirSync(directory).flatMap((name) => { + const path = join(directory, name); + if (statSync(path).isDirectory()) return sourceFiles(path); + return ['.js', '.jsx', '.ts', '.tsx'].includes(extname(path)) ? [path] : []; + }); +} + +function assertVersionAtLeast(actual, minimum, label) { + if (!actual) throw new Error(`${label} is missing from package-lock.json`); + const parts = actual.split('.').map((part) => Number(part.replace(/\D.*$/u, ''))); + if (minimum.some((value, index) => parts[index] < value && minimum.slice(0, index).every((item, i) => parts[i] === item))) { + throw new Error(`${label} ${actual} is older than ${minimum.join('.')}`); + } +} + +function assertEqual(actual, expected, label) { + if (actual !== expected) throw new Error(`${label}: expected ${expected}, received ${actual ?? 'missing'}`); +}