diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-08-09 21:08:20 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-08-09 21:08:20 +0900 |
| commit | b07b41cd435991cd254957af79055080c85b346e (patch) | |
| tree | 27720d3ce14e4d0b84670a55ac26a2f877ce3a20 /src/Agent.Windows/Network/LoopbackExemption.cs | |
| parent | 2fefd29e86c3b926d0cf4a362251d3f119688612 (diff) | |
feat: AI Agent Sandbox MCP Server v1を実装
Specifications、Design、ImplementationPlanの優先順位を整理し、単一Task実行モデル、typed option優先、設定可能なresource上限、local Ollamaを必須とするRelease Gateを文書化。
.NET 10/C# 14の5 production project、3 test project、E2E Clientを構築。typed YAML、en/ja locale、fixed/user prompt、stdioおよびloopback限定Streamable HTTP、agent_run/agent_status、Session、Ollama text/tool/Vision、TaskGraphとtoken accountingを実装。
Windows sandboxとしてfixed AppContainer、canonical/reparse検査、能力別ACL、path+SHA-256 executable approval、child-process restriction、Job Object、task journal、atomic file transaction、crash recoveryを実装。WFPのIPv4/IPv6 default denyとexact allow、dynamic session、temporary loopback exemption、管理者/BFE診断を追加。
Security/Recovery test、MCP/Ollama integration test、stdio/HTTP Operational E2E、Release Bundle生成・checksum・manifest・fresh extraction検査を追加。Requirement TraceabilityとPhase 0〜11の進捗、初回セットアップ可能なREADMEを整備。
Debug/Release/publish/objをrepository rootのbuild/へ集約し、旧project-local bin/objへの依存を除去。artifacts/とbuild/をGit管理外に設定し、clean checkoutでもsolution buildからE2E Clientまで生成可能にした。
Diffstat (limited to 'src/Agent.Windows/Network/LoopbackExemption.cs')
| -rw-r--r-- | src/Agent.Windows/Network/LoopbackExemption.cs | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/src/Agent.Windows/Network/LoopbackExemption.cs b/src/Agent.Windows/Network/LoopbackExemption.cs new file mode 100644 index 0000000..a3c68ba --- /dev/null +++ b/src/Agent.Windows/Network/LoopbackExemption.cs @@ -0,0 +1,123 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Security; +using Windows.Win32.System.Memory; + +namespace Agent.Windows.Network; + +internal static unsafe class LoopbackExemption +{ + private const string MutexName = "Local\\AgentSandboxMcpLoopbackExemption"; + + public static bool Contains(SecurityIdentifier sid) + { + using MutexLease mutex = AcquireMutex(); + return Read().Any(item => item.Sid.Equals(sid)); + } + + public static bool Ensure(SecurityIdentifier sid) + { + using MutexLease mutex = AcquireMutex(); + List<Entry> entries = Read(); + if (entries.Any(item => item.Sid.Equals(sid))) return false; + entries.Add(new(sid, 0)); + Write(entries); + return true; + } + + public static void Remove(SecurityIdentifier sid) + { + using MutexLease mutex = AcquireMutex(); + List<Entry> entries = Read(); + if (entries.RemoveAll(item => item.Sid.Equals(sid)) == 0) return; + Write(entries); + } + + private static MutexLease AcquireMutex() + { + var mutex = new Mutex(false, MutexName); + try + { + try + { + if (!mutex.WaitOne(TimeSpan.FromSeconds(30))) throw new TimeoutException("Timed out waiting for the loopback exemption lock."); + } + catch (AbandonedMutexException) + { + // Ownership is granted to this thread when the prior owner died. + } + return new(mutex); + } + catch + { + mutex.Dispose(); + throw; + } + } + + private static List<Entry> Read() + { + uint result = PInvoke.NetworkIsolationGetAppContainerConfig(out uint count, out SID_AND_ATTRIBUTES* native); + ThrowIfError(result, "NetworkIsolationGetAppContainerConfig"); + try + { + var entries = new List<Entry>(checked((int)count)); + for (uint index = 0; index < count; index++) + { + SID_AND_ATTRIBUTES item = native[index]; + entries.Add(new(new SecurityIdentifier((nint)item.Sid.Value), item.Attributes)); + } + return entries; + } + finally + { + HANDLE heap = PInvoke.GetProcessHeap(); + for (uint index = 0; index < count; index++) + if (native[index].Sid.Value is not null) _ = PInvoke.HeapFree(heap, HEAP_FLAGS.HEAP_NONE, native[index].Sid.Value); + if (native is not null) _ = PInvoke.HeapFree(heap, HEAP_FLAGS.HEAP_NONE, native); + } + } + + private static void Write(IReadOnlyList<Entry> entries) + { + var native = new SID_AND_ATTRIBUTES[entries.Count]; + var allocations = new List<nint>(entries.Count); + try + { + for (int index = 0; index < entries.Count; index++) + { + byte[] bytes = new byte[entries[index].Sid.BinaryLength]; + entries[index].Sid.GetBinaryForm(bytes, 0); + void* allocation = NativeMemory.Alloc((nuint)bytes.Length); + Marshal.Copy(bytes, 0, (nint)allocation, bytes.Length); + allocations.Add((nint)allocation); + native[index].Sid = new PSID(allocation); + native[index].Attributes = entries[index].Attributes; + } + ThrowIfError(PInvoke.NetworkIsolationSetAppContainerConfig(native), "NetworkIsolationSetAppContainerConfig"); + } + finally + { + foreach (nint allocation in allocations) NativeMemory.Free((void*)allocation); + } + } + + private static void ThrowIfError(uint result, string operation) + { + if (result != 0) throw new Win32Exception(unchecked((int)result), $"{operation} failed with Win32 error 0x{result:X8}."); + } + + private sealed record Entry(SecurityIdentifier Sid, uint Attributes); + + private sealed class MutexLease(Mutex mutex) : IDisposable + { + public void Dispose() + { + mutex.ReleaseMutex(); + mutex.Dispose(); + } + } +} |
