fix: mitigate npm dependency advisories

This commit is contained in:
hectorzhao
2026-07-26 00:29:49 +08:00
parent 54617c927e
commit b461532075
11 changed files with 190 additions and 56 deletions
+54 -47
View File
@@ -15,6 +15,7 @@
"@nestjs/swagger": "^11.2.3", "@nestjs/swagger": "^11.2.3",
"@prisma/adapter-pg": "^7.9.0", "@prisma/adapter-pg": "^7.9.0",
"@prisma/client": "^7.9.0", "@prisma/client": "^7.9.0",
"brace-expansion": "file:vendor/brace-expansion-compat",
"bullmq": "^5.79.2", "bullmq": "^5.79.2",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.3", "class-validator": "^0.14.3",
@@ -2918,14 +2919,8 @@
} }
}, },
"node_modules/archiver-utils/node_modules/brace-expansion": { "node_modules/archiver-utils/node_modules/brace-expansion": {
"version": "1.1.16", "resolved": "node_modules/archiver-utils/node_modules/minimatch/vendor/brace-expansion-compat",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", "link": true
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
}, },
"node_modules/archiver-utils/node_modules/glob": { "node_modules/archiver-utils/node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
@@ -2960,6 +2955,7 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/archiver-utils/node_modules/minimatch/vendor/brace-expansion-compat": {},
"node_modules/archiver-utils/node_modules/readable-stream": { "node_modules/archiver-utils/node_modules/readable-stream": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -3128,10 +3124,13 @@
} }
}, },
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT" "license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
}, },
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
@@ -3259,12 +3258,20 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.1.2", "resolved": "vendor/brace-expansion-compat",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", "link": true
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", },
"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", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
} }
}, },
"node_modules/browser-or-node": { "node_modules/browser-or-node": {
@@ -3811,12 +3818,6 @@
"node": "*" "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": { "node_modules/concat-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
@@ -6645,6 +6646,13 @@
"url": "https://github.com/sponsors/isaacs" "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": { "node_modules/minimist": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@@ -7676,6 +7684,10 @@
"minimatch": "^5.1.0" "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": { "node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.9", "version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
@@ -7688,6 +7700,7 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/readdir-glob/node_modules/minimatch/vendor/brace-expansion-compat": {},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
@@ -7816,14 +7829,8 @@
} }
}, },
"node_modules/rimraf/node_modules/brace-expansion": { "node_modules/rimraf/node_modules/brace-expansion": {
"version": "1.1.16", "resolved": "node_modules/rimraf/node_modules/minimatch/vendor/brace-expansion-compat",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", "link": true
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
}, },
"node_modules/rimraf/node_modules/glob": { "node_modules/rimraf/node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
@@ -7858,6 +7865,7 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/rimraf/node_modules/minimatch/vendor/brace-expansion-compat": {},
"node_modules/robust-predicates": { "node_modules/robust-predicates": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", "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": { "node_modules/test-exclude/node_modules/brace-expansion": {
"version": "1.1.16", "resolved": "node_modules/test-exclude/node_modules/minimatch/vendor/brace-expansion-compat",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", "link": true
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
}, },
"node_modules/test-exclude/node_modules/glob": { "node_modules/test-exclude/node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
@@ -8613,6 +8614,9 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/test-exclude/node_modules/minimatch/vendor/brace-expansion-compat": {
"dev": true
},
"node_modules/through2": { "node_modules/through2": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
@@ -9480,14 +9484,8 @@
} }
}, },
"node_modules/zip-stream/node_modules/brace-expansion": { "node_modules/zip-stream/node_modules/brace-expansion": {
"version": "1.1.16", "resolved": "node_modules/zip-stream/node_modules/minimatch/vendor/brace-expansion-compat",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", "link": true
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
}, },
"node_modules/zip-stream/node_modules/glob": { "node_modules/zip-stream/node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
@@ -9521,6 +9519,15 @@
"engines": { "engines": {
"node": "*" "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"
}
} }
} }
} }
+2
View File
@@ -24,6 +24,7 @@
"bullmq": "^5.79.2", "bullmq": "^5.79.2",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.3", "class-validator": "^0.14.3",
"brace-expansion": "file:vendor/brace-expansion-compat",
"exceljs": "^4.4.0", "exceljs": "^4.4.0",
"ioredis": "^5.11.1", "ioredis": "^5.11.1",
"minio": "^8.0.7", "minio": "^8.0.7",
@@ -41,6 +42,7 @@
"typescript": "^6.0.3" "typescript": "^6.0.3"
}, },
"overrides": { "overrides": {
"brace-expansion": "$brace-expansion",
"exceljs": { "exceljs": {
"uuid": "11.1.1" "uuid": "11.1.1"
}, },
+6
View File
@@ -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.
+11
View File
@@ -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;
+11
View File
@@ -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"
}
}
@@ -1677,6 +1677,14 @@
1. 运营端“系统日志”必须将人员操作审计与协议通讯日志分成两个独立页签。通讯日志至少支持协议、交互方向、事件类型、结果、关键字和时间范围过滤,并展示平台消息号、上游消息号或HTTP请求号、脱敏对象、结果码、耗时和安全详情。 1. 运营端“系统日志”必须将人员操作审计与协议通讯日志分成两个独立页签。通讯日志至少支持协议、交互方向、事件类型、结果、关键字和时间范围过滤,并展示平台消息号、上游消息号或HTTP请求号、脱敏对象、结果码、耗时和安全详情。
2. CMPP应覆盖客户登录/Submit、供应商SubmitResp、状态报告Deliver、上行Deliver及平台下游投递;HTTP应覆盖客户发送请求和平台回执/上行Webhook。数据库中一条记录必须对应一个真实业务报文,不得把同一报文的“入口收到”和“处理成功”拆成两条记录;处理结果、结果码和耗时写在该报文同一条记录中,失败、重试等后续真实交互另行记录。 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`;箭头只表达报文实际传输方向。 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心跳不得逐包写入数据库,连接健康仍使用连接状态和聚合指标。 4. 通讯日志不得保存短信正文、密码、密钥、Token、签名鉴权值或完整HTTP请求体;手机号只保存脱敏值。CMPP心跳不得逐包写入数据库,连接健康仍使用连接状态和聚合指标。
5. 通讯日志写入不能阻塞短信主链路,默认批量异步写入,缓冲区应有上限和溢出告警;热数据默认保留30天,保留期允许通过环境变量配置。 5. 通讯日志写入不能阻塞短信主链路,默认批量异步写入,缓冲区应有上限和溢出告警;热数据默认保留30天,保留期允许通过环境变量配置。
6. 通讯日志方向固定使用“企业应用 → 平台、平台 → 供应商通道、供应商通道 → 平台、平台 → 企业应用”。供应商长短信每个真实 `SUBMIT``SUBMIT_RESP` 分片各记一条,企业应用每个真实 `SUBMIT_RESP` 也必须记录;内部 `submit-result` 聚合回调不是协议报文,不得重复生成通讯日志。 6. 通讯日志方向固定使用“企业应用 → 平台、平台 → 供应商通道、供应商通道 → 平台、平台 → 企业应用”。供应商长短信每个真实 `SUBMIT``SUBMIT_RESP` 分片各记一条,企业应用每个真实 `SUBMIT_RESP` 也必须记录;内部 `submit-result` 聚合回调不是协议报文,不得重复生成通讯日志。
+9
View File
@@ -2451,3 +2451,12 @@ git diff --check
- 新增 migration `20260725160000_add_reliable_receipt_delivery_tracking`,仅新增下游逐次投递表、上游回执收件箱及索引/外键,不改写既有短信、提交、回执或投递历史。回滚必须先停止新版本 API/Gateway,再删除两张新表;回滚会丢失新版本产生的逐次投递和待匹配收件箱证据。 - 新增 migration `20260725160000_add_reliable_receipt_delivery_tracking`,仅新增下游逐次投递表、上游回执收件箱及索引/外键,不改写既有短信、提交、回执或投递历史。回滚必须先停止新版本 API/Gateway,再删除两张新表;回滚会丢失新版本产生的逐次投递和待匹配收件箱证据。
- 发布前门禁通过:Prisma format/generate/validateAPI 定向 3 suites / 119 testsAPI 全量 26 suites / 325 testsAPI TypeScript build,前端 TypeScript/Vite生产构建及 Gateway `go test ./...`。API 全量仅保留既有 Redis 不可用容错告警和 `--forceExit` 异步句柄提示;前端保留既有约 1.94 MB 单 chunk 警告。`git diff --check`通过。 - 发布前门禁通过:Prisma format/generate/validateAPI 定向 3 suites / 119 testsAPI 全量 26 suites / 325 testsAPI TypeScript build,前端 TypeScript/Vite生产构建及 Gateway `go test ./...`。API 全量仅保留既有 Redis 不可用容错告警和 `--forceExit` 异步句柄提示;前端保留既有约 1.94 MB 单 chunk 警告。`git diff --check`通过。
- 本节当前为发布前记录;提交、推送、数据库/源码/环境备份、migration、服务重启和预发布只读验收结果在发布完成后追加。未经额外授权不发送真实短信,不改写历史业务记录。 - 本节当前为发布前记录;提交、推送、数据库/源码/环境备份、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 testsAPI 全量 26 suites / 325 testsAPI 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 干净安装结果为准。提交、推送和预发布部署结果待发布后补记。
+8 -8
View File
@@ -16,7 +16,7 @@
"lucide-react": "^1.18.0", "lucide-react": "^1.18.0",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.7", "react-dom": "^19.2.7",
"react-router-dom": "^7.18.1", "react-router-dom": "7.18.1",
"vite": "^8.0.16", "vite": "^8.0.16",
"zustand": "^5.0.14" "zustand": "^5.0.14"
}, },
@@ -1062,9 +1062,9 @@
} }
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.12", "version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -1119,9 +1119,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.15", "version": "8.5.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -1138,7 +1138,7 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.12", "nanoid": "^3.3.16",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
+5 -1
View File
@@ -11,6 +11,7 @@
"start:local": "powershell -NoProfile -ExecutionPolicy Bypass -File tools/start-local.ps1", "start:local": "powershell -NoProfile -ExecutionPolicy Bypass -File tools/start-local.ps1",
"start:local:minio": "powershell -NoProfile -ExecutionPolicy Bypass -File tools/start-local.ps1 -OnlyMinio", "start:local:minio": "powershell -NoProfile -ExecutionPolicy Bypass -File tools/start-local.ps1 -OnlyMinio",
"prisma:generate": "npm --prefix api run prisma:generate", "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: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: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", "spike:bullmq": "node api/src/spike/bullmq-link-spike.mjs",
@@ -34,7 +35,7 @@
"lucide-react": "^1.18.0", "lucide-react": "^1.18.0",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.7", "react-dom": "^19.2.7",
"react-router-dom": "^7.18.1", "react-router-dom": "7.18.1",
"vite": "^8.0.16", "vite": "^8.0.16",
"zustand": "^5.0.14" "zustand": "^5.0.14"
}, },
@@ -43,5 +44,8 @@
"@types/react": "^19.2.17", "@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"typescript": "^6.0.3" "typescript": "^6.0.3"
},
"overrides": {
"postcss": "8.5.23"
} }
} }
+3
View File
@@ -40,6 +40,9 @@ echo "[deploy] Installing dependencies"
npm ci --include=dev npm ci --include=dev
npm --prefix api 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" echo "[deploy] Generating Prisma client and applying migrations"
npm --prefix api run prisma:generate npm --prefix api run prisma:generate
npm --prefix api run prisma:migrate:deploy npm --prefix api run prisma:migrate:deploy
@@ -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'}`);
}