Skip to content

fix(artifact): sanitize sessionId to block path traversal outside baseDir - #453

Closed
Ver-zhzh wants to merge 1 commit into
huiliyi37:mainfrom
Ver-zhzh:secaudit/artifact-store-20261012
Closed

Ver-zhzh wants to merge 1 commit into
huiliyi37:mainfrom
Ver-zhzh:secaudit/artifact-store-20261012

Conversation

@Ver-zhzh

Copy link
Copy Markdown
Collaborator

修复批次:ArtifactStore sessionId 路径穿越

安全审计(src/artifact 模块,2026-10-12)新发现:ArtifactStore 的 sessionId 被当作原始路径分量拼进存储目录,无任何字符约束。sessionId 在 worker 路径上可由 model 生成的 tool_calls[].id 经 deriveWorkerSessionId 派生 → baseDir 外建目录并落盘(沙箱逃逸/任意目录写入原语)。磁盘级 PoC 已实测(探针已清理)。

1. ArtifactStore sessionId 路径穿越

现象:sessionId = "../../PWNED-x" → rawPath 逃出 baseDir;toolUseId = "batch:../../../../PWNED-escape" 派生的 worker sessionId → 写到文件系统根。writeFile + _index.jsonl append 落盘任意目录。
根因:src/artifact/store.ts:73 join(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 名字来自 readdir dirent,不含 /。
  • save() 对 rawContent 无大小上限(磁盘配额面未防护);readLineRange 已有 MAX_RANGE_LINES 截断 + 流式,read-section 整读路径有 2MB 门——配额策略是设计面问题,未纳入本批。
  • store 落盘工具输出未经 scrubMemoryText(含密钥的工具输出明文存于 .rivet/artifacts/);memory scrub 已有但 artifact 写入路径未接入——是否需要 scrub+索引分离存储,需作者定口径,未纳入本批。

验证

项 结果
npx tsc --noEmit 无新增报错
npm test -- src/artifact 30/30 通过(新增回归测试 store-path-traversal.test.ts)
真机冒烟 ../../PWNED-smoke 不出 baseDir;正常 save 落盘;worker 风格 id 净化后目录一致;fallback 与 forSession 同目录

Closes #450 相关审计后续(无独立 issue;本批为漏洞审计挖出的真实可利用问题)。

…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
@Ver-zhzh

Copy link
Copy Markdown
Collaborator Author

CI 说明(奶娃核验):本 PR 的红(ci (24) + windows-smoke)为上游基线红,非本改动引入。失败用例集合(capsule-recall / AtomGit-DRY RUN / publishers / max-lines ratchet / architecture guards / RSS 基线;windows-smoke 为 runner 临时目录 EPERM)与 #447 同一套 CI 跑出的失败集合逐项一致。npm test -- src/artifact 在本分支 30/30 全绿,npx tsc --noEmit 干净。

@sky-mirrors sky-mirrors left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

独立复验通过 ✅

在 origin/main @ abb7628(v3.30.0 republish 最新)上以 git apply 套用本 PR diff 实测:

  • 专项测试:src/artifact/__tests__/store-path-traversal.test.ts 5/5 通过(329ms)——含裸 .. 逃逸拒绝、sanitize 后 save() 保持在 baseDir 内且 escape 路径无文件创建、worker-../../x/y 型 id 跨重启可读;
  • 回归:store.test.ts 15/15、summarize.test.ts 全绿,无破坏。

实现核验:safeSessionId 双防线结构正确——字符过滤([^a-zA-Z0-9._-])挡住分隔符注入,resolve 前缀断言兜住穿过过滤的裸 ..;读路径(fallback session ids / _index.jsonl / rawPath 重建)与写路径同源消毒,resolve(baseDir) 前缀化统一。读写 sink 一致性有测试钉住。

可收编。

@huiliyi37

Copy link
Copy Markdown
Owner

已通过 sync 流程合入 dev,感谢 @Ver-zhzh 的贡献 🎉

本 PR 的改动(fix(artifact): sanitize sessionId to block path traversal outside baseDir)已经 sync 流程合入主仓(本次 sync),并已收录进 CONTRIBUTORS.md。

按仓库的双仓流程,公开仓 PR 不经 merge 按钮合入(保持 dev 主仓唯一事实源),现以 sync-merged 标记关闭。欢迎继续贡献!

@huiliyi37 huiliyi37 added the sync-merged 内容已通过 sync 流程合入 dev 主仓(PR 形态留痕,作者计入 CONTRIBUTORS) label Oct 12, 2026
@huiliyi37 huiliyi37 closed this Oct 12, 2026
huiliyi37 added a commit that referenced this pull request Oct 12, 2026
… traversal outside baseDir

内容已经 sync 流程合入(本次 sync),本提交为作者署名落账:
GitHub 贡献者图谱按 Co-authored-by trailer 计入。

Co-authored-by: Ver-zhzh <Ver-zhzh@users.noreply.github.com>
huiliyi37 pushed a commit that referenced this pull request Oct 12, 2026
huiliyi37 added a commit that referenced this pull request Oct 23, 2026
… traversal outside baseDir

内容已经 sync 流程合入(本次 sync),本提交为作者署名落账:
GitHub 贡献者图谱按 Co-authored-by trailer 计入。

Co-authored-by: Ver-zhzh <Ver-zhzh@users.noreply.github.com>
huiliyi37 pushed a commit that referenced this pull request Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sync-merged 内容已通过 sync 流程合入 dev 主仓(PR 形态留痕,作者计入 CONTRIBUTORS)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] sidecar 启动期 ACL 加固全树递归 + 10s 硬超时:数据目录涨到 48k 文件后本地服务必崩且不自愈

3 participants