- @openclaw/fs-safe là primitive mới cho Node.js giúp confine mọi filesystem operation trong một root handle, chặn path traversal, symlink swap, hardlink alias và TOCTOU race condition.
- API đơn giản: gọi root() một lần, mọi write/read sau đó tự validate boundary.
- MIT license, yêu cầu Node 20.11+.
TL;DR
@openclaw/fs-safe là một Node.js package mới vừa được tách ra từ OpenClaw. Thay vì dùng path.normalize() hay startsWith(root) để kiểm tra path an toàn, bạn tạo một root handle - mọi filesystem operation đều chạy trong boundary đó và tự động throw nếu có ai cố escape. Sinh ra từ những CVE thật trong chính OpenClaw, không phải security theater.
Vấn đề với String Normalization
Pattern này xuất hiện ở khắp nơi trong Node.js code:
const safe = path.resolve(root, userInput);
if (!safe.startsWith(root)) throw new Error("escape");
fs.readFileSync(safe); // <-- vẫn có thể bị exploit
Trông ổn, nhưng có ít nhất 3 lỗ hổng:
- TOCTOU race: attacker swap symlink vào thư mục giữa bước check và bước read
- Hardlink alias: file ngoài root được hardlink vào trong -
startsWithpass, nhưng nội dung là file ngoài - Symlink chain: một component trong path trỏ ra ngoài sau khi validation xong
OpenClaw học bài này theo cách đắt nhất: CVE-2026-26972 (path traversal qua browser download helper, moderate severity) và GHSA-5h3g-6xhh-rg6p (TOCTOU trong readFile bridge, fixed 2026.4.22). Cả hai đều bypass được string-level check.
Root Handle Hoạt Động Thế Nào
fs-safe dùng mô hình capability-style: bạn mở một root một lần, nhận về handle, và sau đó chỉ dùng handle đó.
import { root } from "@openclaw/fs-safe";
const fs = await root("/safe/workspace");
await fs.write("notes/today.txt", "hello\n"); // OK
await fs.write("../escape.txt", "x"); // throws FsSafeError: outside-workspace
Không có cửa sổ race vì mọi operation dùng cùng fd-context từ lúc root() được gọi. Symlink được resolve tại thời điểm mở, không phải tại thời điểm dùng. Hardlink được detect qua inode comparison.
Trên Linux/BSD, fs-safe dùng O_NOFOLLOW và một Python helper nhỏ cho fd-relative rename/unlink - những syscall mà Node.js không expose ergonomically. Bật bằng OPENCLAW_FS_SAFE_PYTHON_MODE=auto.
Những Gì Bạn Nhận Được
fs-safe không chỉ wrap fs module thêm một lớp check. Nó cung cấp primitives đủ để xây cả một file-handling layer an toàn:
| Feature | Chi tiết |
|---|---|
| Path validation | Chặn .., symlink swap, hardlink alias, TOCTOU |
| Atomic writes | temp + rename pattern, optional fsync - không bị corrupt khi crash giữa chừng |
| Archive extraction | ZIP/TAR có entry-count & byte-size budget - chặn zip bomb và zip slip |
| JSON store | Atomic read-modify-write với file locking - an toàn với concurrent access |
| Secret-file helpers | Validate owner, permissions, hardlink count trước khi đọc credential file |
| Error types | FsSafeError với closed code union: outside-workspace, symlink, hardlink, path-mismatch, not-found |
Đặc biệt với phần archive: ZIP slip là lỗ hổng cực kỳ phổ biến khi extract file từ user. ../../../etc/cron.d/backdoor là entry name hợp lệ trong ZIP - nếu bạn không validate từng entry trước khi write, bạn đang cài cửa hậu cho attacker. fs-safe check entry-by-entry và throw ngay khi phát hiện escape.
Ai Nên Dùng Ngay
fs-safe sinh ra để giải quyết một class lỗ hổng cụ thể: path từ nguồn không tin cậy. Nếu app của bạn nhận path từ bất kỳ một trong những nguồn này, bạn cần một root handle:
- AI agent / plugin system: LLM trả về path, plugin cung cấp filename - đây là attack surface lớn nhất hiện tại
- File upload handler: filename traversal trong multipart là vector cổ điển
- Config loader: user chỉ định đường dẫn file config
- Archive extractor: ZIP slip vào
/etcnếu không validate từng entry
Nếu path 100% từ code của bạn, không cần fs-safe. Nhưng nếu có bất kỳ external input nào chạm vào filesystem - dùng nó.
Giới Hạn Cần Biết
fs-safe là filesystem boundary, không phải full sandbox. Nó không isolate CPU, memory hay network - process vẫn chạy với đủ quyền, chỉ có filesystem access bị constrain trong root đã định nghĩa. Nếu bạn cần isolate process hoàn toàn, cần kết hợp với container hoặc seccomp profiles.
Một số điểm cần lưu ý:
- Windows: không có
O_NOFOLLOWequivalent, symlink protection yếu hơn Linux. Python helper cần Python runtime để bật fd-relative hardening đầy đủ trên POSIX. - Node 20.11+ bắt buộc: package dùng các API mới của Node để implement security semantics. Node 18 không được support.
- Không chặn được logic bug: nếu code của bạn tự construct path sai, fs-safe không giúp được. Nó chỉ protect perimeter - boundary giữa trusted và untrusted input.
Cuối cùng: fs-safe là primitive, không phải silver bullet. Bạn vẫn cần thiết kế đúng threat model - biết data nào là trusted, data nào là untrusted, và enforce boundary tại đúng chỗ.
Bắt Đầu
npm install @openclaw/fs-safe
MIT license. Docs tại fs-safe.io. Source tại github.com/openclaw/fs-safe.
Nguồn: steipete trên X, OpenClaw Security Docs.
