{"record":{"id":"6b0cd9e2c4cc9e35","repo":"openclaw/openclaw","slug":"foreign-file-owner","errorCode":null,"errorMessage":"foreign file owner","messagePattern":"foreign file owner","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"extensions/browser/src/browser/extension-native-host.ts","lineNumber":74,"sourceCode":"    }\n  }\n  validateExpectedOrigins(expectedOrigins);\n  if (!EXTENSION_ORIGIN_PATTERN.test(callerOrigin)) {\n    throw new Error(\"missing Chrome extension origin\");\n  }\n  return { expectedOrigins, callerOrigin };\n}\n\nasync function validateOwnedFile(filePath: string, executable: boolean): Promise<string> {\n  const resolved = path.resolve(filePath);\n  const info = await fs.lstat(resolved);\n  if (!info.isFile() || info.isSymbolicLink()) {\n    throw new Error(\"unsafe file type\");\n  }\n  if (process.platform !== \"win32\") {\n    const uid = process.getuid?.();\n    if (uid !== undefined && info.uid !== uid) {\n      throw new Error(\"foreign file owner\");\n    }\n    const mode = info.mode & 0o777;\n    if ((mode & 0o077) !== 0 || (executable && (mode & 0o100) === 0)) {\n      throw new Error(\"unsafe file mode\");\n    }\n  }\n  const canonical = await fs.realpath(resolved);\n  if (canonical !== resolved) {\n    throw new Error(\"non-canonical file path\");\n  }\n  return canonical;\n}\n\nasync function validateNativeManifest(params: {\n  manifestPath: string;\n  launcherPath: string;\n  callerOrigin: string;\n  expectedOrigins: string[];","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/browser/src/browser/extension-native-host.ts#L56-L92","documentation":"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.","triggerScenarios":"validateOwnedFile runs under process.getuid() === A but the manifest or launcher file's stat uid is B (different user).","commonSituations":"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.","solutions":["Reinstall as the same user account that runs the browser and native host.","chown the manifest and launcher files to the current user if that is intentional and safe.","Run `openclaw doctor --fix` under the correct user account to repair ownership."],"exampleFix":"// before: files owned by root after a sudo install\n// sudo chown -R $USER \"$OPENCLAW_STATE_DIR/browser\"\n// after: reinstall as the normal user\nawait installChromeExtensionBootstrap({ bundledDir, pluginRoot, deps });","handlingStrategy":"try-catch","validationCode":"import fs from \"node:fs/promises\";\nasync function fileOwnedByCurrentUser(p: string): Promise<boolean> {\n  if (process.platform === \"win32\") return true;\n  const uid = process.getuid?.();\n  if (uid === undefined) return true;\n  try {\n    const st = await fs.lstat(p);\n    return st.uid === uid;\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await validateNativeManifest(params);\n} catch (error) {\n  if (error instanceof Error && error.message === \"foreign file owner\") {\n    // chown the file to the current user or reinstall under the correct account\n  }\n}","preventionTips":["Install and run the native host under the same user account.","Avoid `sudo` for installs that create per-user native messaging files.","Run `openclaw doctor --fix` as the browser user after ownership changes."],"tags":["browser-extension","native-messaging","security","filesystem","permissions"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}