fix(doctor): skip false positive permission warnings for Nix store symlinks (#24901)

On NixOS/Nix-managed installs, config and state directories are symlinks
into /nix/store/. Symlinks on Linux always report 0o777 via lstatSync,
causing `openclaw doctor` to incorrectly warn about open permissions.

Use lstatSync to detect symlinks, resolve the target, and only suppress
the warning when the resolved path lives in /nix/store/ (an immutable
filesystem). Symlinks to insecure targets still trigger warnings.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Soumik Bhatta
2026-02-23 22:22:52 -05:00
committed by GitHub
parent 588ad7fb38
commit 70cfb69a5f

View File

@@ -261,8 +261,15 @@ export async function noteStateIntegrity(
}
if (stateDirExists && process.platform !== "win32") {
try {
const stat = fs.statSync(stateDir);
if ((stat.mode & 0o077) !== 0) {
const dirLstat = fs.lstatSync(stateDir);
const isDirSymlink = dirLstat.isSymbolicLink();
// For symlinks, check the resolved target permissions instead of the
// symlink itself (which always reports 777). Skip the warning only when
// the target lives in a known immutable store (e.g. /nix/store/).
const stat = isDirSymlink ? fs.statSync(stateDir) : dirLstat;
const resolvedDir = isDirSymlink ? fs.realpathSync(stateDir) : stateDir;
const isImmutableStore = resolvedDir.startsWith("/nix/store/");
if (!isImmutableStore && (stat.mode & 0o077) !== 0) {
warnings.push(
`- State directory permissions are too open (${displayStateDir}). Recommend chmod 700.`,
);
@@ -282,10 +289,14 @@ export async function noteStateIntegrity(
if (configPath && existsFile(configPath) && process.platform !== "win32") {
try {
const linkStat = fs.lstatSync(configPath);
const stat = fs.statSync(configPath);
const isSymlink = linkStat.isSymbolicLink();
if (!isSymlink && (stat.mode & 0o077) !== 0) {
const configLstat = fs.lstatSync(configPath);
const isSymlink = configLstat.isSymbolicLink();
// For symlinks, check the resolved target permissions. Skip the warning
// only when the target lives in an immutable store (e.g. /nix/store/).
const stat = isSymlink ? fs.statSync(configPath) : configLstat;
const resolvedConfig = isSymlink ? fs.realpathSync(configPath) : configPath;
const isImmutableConfig = resolvedConfig.startsWith("/nix/store/");
if (!isImmutableConfig && (stat.mode & 0o077) !== 0) {
warnings.push(
`- Config file is group/world readable (${displayConfigPath ?? configPath}). Recommend chmod 600.`,
);