openclaw/openclaw · critical · Error

foreign file owner

Error message

foreign file owner

What it means

On non-Windows platforms, validateOwnedFile requires the manifest and launcher files to be owned by the current process uid (process.getuid()). A file whose lstat uid differs from the process uid throws. This prevents the native host from trusting files controlled by another user.

Source

Thrown at extensions/browser/src/browser/extension-native-host.ts:74

    }
  }
  validateExpectedOrigins(expectedOrigins);
  if (!EXTENSION_ORIGIN_PATTERN.test(callerOrigin)) {
    throw new Error("missing Chrome extension origin");
  }
  return { expectedOrigins, callerOrigin };
}

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[];

View on GitHub (pinned to 01804a7531)

Solutions

  1. Reinstall as the same user account that runs the browser and native host.
  2. chown the manifest and launcher files to the current user if that is intentional and safe.
  3. Run `openclaw doctor --fix` under the correct user account to repair ownership.

Example fix

// before: files owned by root after a sudo install
// sudo chown -R $USER "$OPENCLAW_STATE_DIR/browser"
// after: reinstall as the normal user
await installChromeExtensionBootstrap({ bundledDir, pluginRoot, deps });
Defensive patterns

Strategy: try-catch

Validate before calling

import fs from "node:fs/promises";
async function fileOwnedByCurrentUser(p: string): Promise<boolean> {
  if (process.platform === "win32") return true;
  const uid = process.getuid?.();
  if (uid === undefined) return true;
  try {
    const st = await fs.lstat(p);
    return st.uid === uid;
  } catch {
    return false;
  }
}

Try / catch

try {
  await validateNativeManifest(params);
} catch (error) {
  if (error instanceof Error && error.message === "foreign file owner") {
    // chown the file to the current user or reinstall under the correct account
  }
}

Prevention

When it happens

Trigger: validateOwnedFile runs under process.getuid() === A but the manifest or launcher file's stat uid is B (different user).

Common situations: The extension was installed under a different user account; running the native host after a `sudo install` changed ownership to root; a multi-user machine where another user's OpenClaw owns the files; OPENCLAW_STATE_DIR points at a shared location owned by another user.

Related errors


AI-assisted analysis of openclaw/openclaw@01804a7531 (2026-08-12). Data as JSON: /api/errors/6b0cd9e2c4cc9e35. Report an issue: GitHub.