From 36ae1a4cba791ec633a81b38b1d8345af8487276 Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Thu, 13 Aug 2026 19:15:01 +0900 Subject: feat!: unify sandbox configuration and permission application Replace the split executable approval and sandbox resolution workflows with schema_version 2 and a YAML-driven execution.entries model. Resolve workspace, mount, executable, runtime, network, and resource settings into one config-scoped Applied Configuration snapshot protected by a user-only DACL and written with atomic replacement. Reduce the public CLI to implicit server startup, show, and apply. Remove serve/check, executable and sandbox subcommands, and configuration override flags including --config, --lang, --transport, --path, and --runtime-root. Add migration diagnostics for every removed schema and command surface, and keep server preflight mandatory for both stdio and HTTP. Update effective-policy compilation to validate the applied fingerprint, resolved paths, and executable hashes before startup. Preserve fail-closed behavior while allowing execution entries and runtime roots inside a development workspace to inherit that workspace capability without creating an impossible narrower ACL boundary. Rewrite the README around the consolidated permission matrix and document configuration, application, store location, startup, transport-specific elevation, Python 3.14, PowerShell, and cmd usage. Align specifications, design, implementation plan, traceability, audit report, localization, publish scripts, and operational verification with the breaking schema. Verification: Release build completed with zero warnings and errors; Agent.Core.Tests 104/104 and Agent.Planner.Tests 10/10 passed; administrator Windows Security/Recovery 56/56 and README operational 19/19 passed with no skips; published HTTP MCP completed a cmd/curl/venv-Python task with network deny and zero ACL, WFP, journal, backup, or process residue. --- src/Agent.App/Configuration/AgentConfiguration.cs | 11 ++++++++++- src/Agent.App/Configuration/ConfigurationLoader.cs | 13 +++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'src/Agent.App/Configuration') diff --git a/src/Agent.App/Configuration/AgentConfiguration.cs b/src/Agent.App/Configuration/AgentConfiguration.cs index b2f94a7..ef2eebc 100644 --- a/src/Agent.App/Configuration/AgentConfiguration.cs +++ b/src/Agent.App/Configuration/AgentConfiguration.cs @@ -74,7 +74,16 @@ public sealed class SandboxOptions public ResourceOptions Resources { get; set; } = new(); } public sealed class MountOptions { public string Path { get; set; } = string.Empty; public string Access { get; set; } = "read_only"; } -public sealed class ExecutionOptions { public List Executables { get; set; } = []; public List Shells { get; set; } = []; } +public sealed class ExecutionOptions +{ + public List Entries { get; set; } = []; +} +public sealed class ExecutionEntryOptions +{ + public string Alias { get; set; } = string.Empty; + public string Path { get; set; } = string.Empty; + public string? RuntimeRoot { get; set; } +} public sealed class NetworkOptions { public string Access { get; set; } = "none"; diff --git a/src/Agent.App/Configuration/ConfigurationLoader.cs b/src/Agent.App/Configuration/ConfigurationLoader.cs index 6e15904..122c499 100644 --- a/src/Agent.App/Configuration/ConfigurationLoader.cs +++ b/src/Agent.App/Configuration/ConfigurationLoader.cs @@ -43,7 +43,7 @@ public static partial class ConfigurationLoader public static void Validate(AgentConfiguration value, string baseDirectory) { - if (value.SchemaVersion != 1) Throw("schema_version must be 1."); + if (value.SchemaVersion != 2) Throw("schema_version must be 2. Version 1 is no longer supported; migrate sandbox.execution to entries and run 'agent-mcp apply'."); if (string.IsNullOrWhiteSpace(value.Application.Language)) Throw("application.language is required."); if (value.Mcp.Transport is not ("stdio" or "http")) Throw("mcp.transport must be stdio or http."); if (!IPAddress.TryParse(value.Mcp.Http.Host, out IPAddress? host) || !IPAddress.IsLoopback(host)) Throw("mcp.http.host must be 127.0.0.1 or ::1."); @@ -95,10 +95,12 @@ public static partial class ConfigurationLoader if (sandbox.Profile == "readonly" && mount.Access != "read_only") Throw("readonly sandbox.profile permits only read_only mounts."); } - string[] aliases = sandbox.Execution.Executables.Concat(sandbox.Execution.Shells).ToArray(); + string[] aliases = sandbox.Execution.Entries.Select(static entry => entry.Alias).ToArray(); if (aliases.Any(alias => !AliasRegex().IsMatch(alias))) Throw("Executable Alias must match ^[a-z0-9][a-z0-9._-]{0,63}$."); if (aliases.Distinct(StringComparer.OrdinalIgnoreCase).Count() != aliases.Length) - Throw("Executable Alias values must be unique across sandbox.execution.executables and shells."); + Throw("sandbox.execution.entries Alias values must be unique."); + if (sandbox.Execution.Entries.Any(static entry => string.IsNullOrWhiteSpace(entry.Path))) + Throw("sandbox.execution.entries requires path for every Alias."); if (sandbox.Network.Access is not ("none" or "local" or "restricted")) Throw("sandbox.network.access must be none, local, or restricted."); @@ -125,7 +127,10 @@ public static partial class ConfigurationLoader YamlNode root = LoadRoot(yaml, "merged configuration"); if (root is not YamlMappingNode rootMap || !TryGet(rootMap, "sandbox", out YamlNode? sandboxNode) || sandboxNode is not YamlMappingNode sandbox) return; if (TryGet(sandbox, "filesystem", out _) || TryGet(sandbox, "process", out _)) - Throw("Legacy sandbox.filesystem/sandbox.process configuration is no longer supported. Migrate to sandbox.profile, workspaces, mounts, execution, network, and resources; then run 'sandbox resolve' and 'sandbox confirm'."); + Throw("Legacy sandbox.filesystem/sandbox.process configuration is no longer supported. Migrate to schema_version 2 and sandbox.execution.entries; then run 'agent-mcp apply'."); + if (TryGet(sandbox, "execution", out YamlNode? executionNode) && executionNode is YamlMappingNode execution && + (TryGet(execution, "executables", out _) || TryGet(execution, "shells", out _))) + Throw("schema_version 1 sandbox.execution.executables/shells is no longer supported. Migrate each Alias to sandbox.execution.entries with path and optional runtime_root; then run 'agent-mcp apply'."); } private static bool TryGet(YamlMappingNode map, string name, out YamlNode? value) -- cgit v1.2.3