summaryrefslogtreecommitdiff
path: root/src/Agent.Windows
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.Windows')
-rw-r--r--src/Agent.Windows/Security/TaskAclManager.cs23
-rw-r--r--src/Agent.Windows/Tools/WindowsToolBackend.cs32
2 files changed, 11 insertions, 44 deletions
diff --git a/src/Agent.Windows/Security/TaskAclManager.cs b/src/Agent.Windows/Security/TaskAclManager.cs
index f01e859..2b091a6 100644
--- a/src/Agent.Windows/Security/TaskAclManager.cs
+++ b/src/Agent.Windows/Security/TaskAclManager.cs
@@ -26,7 +26,13 @@ public sealed class TaskAclManager(SecurityIdentifier appContainerSid, ISecurity
security.AddAccessRule(rule);
WriteSecurity(canonical, security);
if (journalEntry is not null) journal!.MarkApplied(journalEntry.Value);
- audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.apply", "applied", new Dictionary<string, string> { ["path"] = canonical, ["sid"] = appContainerSid.Value, ["rights"] = rights.ToString() }));
+ audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.apply", "applied", new Dictionary<string, string>
+ {
+ ["path"] = canonical,
+ ["sid"] = appContainerSid.Value,
+ ["rights"] = rights.ToString(),
+ ["scope"] = directory ? "root_inheritance" : "file"
+ }));
return new(canonical, original, journalEntry);
}
@@ -39,21 +45,6 @@ public sealed class TaskAclManager(SecurityIdentifier appContainerSid, ISecurity
audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.restore", "restored", new Dictionary<string, string> { ["path"] = snapshot.Path }));
}
- public void NormalizeNewEntry(Guid taskId, string path)
- {
- string canonical = CanonicalPath.Resolve(path);
- FileSystemSecurity security = ReadSecurity(canonical);
- security.SetAccessRuleProtection(isProtected: true, preserveInheritance: false);
- foreach (FileSystemAccessRule rule in security.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
- .OfType<FileSystemAccessRule>()
- .Where(rule => rule.IdentityReference == appContainerSid)
- .ToArray())
- security.RemoveAccessRuleSpecific(rule);
- security.SetAccessRuleProtection(isProtected: false, preserveInheritance: false);
- WriteSecurity(canonical, security);
- audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.normalize", "normalized", new Dictionary<string, string> { ["path"] = canonical }));
- }
-
private static FileSystemSecurity ReadSecurity(string path) => Directory.Exists(path)
? FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(path), AccessControlSections.Access)
: FileSystemAclExtensions.GetAccessControl(new FileInfo(path), AccessControlSections.Access);
diff --git a/src/Agent.Windows/Tools/WindowsToolBackend.cs b/src/Agent.Windows/Tools/WindowsToolBackend.cs
index a21544b..c0d1561 100644
--- a/src/Agent.Windows/Tools/WindowsToolBackend.cs
+++ b/src/Agent.Windows/Tools/WindowsToolBackend.cs
@@ -49,8 +49,10 @@ public sealed class WindowsToolBackend(WindowsSandboxSettings settings, ISecurit
// Do not rewrite a file ACL when execute is the only requested capability; CreateProcess remains
// constrained by AppContainer access plus canonical path/SHA-256 approval.
if (File.Exists(path) && capabilities == FileCapability.Execute) continue;
- context.Roots.Add(path);
- foreach (string target in ExistingAclTargets(path)) context.Acls.Add(acl.Grant(taskId, target, capabilities));
+ // A single inheritable ACE on an approved directory covers existing and newly-created
+ // descendants. Snapshotting and rewriting every child ACL is both redundant and
+ // prohibitively expensive for runtime trees such as Python installations.
+ context.Acls.Add(acl.Grant(taskId, path, capabilities));
}
IReadOnlyList<NetworkEndpoint> allowances = settings.NetworkMode == "allow"
? await NetworkAllowanceSnapshot.ResolveAsync(settings.NetworkAllowances, cancellationToken).ConfigureAwait(false)
@@ -193,11 +195,6 @@ public sealed class WindowsToolBackend(WindowsSandboxSettings settings, ISecurit
List<Exception> failures = [];
try { context.Network?.Dispose(); } catch (Exception ex) { failures.Add(ex); }
foreach (AclSnapshot snapshot in context.Acls.AsEnumerable().Reverse()) try { context.Acl.Restore(taskId, snapshot); } catch (Exception ex) { failures.Add(ex); }
- var existingSnapshots = context.Acls.Select(static snapshot => snapshot.Path).ToHashSet(StringComparer.OrdinalIgnoreCase);
- foreach (string entry in context.Roots.SelectMany(ExistingAclTargets).Distinct(StringComparer.OrdinalIgnoreCase)
- .Where(entry => !existingSnapshots.Contains(entry))
- .OrderBy(static entry => entry.Count(character => character == Path.DirectorySeparatorChar)))
- try { context.Acl.NormalizeNewEntry(taskId, entry); } catch (Exception ex) { failures.Add(ex); }
if (failures.Count == 0) context.Journal.CompleteAndDelete();
await Task.CompletedTask;
if (failures.Count > 0) throw new AggregateException("Task resource cleanup failed.", failures);
@@ -215,26 +212,6 @@ public sealed class WindowsToolBackend(WindowsSandboxSettings settings, ISecurit
var regex = new Regex(query, RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1));
return files.Where(path => IsTextMatch(path, regex.IsMatch));
}
- private static IEnumerable<string> ExistingAclTargets(string path)
- {
- if (File.Exists(path))
- {
- yield return path;
- yield break;
- }
- if (!Directory.Exists(path)) yield break;
- var options = new EnumerationOptions
- {
- RecurseSubdirectories = true,
- IgnoreInaccessible = false,
- ReturnSpecialDirectories = false,
- AttributesToSkip = FileAttributes.ReparsePoint
- };
- foreach (string entry in Directory.EnumerateFileSystemEntries(path, "*", options)
- .OrderByDescending(static entry => entry.Count(character => character == Path.DirectorySeparatorChar)))
- yield return entry;
- yield return path;
- }
private static string ApplyPatch(string path, string patch)
{
string current = File.Exists(path) ? File.ReadAllText(path) : string.Empty;
@@ -251,7 +228,6 @@ public sealed class WindowsToolBackend(WindowsSandboxSettings settings, ISecurit
public TaskResourceJournal Journal { get; } = journal;
public TaskAclManager Acl { get; } = acl;
public List<AclSnapshot> Acls { get; } = [];
- public HashSet<string> Roots { get; } = new(StringComparer.OrdinalIgnoreCase);
public TaskNetworkPolicy? Network { get; set; }
}
}