docs(fs): document boundary guardrails

This commit is contained in:
Peter Steinberger 2026-05-06 21:21:42 +01:00
parent 925dbfa29b
commit c2e5849039
No known key found for this signature in database
3 changed files with 21 additions and 4 deletions

View File

@ -6,10 +6,13 @@
- Reject `fileStore()` and `fileStoreSync()` writes through symlinked parent directories so store commits cannot escape the configured root.
- Harden Root fallback mutators, archive merges, private store reads/writes, durable queue ids, JSON fallback writes, sibling temp writes, temp filename sanitization, and trash moves against symlink-swap and path traversal edge cases.
- Centralize safe path segment validation, directory identity guards, and guarded mutation wrappers so future filesystem helpers reuse the same race-resistant checks.
- Route JSON sync writes, archive ZIP staging, temp workspace sync reads, secret-file commits, and atomic move/replace fallbacks through shared pinned-read or guarded-write primitives.
### Tests
- Added regression coverage for the filesystem race and traversal findings fixed in this release.
- Added a static filesystem-boundary primitive check that blocks reintroducing known raw copy/read/guard patterns.
- Increased filesystem edge coverage around secure temp fallback handling, sibling-temp cleanup, local-root resolution, file locks, and file identity checks.
- Prevented POSIX test runs from leaving Windows-style secure-temp fallback paths in the repository root.

View File

@ -68,7 +68,12 @@ If `beforeRename` throws, the rename is skipped and the temp file is removed —
### `EPERM` and copy fallback
On systems where `rename` fails with `EPERM`/`EEXIST`, pass `copyFallbackOnPermissionError: true` to fall back to copy + unlink. The fallback refuses symlink destinations before copying so it does not write through a replaced destination link.
On systems where `rename` fails with `EPERM`/`EEXIST`, pass
`copyFallbackOnPermissionError: true` to fall back to a non-atomic copy
replacement. The fallback removes the old destination, opens the replacement
with exclusive/no-follow flags where the platform supports them, and refuses
known symlink destinations so it does not write through a replaced destination
link.
### Sync variant
@ -117,9 +122,8 @@ Rename a path. If the rename fails with `EXDEV` (cross-device) or `EPERM`, fall
import { movePathWithCopyFallback } from "@openclaw/fs-safe/atomic";
await movePathWithCopyFallback({
source: "/srv/cache/blob.bin",
destination: "/srv/persistent/blob.bin",
overwrite: true,
from: "/srv/cache/blob.bin",
to: "/srv/persistent/blob.bin",
});
```

View File

@ -159,6 +159,16 @@ Run only the security boundary corpus while iterating on root/path/archive/temp
pnpm test:security
```
Run the static primitive guard after changing low-level filesystem helpers:
```sh
pnpm lint:fs-boundary
```
It catches the specific raw fallback patterns that previously led to
check-then-use bugs, such as direct copy-to-destination fallback and sync temp
workspace reads that bypass pinned file descriptors.
`pnpm check` also runs `pnpm lint:file-size`. New source and test files should stay under 500 lines. Existing larger files have explicit budgets in `scripts/check-file-size.mjs`; do not increase those budgets as part of unrelated work.
## See also