跳转至

简体中文

Recipe: Safe File Access and Atomic Write

1. Scenario and Non-Scenario

Applicable: you need to constrain, within Node, an externally-provided path to within some root directory, and atomically write an ordinary file to a contained path, rolling back on failure without leaving residue.

Not applicable: needs to put file-selection business rules into Foundation (business rules are owned by the caller); needs to write to an uncontained path.

2. Architecture Choice and Capability ID

Preferred capabilities: foundation.harness.path-containment (path classification and contained resolution) and foundation.harness.atomic-write (atomic write within contained paths). Both implement only the mechanism, owning no semantics.

3. Preconditions and Trust Boundary

  • The caller provides an explicit root directory (containment boundary).
  • Trust boundary: all resolution results must fall within the root directory; out-of-bounds, symlink escape, and realpath escape are all refused.

4. Minimal Code

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"); // contained absolute path
await writeFileAtomic(target, JSON.stringify({ ok: true }));

The code above shows the basic order of first containing the path, then atomically writing; writeFileAtomic uses temp-file + fsync + rename, rolling back on failure.

5. Expected Output and Evidence

resolveContained returns an absolute path within the root; writeFileAtomic writes the completed file, whose bytes are consistent with the input. Evidence: packages/skill-family-harness-node/test/containment.test.mjs and test/atomic.test.mjs.

6. Safety / Failure Negative Case

// Negative case: attempting to escape the root -> SFC2004 (path-traversal)
import { resolveContained } from "skill-family-harness-node";
resolveContained(root, "../escape.json"); // throws HarnessError, details.kind = "path-traversal"

Out-of-bounds, symlink-escape, and realpath-escape all map to SFC2004 with a stable details.kind; atomic write throws on an out-of-bounds path or illegal data type and rolls back the temp file.

7. Copy-Paste Verification Command

node --test packages/skill-family-harness-node/test/containment.test.mjs \
           packages/skill-family-harness-node/test/atomic.test.mjs

8. Business Logic the Caller Continues to Own

  • Which paths are business-allowed selection rules (not within Foundation).
  • The business correctness of the written content.

9. Upgrade and Rollback Notes

  • Path containment and atomic write are contained mechanisms; on failure they roll back without leaving half-products, so no external rollback is needed.
  • Upgrade follows the skill-family-harness-node version; HARNESS_EXCLUSIONS introduces no remote or git writes.