summaryrefslogtreecommitdiff
path: root/src/Agent.App
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-13 22:48:24 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-13 22:48:24 +0900
commit9337ac3e5edd42bb17f5e2ade6c2de02c7ef576d (patch)
tree9a48fefc88cc046f4ce1e2bc34fa0a47351e4f83 /src/Agent.App
parent36ae1a4cba791ec633a81b38b1d8345af8487276 (diff)
feat!: isolate privileged sandbox network operations per task
Run agent-mcp with an explicit asInvoker manifest and move WFP and loopback mutations into a fixed requireAdministrator helper under the release admin directory. Add the Agent.Admin client and host projects with task-scoped runas launch, a logon-SID and Administrators-only local named pipe, remote-client rejection, mutual PID validation, strict versioned JSON framing, fixed network-only operations, and bounded messages. Delay ACL and network Task Resource application until the first process_run. Verify executable identity, managed entry points, DNS snapshots, and normal-token WRITE_DAC access before elevation; reuse the helper only within the Task and keep elevation failures terminal without policy replanning. Keep LPAC creation, executable approval, Job confinement, and ACL leases in the normal process. Make startup platform checks read-only, create the fixed Application Profile during apply, preserve dynamic WFP lifetime in the helper, and route persistent loopback recovery through the same elevated boundary. Update recovery journaling, localized elevation and IPC errors, audit events, release publishing, manifests, README guidance, Specifications, Design, implementation records, and requirement traceability for the on-demand elevation model. Add administrative-boundary, file-only no-elevation, elevation-terminal, lazy-resource, crash-recovery, manifest, IPC schema, and operational verification coverage. Verified Release build with zero warnings, Core 105/105, Planner 10/10, administrative boundary 4/4, elevated Windows suite 79/79, and published stdio/HTTP MCP E2E.
Diffstat (limited to 'src/Agent.App')
-rw-r--r--src/Agent.App/Agent.App.csproj2
-rw-r--r--src/Agent.App/Program.cs27
-rw-r--r--src/Agent.App/app.manifest10
3 files changed, 31 insertions, 8 deletions
diff --git a/src/Agent.App/Agent.App.csproj b/src/Agent.App/Agent.App.csproj
index e3447c5..c63f3c0 100644
--- a/src/Agent.App/Agent.App.csproj
+++ b/src/Agent.App/Agent.App.csproj
@@ -5,10 +5,12 @@
<AssemblyName>agent-mcp</AssemblyName>
<RootNamespace>Agent.App</RootNamespace>
<Version>1.0.0</Version>
+ <ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Agent.Mcp\Agent.Mcp.csproj" />
<ProjectReference Include="..\Agent.Windows\Agent.Windows.csproj" />
+ <ProjectReference Include="..\Agent.Admin\Agent.Admin.Client\Agent.Admin.Client.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Agent.Core.Tests" />
diff --git a/src/Agent.App/Program.cs b/src/Agent.App/Program.cs
index 7372a13..3de5b50 100644
--- a/src/Agent.App/Program.cs
+++ b/src/Agent.App/Program.cs
@@ -12,6 +12,7 @@ using Agent.Core.Tools;
using Agent.Windows.Recovery;
using Agent.Windows.Security;
using Agent.Windows.Tools;
+using Agent.Admin.Client;
namespace Agent.App;
@@ -46,24 +47,27 @@ internal static class Program
UserOnlyDirectory.Ensure(logDirectory);
using var log = new RollingFileLog(logDirectory, configuration.Logging.MaxFileBytes, configuration.Logging.RetainedFiles, configuration.Logging.Level);
log.Information(locale.Get("app.starting"));
- WindowsPlatformCheckResult servePlatform = WindowsPlatformCheck.Run();
+ string helperPath = Path.Combine(AppContext.BaseDirectory, "admin", "agent-mcp-admin.exe");
+ WindowsPlatformCheckResult servePlatform = WindowsPlatformCheck.Run(helperPath);
if (!servePlatform.Success)
{
string failure = locale.Format("app.preflight_failed", new Dictionary<string, object?>
{
["operation"] = servePlatform.ErrorOperation ?? "privilege/BFE/WFP probe",
["error_code"] = servePlatform.ErrorCode,
- ["administrator"] = servePlatform.IsAdministrator.ToString().ToLowerInvariant(),
+ ["platform"] = servePlatform.PlatformSupported.ToString().ToLowerInvariant(),
+ ["profile"] = servePlatform.ApplicationProfileAvailable.ToString().ToLowerInvariant(),
["bfe"] = servePlatform.BfeEngineOpen.ToString().ToLowerInvariant(),
- ["transaction"] = servePlatform.WfpWriteTransaction.ToString().ToLowerInvariant(),
- ["filters"] = servePlatform.WfpFilterAddDelete.ToString().ToLowerInvariant()
+ ["helper"] = servePlatform.HelperAvailable.ToString().ToLowerInvariant(),
+ ["version"] = servePlatform.HelperVersionCompatible.ToString().ToLowerInvariant()
});
log.Fatal(failure);
return 2;
}
var sessionStore = new SessionStore();
- InternalToolDispatcher toolDispatcher = CreateToolDispatcher(configuration, effectiveSandboxPolicy, logDirectory, sessionStore);
+ InternalToolDispatcher toolDispatcher = await CreateToolDispatcherAsync(configuration, effectiveSandboxPolicy, logDirectory,
+ sessionStore, new ElevatedNetworkSessionFactory(helperPath), CancellationToken.None).ConfigureAwait(false);
using var runtimeObserver = new JsonLinesRuntimeObserver(logDirectory, configuration.Logging.MaxFileBytes, configuration.Logging.RetainedFiles);
string taskStateDirectory = Path.Combine(baseDirectory, "state", "mcp-tasks");
UserOnlyDirectory.Ensure(taskStateDirectory);
@@ -163,6 +167,9 @@ internal static class Program
Console.Out.WriteLine("\n" + locale.Get(current ? "apply.no_changes" : "apply.replace_snapshot"));
ConfirmMutation(command.Yes, locale.Get("apply.prompt"));
+ // Application Profile creation is a normal-user, non-elevated configuration operation.
+ // Server startup remains read-only and only derives the fixed SID.
+ _ = AppContainerProfile.EnsureFixed();
UserOnlyDirectory.Ensure(logDirectory);
AppliedConfigurationDocument document = appliedConfiguration.Apply();
var audit = new JsonLinesSecurityAuditSink(Path.Combine(logDirectory, "audit.jsonl"));
@@ -214,14 +221,18 @@ internal static class Program
return previous is null ? "added" : previous == item ? "active" : "changed";
}
- private static InternalToolDispatcher CreateToolDispatcher(AgentConfiguration configuration, EffectiveSandboxPolicy effectiveSandboxPolicy, string logDirectory, SessionStore? sessionStore = null)
+ private static async Task<InternalToolDispatcher> CreateToolDispatcherAsync(AgentConfiguration configuration,
+ EffectiveSandboxPolicy effectiveSandboxPolicy, string logDirectory, SessionStore? sessionStore,
+ IElevatedNetworkSessionFactory elevatedNetwork, CancellationToken cancellationToken)
{
Directory.CreateDirectory(effectiveSandboxPolicy.JournalDirectory);
Directory.CreateDirectory(effectiveSandboxPolicy.BackupDirectory);
var audit = new JsonLinesSecurityAuditSink(Path.Combine(logDirectory, "audit.jsonl"));
- RecoveryResult recovery = new RecoveryManager(effectiveSandboxPolicy.JournalDirectory, audit).RecoverAll();
+ RecoveryResult recovery = await new RecoveryManager(effectiveSandboxPolicy.JournalDirectory, audit, elevatedNetwork)
+ .RecoverAllAsync(cancellationToken).ConfigureAwait(false);
if (!recovery.IsClean) throw new InvalidOperationException("Recovery did not reach a clean state: " + string.Join(" | ", recovery.Errors));
- var backend = new WindowsToolBackend(effectiveSandboxPolicy, audit, new TaskGraphToolHandler(configuration.Planner.MaxTasks, sessionStore));
+ var backend = new WindowsToolBackend(effectiveSandboxPolicy, audit,
+ new TaskGraphToolHandler(configuration.Planner.MaxTasks, sessionStore), elevatedNetwork);
var allowed = InternalToolCatalog.All.Select(static item => item.Name).ToHashSet(StringComparer.Ordinal);
return new InternalToolDispatcher(new DenyByDefaultPolicy(allowed), backend);
}
diff --git a/src/Agent.App/app.manifest b/src/Agent.App/app.manifest
new file mode 100644
index 0000000..8843587
--- /dev/null
+++ b/src/Agent.App/app.manifest
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+</assembly>