Recipe:安全文件访问与原子写¶
1. 场景与非场景¶
适用:需要在 Node 内把外部提供的路径限制在某个根目录内,并向受收容路径原子写入普通文件,失败时回滚不留残影。
不适用:需要把文件选择的业务规则放入 Foundation(业务规则由调用方拥有);需要写入未受收容路径。
2. 架构选择及能力 ID¶
首选能力:foundation.harness.path-containment(路径分类与受收容解析)与 foundation.harness.atomic-write(受收容路径内原子写)。两者都只实现机制,不拥有语义。
3. 前置条件与信任边界¶
- 调用方提供明确的根目录(收容边界)。
- 信任边界:所有解析结果必须落在根目录内;越界、符号链接逃逸、真实路径逃逸均被拒绝。
4. 最小代码¶
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { resolveContained, writeFileAtomic } from "skill-family-harness-node";
const root = mkdtempSync(join(tmpdir(), "sf-safe-"));
const target = resolveContained(root, "nested/config.json"); // 受收容绝对路径
await writeFileAtomic(target, JSON.stringify({ ok: true }));
以上代码展示了先收容路径、再原子写入的基本顺序;writeFileAtomic 用临时文件 + fsync + rename,失败回滚。
5. 预期输出与证据¶
resolveContained 返回落在根内的绝对路径;writeFileAtomic 写入完成的文件,字节与输入一致。证据:packages/skill-family-harness-node/test/containment.test.mjs 与 test/atomic.test.mjs。
6. 安全/失败负例¶
// 负例:试图越出根目录 -> SFC2004 (path-traversal)
import { resolveContained } from "skill-family-harness-node";
resolveContained(root, "../escape.json"); // 抛 HarnessError,details.kind = "path-traversal"
越界、symlink-escape、realpath-escape 均归 SFC2004 加稳定 details.kind;原子写对越界路径或非法数据类型抛错并回滚临时文件。
7. 可复制验证命令¶
node --test packages/skill-family-harness-node/test/containment.test.mjs \
packages/skill-family-harness-node/test/atomic.test.mjs
8. 调用方继续拥有的业务逻辑¶
- 哪些路径是业务允许的选择规则(不在 Foundation 内)。
- 写入内容的业务正确性。
9. 升级与回滚注意事项¶
- 路径收容与原子写均为受收容机制,失败回滚不留半成品,无外部回滚需求。
- 升级跟随
skill-family-harness-node版本;HARNESS_EXCLUSIONS不引入远端或 git 写。