Pinned open
openPinnedFileSync() is a low-level synchronous file open that re-pins identity after the open: it opens with O_NOFOLLOW, then fstats and verifies the result matches what the path resolves to. It is the building block under Root.open() and a few of the other primitives — exposed for callers that want the same defense without going through root().
import { openPinnedFileSync } from "@openclaw/fs-safe/advanced";
#Why a separate primitive
fs.openSync plus fs.statSync plus fs.realpathSync is three syscalls and a swap window. openPinnedFileSync performs them in one helper that:
- Opens with
O_NOFOLLOW(where the platform supports it). fstats the open fd.- Compares the fd's identity to the resolved path's identity.
- Returns a typed result:
okplus thefd, ornot-okwith areason.
If you're already using root().open(), you don't need this — but if you're building a new helper that needs a synchronous pinned open, this is the foundation.
#Signature
type PinnedOpenSyncFailureReason = "path" | "validation" | "io";
type PinnedOpenSyncAllowedType = "file" | "directory";
type PinnedOpenSyncResult =
| { ok: true; fd: number; stat: Stats }
| { ok: false; reason: PinnedOpenSyncFailureReason; cause?: unknown };
function openPinnedFileSync(params: {
rootDir: string; // canonical root absolute path
filePath: string; // absolute target path
fs?: PinnedOpenSyncFs; // injectable for tests
allowedType?: PinnedOpenSyncAllowedType; // default "file"
flags?: number; // default O_RDONLY | O_NOFOLLOW
}): PinnedOpenSyncResult;
PinnedOpenSyncFs is a small subset of node:fs:
type PinnedOpenSyncFs = Pick<
typeof fs,
"openSync" | "fstatSync" | "closeSync" | "realpathSync" | "lstatSync"
>;
#Reasons for failure
"path"— the resolved path does not stay insiderootDir, contains a symlink, or otherwise fails the lexical/canonical check."validation"— the open succeeded butfstatdoes not match the path's identity (TOCTOU race), the file is not the allowed type, or it has unexpectednlink."io"— the underlyingopenSyncthrew. Inspectcausefor the originalNodeJS.ErrnoException.
The caller is responsible for closing the fd on success:
import fs from "node:fs";
import { openPinnedFileSync } from "@openclaw/fs-safe/advanced";
const r = openPinnedFileSync({
rootDir: "/srv/workspace",
filePath: "/srv/workspace/state.json",
});
if (!r.ok) {
if (r.reason === "io") throw new Error("io error", { cause: r.cause });
throw new Error(`pinned open refused: ${r.reason}`);
}
try {
const buf = Buffer.alloc(r.stat.size);
fs.readSync(r.fd, buf, 0, buf.length, 0);
// ...
} finally {
fs.closeSync(r.fd);
}
#Allowed type
By default the helper requires the result to be a regular file. Pass allowedType: "directory" when you want to pin-open a directory (for fdopendir-style use). Any other type triggers "validation".
#Test injection
The optional fs field accepts a partial node:fs interface. Use it in unit tests to simulate a TOCTOU swap or a denied open:
import { openPinnedFileSync } from "@openclaw/fs-safe/advanced";
const fakeFs = {
...fs,
openSync: () => fakeFd,
fstatSync: () => ({ ...realStat, ino: differentIno } as Stats),
closeSync: () => {},
realpathSync: () => filePath,
lstatSync: () => realStat,
};
const r = openPinnedFileSync({ rootDir, filePath, fs: fakeFs });
expect(r.ok).toBe(false);
expect(r.reason).toBe("validation");
#Companions
The same module exports a higher-level helper used by the rest of the library:
import {
canUseRootFileOpen,
matchRootFileOpenFailure,
openRootFile,
openRootFileSync,
} from "@openclaw/fs-safe/advanced";
openRootFileSync(params)—openPinnedFileSyncplus a richer result that distinguishes"validation"failures by sub-reason.openRootFile(params)— async variant, accepts asignal: AbortSignalfor cancellation.canUseRootFileOpen(io)— checks whether the platform supportsO_NOFOLLOW. Useful for falling back when the real defense is unavailable.matchRootFileOpenFailure(failure, handlers)— exhaustive switch helper for branching on the failure reason.
#See also
root()— the high-level wrapper most callers want.- Reading — read pipeline that uses
openPinnedFileSyncinternally. - Path helpers — the lexical building blocks (
isPathInside,safeRealpathSync).