Repository navigation
Conversation
…eDir Audit finding: ArtifactStore joined an unsanitized sessionId into the storage directory; worker session ids derive from model-generated tool_call ids (deriveWorkerSessionId only rewrites ':'), so a crafted id could escape baseDir and write files anywhere. Add safeSessionId(): character sanitization (non [a-zA-Z0-9._-] -> '-') plus a resolve() containment assertion that rejects bare '..'. Applied in the constructor (write sink) and addFallbackSession (read sink, so both resolve to the same directory). Regression tests: src/artifact/__tests__/store-path-traversal.test.ts
|
CI 说明(奶娃核验):本 PR 的红( |
sky-mirrors
left a comment
There was a problem hiding this comment.
独立复验通过 ✅
在 origin/main @ abb7628(v3.30.0 republish 最新)上以 git apply 套用本 PR diff 实测:
- 专项测试:
src/artifact/__tests__/store-path-traversal.test.ts5/5 通过(329ms)——含裸..逃逸拒绝、sanitize 后save()保持在 baseDir 内且 escape 路径无文件创建、worker-../../x/y型 id 跨重启可读; - 回归:
store.test.ts15/15、summarize.test.ts全绿,无破坏。
实现核验:safeSessionId 双防线结构正确——字符过滤([^a-zA-Z0-9._-])挡住分隔符注入,resolve 前缀断言兜住穿过过滤的裸 ..;读路径(fallback session ids / _index.jsonl / rawPath 重建)与写路径同源消毒,resolve(baseDir) 前缀化统一。读写 sink 一致性有测试钉住。
可收编。
|
已通过 sync 流程合入 dev,感谢 @Ver-zhzh 的贡献 🎉 本 PR 的改动(fix(artifact): sanitize sessionId to block path traversal outside baseDir)已经 sync 流程合入主仓(本次 sync),并已收录进 CONTRIBUTORS.md。 按仓库的双仓流程,公开仓 PR 不经 merge 按钮合入(保持 dev 主仓唯一事实源),现以 sync-merged 标记关闭。欢迎继续贡献! |
… traversal outside baseDir 内容已经 sync 流程合入(本次 sync),本提交为作者署名落账: GitHub 贡献者图谱按 Co-authored-by trailer 计入。 Co-authored-by: Ver-zhzh <Ver-zhzh@users.noreply.github.com>
… traversal outside baseDir 内容已经 sync 流程合入(本次 sync),本提交为作者署名落账: GitHub 贡献者图谱按 Co-authored-by trailer 计入。 Co-authored-by: Ver-zhzh <Ver-zhzh@users.noreply.github.com>
修复批次:ArtifactStore sessionId 路径穿越
1. ArtifactStore sessionId 路径穿越
现象:
sessionId = "../../PWNED-x"→rawPath逃出baseDir;toolUseId = "batch:../../../../PWNED-escape"派生的 worker sessionId → 写到文件系统根。writeFile+_index.jsonlappend 落盘任意目录。根因:
src/artifact/store.ts:73join(baseDir, sessionId)直接使用未净化的 sessionId;上游deriveWorkerSessionId(work-order.ts:135)只把:换成-,..、/全部保留。修复:新增
safeSessionId()—— 字符级净化([^a-zA-Z0-9._-]→-)+resolve()后断言不出baseDir(能过字符过滤的裸..由断言拦截,throw)。应用点:构造函数(写路径 sink)与addFallbackSession(读路径 sink,与forSession()同一输入必须落到同一目录)。deriveWorkerSessionId不改(只修 sink,保持最小化)。未修观察(如实报告,非本批引入)
safeArtifactFileStem已把 artifact id 净化;无删除 API;cleanupOldArtifactSessions名字来自readdirdirent,不含/。save()对rawContent无大小上限(磁盘配额面未防护);readLineRange已有MAX_RANGE_LINES截断 + 流式,read-section整读路径有 2MB 门——配额策略是设计面问题,未纳入本批。store落盘工具输出未经scrubMemoryText(含密钥的工具输出明文存于.rivet/artifacts/);memory scrub 已有但 artifact 写入路径未接入——是否需要 scrub+索引分离存储,需作者定口径,未纳入本批。验证
npx tsc --noEmitnpm test -- src/artifactstore-path-traversal.test.ts)../../PWNED-smoke不出 baseDir;正常 save 落盘;worker 风格 id 净化后目录一致;fallback 与 forSession 同目录Closes #450 相关审计后续(无独立 issue;本批为漏洞审计挖出的真实可利用问题)。