openclaw/openclaw · critical · Error
non-canonical file path
Error message
non-canonical file path
What it means
validateOwnedFile requires path.resolve(filePath) to equal fs.realpath(filePath), meaning no component anywhere along the path is a symbolicic link. If realpath differs (the path resolves through one or more symlinks), the path is non-canonical and throws.
Source
Thrown at extensions/browser/src/browser/extension-native-host.ts:83
async function validateOwnedFile(filePath: string, executable: boolean): Promise<string> {
const resolved = path.resolve(filePath);
const info = await fs.lstat(resolved);
if (!info.isFile() || info.isSymbolicLink()) {
throw new Error("unsafe file type");
}
if (process.platform !== "win32") {
const uid = process.getuid?.();
if (uid !== undefined && info.uid !== uid) {
throw new Error("foreign file owner");
}
const mode = info.mode & 0o777;
if ((mode & 0o077) !== 0 || (executable && (mode & 0o100) === 0)) {
throw new Error("unsafe file mode");
}
}
const canonical = await fs.realpath(resolved);
if (canonical !== resolved) {
throw new Error("non-canonical file path");
}
return canonical;
}
async function validateNativeManifest(params: {
manifestPath: string;
launcherPath: string;
callerOrigin: string;
expectedOrigins: string[];
stateDir?: string;
}): Promise<void> {
const manifestPath = await validateOwnedFile(params.manifestPath, false);
const launcherPath = await validateOwnedFile(params.launcherPath, true);
const managedRoot = path.resolve(
params.stateDir ?? resolveStateDir(),
"browser",
"native-messaging",
);View on GitHub (pinned to 01804a7531)
Solutions
- Set OPENCLAW_STATE_DIR to a canonical (realpath-resolved) path with no symlink components.
- Resolve symlinks in the state directory before passing it: fs.realpathSync(dir).
- On macOS, prefer canonical paths (/private/var, /Users) over their symlinked aliases (/var).
Example fix
// before: OPENCLAW_STATE_DIR=/var/lib/openclaw (symlink) -> "non-canonical file path"
// after: use the realpath
process.env.OPENCLAW_STATE_DIR = fs.realpathSync("/var/lib/openclaw"); Defensive patterns
Strategy: try-catch
Validate before calling
import fs from "node:fs/promises";
import path from "node:path";
async function isCanonicalPath(p: string): Promise<boolean> {
const resolved = path.resolve(p);
try {
return (await fs.realpath(resolved)) === resolved;
} catch {
return false;
}
} Try / catch
try {
await validateNativeManifest(params);
} catch (error) {
if (error instanceof Error && error.message === "non-canonical file path") {
// resolve OPENCLAW_STATE_DIR with realpath to remove symlink components
}
} Prevention
- Set OPENCLAW_STATE_DIR to a realpath-resolved value with no symlink components.
- On macOS, avoid /var aliases; prefer /private/var or /Users.
- Resolve symlinks in HOME before relying on it for state paths.
When it happens
Trigger: Any directory component of the manifest or launcher path is a symlink, so fs.realpath differs from path.resolve.
Common situations: OPENCLAW_STATE_DIR points into a symlinked directory (common on macOS where /var -> /private/var); a symlinked HOME; the native-messaging directory itself is a symlink; deployment on a system with symlinked /home or /tmp.
Related errors
- Refusing to overwrite ${existing.state} native host: ${manif
- Refusing to overwrite foreign native host launcher: ${launch
- unsafe file type
- foreign file owner
- unsafe file mode
AI-assisted analysis of openclaw/openclaw@01804a7531 (2026-08-12).
Data as JSON: /api/errors/5521494841e029c1.
Report an issue: GitHub.