From 261dad4ab0bac2e5c1737b47521d05755285fbda Mon Sep 17 00:00:00 2001 From: codeyevsky Date: Tue, 27 Oct 2026 21:14:18 +0300 Subject: [PATCH] config: preserve execute bit when self-protection locks a file lockFile's Linux fallback chmod'd every protected target to 0400 whenever chattr +i failed (no CAP_LINUX_IMMUTABLE, e.g. containers and rootless installs), which is the common case. protectedTargets() includes the guard binary, so every such install silently produced a non-executable guard. The launcher found the file but could not exec it, and the guard fail-closed on every tool call. Use 0500 instead, matching the windows branch, which already grants RX, and protectpaths.go's LockPath, which uses 0500 on directories for the same reason. Write access is what self-protection needs to deny, not execute. Signed-off-by: codeyevsky --- packages/core/config/protect.go | 7 ++++++- packages/core/config/protect_test.go | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/core/config/protect.go b/packages/core/config/protect.go index f3c8b46..0e946b4 100644 --- a/packages/core/config/protect.go +++ b/packages/core/config/protect.go @@ -22,7 +22,12 @@ func lockFile(file string) { run("chflags", "uchg", file) default: run("chattr", "+i", file) - _ = os.Chmod(file, 0o400) + // 0o500, not 0o400: protectedTargets() includes the guard binary, and + // chattr commonly fails without CAP_LINUX_IMMUTABLE (containers, + // rootless), so this chmod is the fallback that actually runs. Keeping + // the execute bit matches the windows ACL above (grants RX) and the + // directory mode in protectpaths.go's LockPath. + _ = os.Chmod(file, 0o500) } } diff --git a/packages/core/config/protect_test.go b/packages/core/config/protect_test.go index 4a6c621..8b756f6 100644 --- a/packages/core/config/protect_test.go +++ b/packages/core/config/protect_test.go @@ -22,8 +22,8 @@ func TestLockFileThenUnlockFileRoundTrip(t *testing.T) { if err != nil { t.Fatal(err) } - if info.Mode().Perm() != 0o400 { - t.Errorf("after lockFile, perm = %v, want 0400", info.Mode().Perm()) + if info.Mode().Perm() != 0o500 { + t.Errorf("after lockFile, perm = %v, want 0500", info.Mode().Perm()) } unlockFile(file) @@ -90,8 +90,8 @@ func TestWriteProtectedFileOverLockedFileUnlocksRewritesAndRelocks(t *testing.T) if err != nil { t.Fatal(err) } - if info.Mode().Perm() != 0o400 { - t.Errorf("perm after relock = %v, want 0400", info.Mode().Perm()) + if info.Mode().Perm() != 0o500 { + t.Errorf("perm after relock = %v, want 0500", info.Mode().Perm()) } }