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 - startsWith pass, 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:

FeatureChi tiết
Path validationChặn .., symlink swap, hardlink alias, TOCTOU
Atomic writestemp + rename pattern, optional fsync - không bị corrupt khi crash giữa chừng
Archive extractionZIP/TAR có entry-count & byte-size budget - chặn zip bomb và zip slip
JSON storeAtomic read-modify-write với file locking - an toàn với concurrent access
Secret-file helpersValidate owner, permissions, hardlink count trước khi đọc credential file
Error typesFsSafeError 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 /etc nế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_NOFOLLOW equivalent, 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.