summaryrefslogtreecommitdiff
path: root/tests/Agent.Windows.Tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Agent.Windows.Tests')
-rw-r--r--tests/Agent.Windows.Tests/SecurityPolicyTests.cs135
1 files changed, 135 insertions, 0 deletions
diff --git a/tests/Agent.Windows.Tests/SecurityPolicyTests.cs b/tests/Agent.Windows.Tests/SecurityPolicyTests.cs
index 8d920b2..a7197b8 100644
--- a/tests/Agent.Windows.Tests/SecurityPolicyTests.cs
+++ b/tests/Agent.Windows.Tests/SecurityPolicyTests.cs
@@ -569,6 +569,141 @@ public sealed class SecurityPolicyTests
Assert.Single(audit.Events, item => item.Event == "acl.restore");
}
+ [Fact]
+ public async Task Dir_mkdir_creates_directory_within_approved_root_and_cleans_up()
+ {
+ using TestDirectory directory = new();
+ string workspace = Path.Combine(directory.Path, "workspace");
+ Directory.CreateDirectory(workspace);
+ string target = Path.Combine(workspace, "subdir");
+ byte[] aclBefore = FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(workspace), AccessControlSections.Access).GetSecurityDescriptorBinaryForm();
+ var audit = new MemorySecurityAuditSink();
+ var settings = new WindowsSandboxSettings(
+ [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], false,
+ new SandboxJobLimits(), "deny", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"),
+ 16 * 1024 * 1024, 1024 * 1024, 1000, 10000);
+ var backend = new WindowsToolBackend(settings, audit);
+ Guid taskId = Guid.NewGuid();
+ await backend.BeginTaskAsync(taskId, TestContext.Current.CancellationToken);
+ using JsonDocument mkdir = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(target)}},"operation":"mkdir"}""");
+
+ ToolExecutionResult result = await backend.ExecuteAsync(taskId, "dir", mkdir.RootElement, TestContext.Current.CancellationToken);
+
+ Assert.True(result.Success);
+ Assert.True(result.ExternalEffect);
+ Assert.True(Directory.Exists(target));
+
+ await backend.EndTaskAsync(taskId, true, TestContext.Current.CancellationToken);
+ Assert.True(Directory.Exists(target)); // mkdir result persists after cleanup
+ byte[] aclAfter = FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(workspace), AccessControlSections.Access).GetSecurityDescriptorBinaryForm();
+ Assert.Equal(Convert.ToHexString(aclBefore), Convert.ToHexString(aclAfter));
+ Assert.Empty(Directory.EnumerateFiles(Path.Combine(directory.Path, "journals")));
+ }
+
+ [Fact]
+ public async Task Dir_rmdir_removes_empty_directory_within_approved_root()
+ {
+ using TestDirectory directory = new();
+ string workspace = Path.Combine(directory.Path, "workspace");
+ Directory.CreateDirectory(workspace);
+ string target = Path.Combine(workspace, "subdir");
+ Directory.CreateDirectory(target);
+ var audit = new MemorySecurityAuditSink();
+ var settings = new WindowsSandboxSettings(
+ [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], false,
+ new SandboxJobLimits(), "deny", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"),
+ 16 * 1024 * 1024, 1024 * 1024, 1000, 10000);
+ var backend = new WindowsToolBackend(settings, audit);
+ Guid taskId = Guid.NewGuid();
+ await backend.BeginTaskAsync(taskId, TestContext.Current.CancellationToken);
+ using JsonDocument rmdir = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(target)}},"operation":"rmdir"}""");
+
+ ToolExecutionResult result = await backend.ExecuteAsync(taskId, "dir", rmdir.RootElement, TestContext.Current.CancellationToken);
+
+ Assert.True(result.Success);
+ Assert.True(result.ExternalEffect);
+ Assert.False(Directory.Exists(target));
+ await backend.EndTaskAsync(taskId, true, TestContext.Current.CancellationToken);
+ Assert.Empty(Directory.EnumerateFiles(Path.Combine(directory.Path, "journals")));
+ }
+
+ [Fact]
+ public async Task Dir_rmdir_rejects_non_empty_directory()
+ {
+ using TestDirectory directory = new();
+ string workspace = Path.Combine(directory.Path, "workspace");
+ Directory.CreateDirectory(workspace);
+ string target = Path.Combine(workspace, "subdir");
+ Directory.CreateDirectory(target);
+ File.WriteAllText(Path.Combine(target, "child.txt"), "content");
+ var audit = new MemorySecurityAuditSink();
+ var settings = new WindowsSandboxSettings(
+ [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], false,
+ new SandboxJobLimits(), "deny", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"),
+ 16 * 1024 * 1024, 1024 * 1024, 1000, 10000);
+ var backend = new WindowsToolBackend(settings, audit);
+ Guid taskId = Guid.NewGuid();
+ await backend.BeginTaskAsync(taskId, TestContext.Current.CancellationToken);
+ using JsonDocument rmdir = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(target)}},"operation":"rmdir"}""");
+
+ await Assert.ThrowsAsync<ToolValidationException>(async () => await backend.ExecuteAsync(taskId, "dir", rmdir.RootElement, TestContext.Current.CancellationToken));
+
+ Assert.True(Directory.Exists(target)); // directory still exists
+ await backend.EndTaskAsync(taskId, true, TestContext.Current.CancellationToken);
+ }
+
+ [Fact]
+ public async Task Dir_list_returns_entries_in_approved_directory()
+ {
+ using TestDirectory directory = new();
+ string workspace = Path.Combine(directory.Path, "workspace");
+ Directory.CreateDirectory(workspace);
+ Directory.CreateDirectory(Path.Combine(workspace, "child-dir"));
+ File.WriteAllText(Path.Combine(workspace, "child.txt"), "content");
+ var audit = new MemorySecurityAuditSink();
+ var settings = new WindowsSandboxSettings(
+ [new FilePolicyEntry(workspace, FileCapability.Read)], [], false,
+ new SandboxJobLimits(), "deny", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"),
+ 16 * 1024 * 1024, 1024 * 1024, 1000, 10000);
+ var backend = new WindowsToolBackend(settings, audit);
+ Guid taskId = Guid.NewGuid();
+ await backend.BeginTaskAsync(taskId, TestContext.Current.CancellationToken);
+ using JsonDocument list = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(workspace)}},"operation":"list"}""");
+
+ ToolExecutionResult result = await backend.ExecuteAsync(taskId, "dir", list.RootElement, TestContext.Current.CancellationToken);
+
+ Assert.True(result.Success);
+ Assert.False(result.ExternalEffect);
+ Assert.Contains("child-dir", result.Output);
+ Assert.Contains("child.txt", result.Output);
+ await backend.EndTaskAsync(taskId, true, TestContext.Current.CancellationToken);
+ }
+
+ [Fact]
+ public async Task Dir_mkdir_outside_approved_root_is_denied()
+ {
+ using TestDirectory directory = new();
+ using TestDirectory outside = new();
+ string workspace = Path.Combine(directory.Path, "workspace");
+ Directory.CreateDirectory(workspace);
+ var audit = new MemorySecurityAuditSink();
+ var settings = new WindowsSandboxSettings(
+ [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write)], [], false,
+ new SandboxJobLimits(), "deny", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"),
+ 16 * 1024 * 1024, 1024 * 1024, 1000, 10000);
+ var backend = new WindowsToolBackend(settings, audit);
+ Guid taskId = Guid.NewGuid();
+ await backend.BeginTaskAsync(taskId, TestContext.Current.CancellationToken);
+ string badTarget = Path.Combine(outside.Path, "escaped");
+ using JsonDocument mkdir = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(badTarget)}},"operation":"mkdir"}""");
+
+ ToolExecutionResult result = await backend.ExecuteAsync(taskId, "dir", mkdir.RootElement, TestContext.Current.CancellationToken);
+
+ Assert.False(result.Success);
+ Assert.False(result.RetryAllowed);
+ await backend.EndTaskAsync(taskId, true, TestContext.Current.CancellationToken);
+ }
+
[Fact(Timeout = 60_000)]
public async Task Wfp_enforces_actual_appcontainer_loopback_deny_allow_and_port_scope()
{