Enforce native private state permissions

This commit is contained in:
pulse-triage[bot]
2026-08-30 03:20:36 +01:00
parent f1ddfce083
commit 95a7191ca9
15 changed files with 428 additions and 29 deletions
+4 -3
View File
@@ -23,6 +23,7 @@ import (
"github.com/rcourtman/pulse-go-rewrite/internal/dockeragent"
"github.com/rcourtman/pulse-go-rewrite/internal/hostagent"
"github.com/rcourtman/pulse-go-rewrite/internal/kubernetesagent"
internalSecurityutil "github.com/rcourtman/pulse-go-rewrite/internal/securityutil"
"github.com/rcourtman/pulse-go-rewrite/pkg/securityutil"
"github.com/rs/zerolog"
)
@@ -1902,7 +1903,7 @@ func testPendingUpdate(t *testing.T, stateDir string) *agentupdate.PendingPrivil
func TestPendingPrivilegedUpdateCommitsOnlyAfterReadinessAndAcceptedReport(t *testing.T) {
stateDir := t.TempDir()
if err := os.Chmod(stateDir, 0o700); err != nil {
if err := internalSecurityutil.HardenPrivatePath(stateDir, 0o700); err != nil {
t.Fatal(err)
}
pending := testPendingUpdate(t, stateDir)
@@ -1952,7 +1953,7 @@ func TestPendingPrivilegedUpdateCommitsOnlyAfterReadinessAndAcceptedReport(t *te
func TestPendingPrivilegedUpdateCancellationRollsBackAndClearsHandoff(t *testing.T) {
stateDir := t.TempDir()
if err := os.Chmod(stateDir, 0o700); err != nil {
if err := internalSecurityutil.HardenPrivatePath(stateDir, 0o700); err != nil {
t.Fatal(err)
}
pending := testPendingUpdate(t, stateDir)
@@ -1987,7 +1988,7 @@ func TestPendingPrivilegedUpdateCancellationRollsBackAndClearsHandoff(t *testing
func TestPendingPrivilegedUpdateRollbackFailurePreservesHandoff(t *testing.T) {
stateDir := t.TempDir()
if err := os.Chmod(stateDir, 0o700); err != nil {
if err := internalSecurityutil.HardenPrivatePath(stateDir, 0o700); err != nil {
t.Fatal(err)
}
pending := testPendingUpdate(t, stateDir)
@@ -88,7 +88,11 @@ Runner readiness is exposed through a bounded, secret-free health marker that
is replaced atomically only after its contents reach stable storage. POSIX
targets must sync the containing directory after rename; Windows targets must
use a write-through replacement rather than attempting to flush the read-only
directory handle returned by the standard library.
directory handle returned by the standard library. The marker and helper-update
handoff must also enforce platform-native privacy: owner-only modes on POSIX,
and a protected DACL limited to the owning identity, LocalSystem, and
Administrators on Windows. Unix-looking mode values are not Windows access
control.
Fresh installs carry an explicit local command-authority profile. The closed
values are `monitoring-only`, `command-capable`, and `legacy`. A
`monitoring-only` service may accept remote configuration that keeps commands
@@ -7185,6 +7185,8 @@
"internal/config/watcher.go",
"internal/crypto/crypto.go",
"internal/logging/logging.go",
"internal/securityutil/private_path_other.go",
"internal/securityutil/private_path_windows.go",
"internal/securityutil/secure_storage_dir.go",
"internal/telemetry/service_health.go",
"internal/telemetry/telemetry.go",
@@ -7366,6 +7368,21 @@
"pkg/tlsutil/tlsutil_test.go"
]
},
{
"id": "private-platform-paths",
"label": "platform-native private path hardening proof",
"match_prefixes": [],
"match_files": [
"internal/securityutil/private_path_other.go",
"internal/securityutil/private_path_windows.go"
],
"allow_same_subsystem_tests": false,
"test_prefixes": [],
"exact_files": [
"internal/securityutil/private_path_test.go",
"internal/securityutil/private_path_windows_test.go"
]
},
{
"id": "storage-directory-security",
"label": "storage directory hardening proof",
@@ -1589,6 +1589,12 @@ file-hardened at `0600`. The mount root itself must be validated as the real
directory path rather than a symlink or other filesystem object, but its mode
bits are not a fatal startup gate when Kubernetes or another runtime owns that
mount point.
Agent lifecycle state that explicitly requires a private local path uses the
stricter shared `internal/securityutil/private_path_*` contract. It must reject
symlinks and special files before hardening. POSIX paths must grant no group or
other access; Windows paths must disable DACL inheritance and grant access only
to an approved owner identity, LocalSystem, and Administrators. `os.Chmod`
success and Unix-style mode bits are not evidence of Windows privacy.
That same Security Overview surface must stay action-oriented once those
low-risk states are demoted out of the global banner:
`frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx` and
+1 -1
View File
@@ -41,7 +41,7 @@ func TestPrivilegedUpdateRollbackFailurePreservesPendingHandoff(t *testing.T) {
sum := sha256.Sum256(binary)
digest := hex.EncodeToString(sum[:])
helper := &fakePrivilegedUpdate{root: t.TempDir(), rollbackErr: errors.New("helper unavailable")}
if err := os.Chmod(helper.root, 0o700); err != nil {
if err := securityutil.HardenPrivatePath(helper.root, 0o700); err != nil {
t.Fatal(err)
}
u := New(Config{PrivilegedUpdate: helper, Disabled: true, StateDir: helper.root, CurrentVersion: "1.0.0"})
@@ -0,0 +1,18 @@
//go:build !windows
package agentupdate
import "os"
func replacePendingUpdateFile(from, to string) error {
return os.Rename(from, to)
}
func syncUpdateDirectory(path string) error {
dir, err := os.Open(path)
if err != nil {
return err
}
defer dir.Close()
return dir.Sync()
}
@@ -0,0 +1,34 @@
//go:build windows
package agentupdate
import (
"fmt"
"golang.org/x/sys/windows"
)
func replacePendingUpdateFile(from, to string) error {
fromPath, err := windows.UTF16PtrFromString(from)
if err != nil {
return fmt.Errorf("encode pending update source path: %w", err)
}
toPath, err := windows.UTF16PtrFromString(to)
if err != nil {
return fmt.Errorf("encode pending update destination path: %w", err)
}
if err := windows.MoveFileEx(
fromPath,
toPath,
windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH,
); err != nil {
return fmt.Errorf("replace pending update handoff with write-through: %w", err)
}
return nil
}
func syncUpdateDirectory(string) error {
// MoveFileEx with WRITE_THROUGH is the Windows replacement durability
// barrier. Windows does not expose a portable directory fsync equivalent.
return nil
}
+14 -13
View File
@@ -14,6 +14,7 @@ import (
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/agenthelper"
"github.com/rcourtman/pulse-go-rewrite/internal/securityutil"
)
const privilegedUpdateQuarantineDir = "/var/lib/pulse-agent/update-quarantine"
@@ -164,20 +165,14 @@ func validPrivilegedArtifactID(value string) bool {
return err == nil
}
func syncUpdateDirectory(path string) error {
dir, err := os.Open(path)
if err != nil {
return err
}
defer dir.Close()
return dir.Sync()
}
func PersistPendingPrivilegedUpdate(stateDir, previousVersion string, activation agenthelper.UpdateResult) error {
path, err := pendingPrivilegedUpdatePath(stateDir)
if err != nil {
return err
}
if err := securityutil.HardenPrivatePath(stateDir, 0o700); err != nil {
return fmt.Errorf("harden pending update state directory: %w", err)
}
if err := validatePendingUpdateStateDir(stateDir); err != nil {
return err
}
@@ -198,7 +193,7 @@ func PersistPendingPrivilegedUpdate(stateDir, previousVersion string, activation
}
tempPath := temp.Name()
defer os.Remove(tempPath)
if err := temp.Chmod(0o600); err != nil {
if err := securityutil.HardenPrivatePath(tempPath, 0o600); err != nil {
_ = temp.Close()
return err
}
@@ -213,7 +208,7 @@ func PersistPendingPrivilegedUpdate(stateDir, previousVersion string, activation
if err := temp.Close(); err != nil {
return err
}
if err := os.Rename(tempPath, path); err != nil {
if err := replacePendingUpdateFile(tempPath, path); err != nil {
return err
}
return syncUpdateDirectory(stateDir)
@@ -234,9 +229,12 @@ func LoadPendingPrivilegedUpdate(stateDir string) (*PendingPrivilegedUpdate, err
if err := validatePendingUpdateStateDir(stateDir); err != nil {
return nil, err
}
if before.Mode()&os.ModeSymlink != 0 || !before.Mode().IsRegular() || before.Mode().Perm()&0o077 != 0 {
if before.Mode()&os.ModeSymlink != 0 || !before.Mode().IsRegular() {
return nil, errors.New("pending update handoff is not a private regular file")
}
if err := securityutil.ValidatePrivatePath(path, before); err != nil {
return nil, fmt.Errorf("pending update handoff is not private: %w", err)
}
file, err := os.Open(path)
if err != nil {
return nil, err
@@ -288,9 +286,12 @@ func pendingPrivilegedUpdatePath(stateDir string) (string, error) {
func validatePendingUpdateStateDir(stateDir string) error {
info, err := os.Lstat(stateDir)
if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() || info.Mode().Perm()&0o077 != 0 {
if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
return errors.New("pending update state directory must be a private real directory")
}
if err := securityutil.ValidatePrivatePath(stateDir, info); err != nil {
return fmt.Errorf("pending update state directory must be private: %w", err)
}
return nil
}
@@ -14,6 +14,7 @@ import (
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/agenthelper"
"github.com/rcourtman/pulse-go-rewrite/internal/securityutil"
)
type fakePrivilegedUpdate struct {
@@ -82,7 +83,7 @@ func TestPrivilegedUpdateStagesActivatesAndRollsBackRestartFailure(t *testing.T)
sum := sha256.Sum256(binary)
digest := hex.EncodeToString(sum[:])
helper := &fakePrivilegedUpdate{root: t.TempDir()}
if err := os.Chmod(helper.root, 0o700); err != nil {
if err := securityutil.HardenPrivatePath(helper.root, 0o700); err != nil {
t.Fatal(err)
}
u := New(Config{PrivilegedUpdate: helper, Disabled: true, StateDir: helper.root, CurrentVersion: "1.0.0"})
@@ -111,7 +112,7 @@ func TestPrivilegedUpdateFailsClosedBeforeActivation(t *testing.T) {
runtimeGOOS = goOSLinux
binary := append([]byte{0x7f, 'E', 'L', 'F'}, []byte("update")...)
helper := &fakePrivilegedUpdate{root: t.TempDir()}
if err := os.Chmod(helper.root, 0o700); err != nil {
if err := securityutil.HardenPrivatePath(helper.root, 0o700); err != nil {
t.Fatal(err)
}
u := New(Config{PrivilegedUpdate: helper, Disabled: true, StateDir: helper.root, CurrentVersion: "1.0.0"})
@@ -128,7 +129,7 @@ func TestPrivilegedUpdateFailsClosedBeforeActivation(t *testing.T) {
func TestPendingPrivilegedUpdateHandoffIsDurableAndStrict(t *testing.T) {
stateDir := t.TempDir()
if err := os.Chmod(stateDir, 0o700); err != nil {
if err := securityutil.HardenPrivatePath(stateDir, 0o700); err != nil {
t.Fatal(err)
}
activation := agenthelper.UpdateResult{
@@ -143,8 +144,10 @@ func TestPendingPrivilegedUpdateHandoffIsDurableAndStrict(t *testing.T) {
t.Fatal(err)
}
path := filepath.Join(stateDir, pendingPrivilegedUpdateFile)
if info, err := os.Stat(path); err != nil || info.Mode().Perm() != 0o600 {
t.Fatalf("handoff mode=%v err=%v", info, err)
if info, err := os.Lstat(path); err != nil {
t.Fatalf("inspect handoff: %v", err)
} else if err := securityutil.ValidatePrivatePath(path, info); err != nil {
t.Fatalf("handoff is not private: %v", err)
}
loaded, err := LoadPendingPrivilegedUpdate(stateDir)
if err != nil {
@@ -163,7 +166,7 @@ func TestPendingPrivilegedUpdateHandoffIsDurableAndStrict(t *testing.T) {
func TestPendingPrivilegedUpdateHandoffRejectsUnsafeState(t *testing.T) {
stateDir := t.TempDir()
if err := os.Chmod(stateDir, 0o700); err != nil {
if err := securityutil.HardenPrivatePath(stateDir, 0o700); err != nil {
t.Fatal(err)
}
path := filepath.Join(stateDir, pendingPrivilegedUpdateFile)
+16 -2
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/agentexec"
"github.com/rcourtman/pulse-go-rewrite/internal/securityutil"
"github.com/rs/zerolog"
)
@@ -106,15 +107,18 @@ func (c *CommandClient) writeActionRunnerHealth() error {
if err := os.MkdirAll(dir, 0700); err != nil {
return err
}
if err := securityutil.HardenPrivatePath(dir, 0o700); err != nil {
return fmt.Errorf("harden action-runner health directory: %w", err)
}
temp, err := os.CreateTemp(dir, ".health-*.tmp")
if err != nil {
return err
}
tempPath := temp.Name()
defer os.Remove(tempPath)
if err := temp.Chmod(0600); err != nil {
if err := securityutil.HardenPrivatePath(tempPath, 0o600); err != nil {
temp.Close()
return err
return fmt.Errorf("harden action-runner health marker: %w", err)
}
if _, err := temp.Write(encoded); err != nil {
temp.Close()
@@ -130,5 +134,15 @@ func (c *CommandClient) writeActionRunnerHealth() error {
if err := replaceActionRunnerHealthFile(tempPath, c.healthPath); err != nil {
return err
}
info, err := os.Lstat(c.healthPath)
if err != nil {
return fmt.Errorf("inspect replaced action-runner health marker: %w", err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return fmt.Errorf("action-runner health marker is not a real regular file")
}
if err := securityutil.ValidatePrivatePath(c.healthPath, info); err != nil {
return fmt.Errorf("validate private action-runner health marker: %w", err)
}
return syncActionRunnerHealthDirectory(dir)
}
@@ -13,6 +13,7 @@ import (
"github.com/gorilla/websocket"
"github.com/rcourtman/pulse-go-rewrite/internal/agentexec"
"github.com/rcourtman/pulse-go-rewrite/internal/securityutil"
"github.com/rs/zerolog"
)
@@ -120,9 +121,12 @@ func TestActionRunnerHealthIsAtomicBoundedAndSecretFree(t *testing.T) {
if !health.Registered || health.RuntimeRole != agentexec.RuntimeRoleActionRunner || health.HostID != "agent-1" || health.Server != "https://pulse.example" || health.RegisteredAt.IsZero() {
t.Fatalf("health = %+v", health)
}
info, err := os.Stat(healthPath)
if err != nil || info.Mode().Perm() != 0600 {
t.Fatalf("health mode = %v, %v", info.Mode().Perm(), err)
info, err := os.Lstat(healthPath)
if err != nil {
t.Fatalf("inspect health marker: %v", err)
}
if err := securityutil.ValidatePrivatePath(healthPath, info); err != nil {
t.Fatalf("health marker is not private: %v", err)
}
matches, err := filepath.Glob(filepath.Join(dir, ".health-*.tmp"))
if err != nil || len(matches) != 0 {
@@ -0,0 +1,36 @@
//go:build !windows
package securityutil
import (
"fmt"
"os"
)
// HardenPrivatePath applies an owner-only mode to a file or directory.
func HardenPrivatePath(path string, mode os.FileMode) error {
before, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("inspect private path before hardening: %w", err)
}
if before.Mode()&os.ModeSymlink != 0 || (!before.IsDir() && !before.Mode().IsRegular()) {
return fmt.Errorf("private path must be a real file or directory")
}
if err := os.Chmod(path, mode); err != nil {
return fmt.Errorf("harden private path permissions: %w", err)
}
info, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("inspect hardened private path: %w", err)
}
return ValidatePrivatePath(path, info)
}
// ValidatePrivatePath rejects paths accessible through group or other mode
// bits. Callers separately validate the expected filesystem object type.
func ValidatePrivatePath(_ string, info os.FileInfo) error {
if info.Mode().Perm()&0o077 != 0 {
return fmt.Errorf("private path grants group or other access")
}
return nil
}
@@ -0,0 +1,36 @@
package securityutil
import (
"os"
"path/filepath"
"testing"
)
func TestHardenPrivatePathProtectsDirectoryAndFile(t *testing.T) {
dir := t.TempDir()
if err := HardenPrivatePath(dir, 0o700); err != nil {
t.Fatal(err)
}
dirInfo, err := os.Lstat(dir)
if err != nil {
t.Fatal(err)
}
if err := ValidatePrivatePath(dir, dirInfo); err != nil {
t.Fatalf("private directory validation failed: %v", err)
}
path := filepath.Join(dir, "state.json")
if err := os.WriteFile(path, []byte("state"), 0o666); err != nil {
t.Fatal(err)
}
if err := HardenPrivatePath(path, 0o600); err != nil {
t.Fatal(err)
}
fileInfo, err := os.Lstat(path)
if err != nil {
t.Fatal(err)
}
if err := ValidatePrivatePath(path, fileInfo); err != nil {
t.Fatalf("private file validation failed: %v", err)
}
}
@@ -0,0 +1,183 @@
//go:build windows
package securityutil
import (
"fmt"
"os"
"strings"
"unsafe"
"golang.org/x/sys/windows"
)
const (
localSystemSID = "S-1-5-18"
administratorsSID = "S-1-5-32-544"
)
// HardenPrivatePath protects a file or directory DACL from inheritance and
// grants access only to its owner identity, LocalSystem, and Administrators.
// Windows ignores Unix owner/group/other mode bits, so os.Chmod cannot provide
// this boundary.
func HardenPrivatePath(path string, _ os.FileMode) error {
info, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("inspect private Windows path: %w", err)
}
if info.Mode()&os.ModeSymlink != 0 || (!info.IsDir() && !info.Mode().IsRegular()) {
return fmt.Errorf("private Windows path must be a real file or directory")
}
allowed, currentSID, err := privateWindowsSIDs()
if err != nil {
return err
}
if err := validatePrivateWindowsOwner(path, allowed); err != nil {
return err
}
inheritance := ""
if info.IsDir() {
inheritance = "OICI"
}
entries := make([]string, 0, len(allowed))
seen := make(map[string]struct{}, len(allowed))
for _, sid := range append([]*windows.SID{currentSID}, allowed...) {
value := sid.String()
if _, exists := seen[value]; exists {
continue
}
seen[value] = struct{}{}
entries = append(entries, fmt.Sprintf("(A;%s;GA;;;%s)", inheritance, value))
}
descriptor, err := windows.SecurityDescriptorFromString("D:P" + strings.Join(entries, ""))
if err != nil {
return fmt.Errorf("build private Windows DACL: %w", err)
}
dacl, _, err := descriptor.DACL()
if err != nil {
return fmt.Errorf("read private Windows DACL: %w", err)
}
if err := windows.SetNamedSecurityInfo(
path,
windows.SE_FILE_OBJECT,
windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
nil,
nil,
dacl,
nil,
); err != nil {
return fmt.Errorf("apply private Windows DACL: %w", err)
}
return ValidatePrivatePath(path, info)
}
// ValidatePrivatePath verifies that inheritance is disabled and every access
// entry belongs to the current identity, LocalSystem, or Administrators.
func ValidatePrivatePath(path string, _ os.FileInfo) error {
allowed, currentSID, err := privateWindowsSIDs()
if err != nil {
return err
}
if err := validatePrivateWindowsOwner(path, allowed); err != nil {
return err
}
descriptor, err := windows.GetNamedSecurityInfo(
path,
windows.SE_FILE_OBJECT,
windows.DACL_SECURITY_INFORMATION,
)
if err != nil {
return fmt.Errorf("read private Windows security descriptor: %w", err)
}
if descriptor == nil {
return fmt.Errorf("private Windows path has no security descriptor")
}
control, _, err := descriptor.Control()
if err != nil {
return fmt.Errorf("read private Windows DACL control: %w", err)
}
if control&windows.SE_DACL_PROTECTED == 0 {
return fmt.Errorf("private Windows DACL still inherits access")
}
dacl, _, err := descriptor.DACL()
if err != nil {
return fmt.Errorf("read private Windows DACL: %w", err)
}
if dacl == nil || dacl.AceCount == 0 {
return fmt.Errorf("private Windows path has no access entries")
}
currentAllowed := false
for index := uint32(0); index < uint32(dacl.AceCount); index++ {
var entry *windows.ACCESS_ALLOWED_ACE
if err := windows.GetAce(dacl, index, &entry); err != nil {
return fmt.Errorf("read private Windows DACL entry: %w", err)
}
if entry.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE {
return fmt.Errorf("private Windows DACL contains a non-allow entry")
}
sid := (*windows.SID)(unsafe.Pointer(&entry.SidStart))
if !sid.IsValid() || !windowsSIDAllowed(sid, allowed) {
return fmt.Errorf("private Windows DACL grants an unapproved identity")
}
if sid.Equals(currentSID) {
currentAllowed = true
}
}
if !currentAllowed {
return fmt.Errorf("private Windows DACL omits the current identity")
}
return nil
}
func privateWindowsSIDs() ([]*windows.SID, *windows.SID, error) {
user, err := windows.GetCurrentProcessToken().GetTokenUser()
if err != nil {
return nil, nil, fmt.Errorf("resolve current Windows identity: %w", err)
}
currentSID, err := user.User.Sid.Copy()
if err != nil {
return nil, nil, fmt.Errorf("copy current Windows identity: %w", err)
}
systemSID, err := windows.StringToSid(localSystemSID)
if err != nil {
return nil, nil, fmt.Errorf("resolve LocalSystem SID: %w", err)
}
adminSID, err := windows.StringToSid(administratorsSID)
if err != nil {
return nil, nil, fmt.Errorf("resolve Administrators SID: %w", err)
}
return []*windows.SID{currentSID, systemSID, adminSID}, currentSID, nil
}
func validatePrivateWindowsOwner(path string, allowed []*windows.SID) error {
descriptor, err := windows.GetNamedSecurityInfo(
path,
windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION,
)
if err != nil {
return fmt.Errorf("read private Windows path owner: %w", err)
}
if descriptor == nil {
return fmt.Errorf("private Windows path has no owner descriptor")
}
owner, _, err := descriptor.Owner()
if err != nil {
return fmt.Errorf("read private Windows path owner: %w", err)
}
if owner == nil || !windowsSIDAllowed(owner, allowed) {
return fmt.Errorf("private Windows path has an unapproved owner")
}
return nil
}
func windowsSIDAllowed(candidate *windows.SID, allowed []*windows.SID) bool {
for _, sid := range allowed {
if candidate.Equals(sid) {
return true
}
}
return false
}
@@ -0,0 +1,42 @@
//go:build windows
package securityutil
import (
"os"
"path/filepath"
"testing"
"golang.org/x/sys/windows"
)
func TestValidatePrivatePathRejectsInheritedOrBroadWindowsDACL(t *testing.T) {
path := filepath.Join(t.TempDir(), "broad.json")
if err := os.WriteFile(path, []byte("state"), 0o600); err != nil {
t.Fatal(err)
}
descriptor, err := windows.SecurityDescriptorFromString("D:(A;;GA;;;WD)")
if err != nil {
t.Fatal(err)
}
dacl, _, err := descriptor.DACL()
if err != nil {
t.Fatal(err)
}
if err := windows.SetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION, nil, nil, dacl, nil); err != nil {
t.Fatal(err)
}
info, err := os.Lstat(path)
if err != nil {
t.Fatal(err)
}
if err := ValidatePrivatePath(path, info); err == nil {
t.Fatal("broad Windows DACL was accepted")
}
if err := HardenPrivatePath(path, 0o600); err != nil {
t.Fatal(err)
}
if err := ValidatePrivatePath(path, info); err != nil {
t.Fatalf("hardened Windows DACL was rejected: %v", err)
}
}