Skip to content

Commit 10f2422

Browse files
fix(windows): grant Users modify ACL on ProgramData log dir; correct comment
The /ru INTERACTIVE scheduled task fires under whatever user is logged on at trigger time, typically a non-admin developer. The default ACLs on C:\ProgramData\StepSecurity (inherited from C:\ProgramData) only grant non-admin users Read & Execute on existing files, so cmd.exe's `>>` redirect to agent.log would fail with Access Denied — and a failed redirect aborts the whole task action, so the periodic scan never runs. Grant BUILTIN\Users (SID 545) Modify rights on the log dir after creating it, propagated to files and subfolders, so any logged-in user can append. Also corrects the /ru INTERACTIVE comment: SID S-1-5-4 is in the NT AUTHORITY domain, not BUILTIN (BUILTIN\* SIDs are S-1-5-32-*).
1 parent bb6266e commit 10f2422

1 file changed

Lines changed: 20 additions & 5 deletions

File tree

internal/schtasks/schtasks.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ func Install(exec executor.Executor, log *progress.Logger) error {
4242
return fmt.Errorf("creating log directory: %w", err)
4343
}
4444

45+
// For admin installs the log dir lives at C:\ProgramData\StepSecurity, which
46+
// inherits ACLs from C:\ProgramData and only grants non-admin users
47+
// Read & Execute on the files inside. The /ru INTERACTIVE task fires under
48+
// whatever user is logged on — typically a non-admin developer — and
49+
// cmd.exe's `>>` redirect to agent.log would fail with Access Denied, which
50+
// aborts the whole task action. Grant BUILTIN\Users (SID 545) Modify rights
51+
// on the log dir, propagated to files and subfolders, so any logged-in
52+
// user can append to the log files.
53+
if exec.IsRoot() {
54+
_, _, _, icaclsErr := exec.Run(ctx, "icacls", logDir, "/grant", "*S-1-5-32-545:(OI)(CI)M", "/Q")
55+
if icaclsErr != nil {
56+
log.Warn("could not adjust log dir ACLs (%v) — non-admin users may not be able to write to %s", icaclsErr, logDir)
57+
}
58+
}
59+
4560
args := buildCreateArgs(binaryPath, logDir, hours, exec.IsRoot())
4661
log.Debug("schtasks create: binary=%q log_dir=%q hours=%d is_admin=%v", binaryPath, logDir, hours, exec.IsRoot())
4762

@@ -101,11 +116,11 @@ func buildCreateArgs(binaryPath, logDir string, hours int, isAdmin bool) []strin
101116
args := []string{"/create", "/tn", taskName, "/tr", taskCmd,
102117
"/sc", "HOURLY", "/mo", strconv.Itoa(hours), "/f"}
103118
if isAdmin {
104-
// /ru INTERACTIVE binds the task to the BUILTIN\INTERACTIVE group
105-
// (SID S-1-5-4) so it fires under the security context of whoever
106-
// is interactively logged on at trigger time — picking up their
107-
// HKCU, %USERPROFILE%, and PATH. /ru SYSTEM would run as
108-
// NT AUTHORITY\SYSTEM, which can't see any of the user-scoped
119+
// /ru INTERACTIVE binds the task to the NT AUTHORITY\INTERACTIVE
120+
// well-known group (SID S-1-5-4) so it fires under the security
121+
// context of whoever is interactively logged on at trigger time —
122+
// picking up their HKCU, %USERPROFILE%, and PATH. /ru SYSTEM would
123+
// run as NT AUTHORITY\SYSTEM, which can't see any of the user-scoped
109124
// data the scanner depends on.
110125
args = append(args, "/ru", "INTERACTIVE")
111126
}

0 commit comments

Comments
 (0)