diff --git a/Codev.Tests/AgentProfileCatalogTests.cs b/Codev.Tests/AgentProfileCatalogTests.cs index fb76c33e..b45d780f 100644 --- a/Codev.Tests/AgentProfileCatalogTests.cs +++ b/Codev.Tests/AgentProfileCatalogTests.cs @@ -309,6 +309,7 @@ await File.WriteAllTextAsync(Path.Combine(directory, "writer.md"), """ Assert.Equal(6, writer.MaxSteps); Assert.Equal(AgentToolPermission.Allow, AgentProfilePolicy.PermissionFor(writer, "read_file", resource: "docs/guide.md")); Assert.Equal(AgentToolPermission.Deny, AgentProfilePolicy.PermissionFor(writer, "read_file", resource: "docs/private/key.md")); + Assert.Equal(AgentToolPermission.Deny, AgentProfilePolicy.PermissionFor(writer, "read_file", resource: "docs/../docs/private/key.md")); Assert.Equal(AgentToolPermission.Ask, AgentProfilePolicy.PermissionFor(writer, "read_file", resource: "README.md")); Assert.Equal(AgentToolPermission.Ask, AgentProfilePolicy.PermissionFor(writer, "read_file", resource: "docs/../README.md")); Assert.Equal(AgentToolPermission.Ask, AgentProfilePolicy.PermissionFor(writer, "read_file", resource: "../docs/guide.md")); @@ -319,6 +320,7 @@ await File.WriteAllTextAsync(Path.Combine(directory, "writer.md"), """ Assert.Equal(AgentToolPermission.Allow, AgentProfilePolicy.PermissionFor(writer, "search_files")); Assert.True(AgentProfilePolicy.CanExposeReadPath(writer, "docs/guide.md")); Assert.False(AgentProfilePolicy.CanExposeReadPath(writer, "docs/private/key.md")); + Assert.False(AgentProfilePolicy.CanExposeReadPath(writer, "docs/../docs/private/key.md")); Assert.False(AgentProfilePolicy.CanExposeReadPath(writer, "README.md")); Assert.False(AgentProfilePolicy.CanExposeReadPath(writer, "docs/../README.md")); Assert.False(AgentProfilePolicy.CanExposeReadPath(writer, "../docs/guide.md")); @@ -326,7 +328,7 @@ await File.WriteAllTextAsync(Path.Combine(directory, "writer.md"), """ Assert.False(AgentProfilePolicy.CanExposeReadPath(writer, "C:/docs/guide.md")); Assert.True(AgentProfilePolicy.CanExposeReadPath(writer, "docs/../docs/guide.md")); Assert.Equal(["docs/guide.md"], AgentProfilePolicy.FilterReadablePaths(writer, - ["docs/guide.md", "docs/private/key.md", "README.md", "docs/../README.md", "../docs/guide.md"])); + ["docs/guide.md", "docs/private/key.md", "docs/../docs/private/key.md", "README.md", "docs/../README.md", "../docs/guide.md"])); Assert.Empty(loaded.Warnings); } diff --git a/Codev.Tests/CodeTaskToolExecutorTests.cs b/Codev.Tests/CodeTaskToolExecutorTests.cs index fb38b1be..55a3b8a1 100644 --- a/Codev.Tests/CodeTaskToolExecutorTests.cs +++ b/Codev.Tests/CodeTaskToolExecutorTests.cs @@ -127,6 +127,7 @@ public async Task OpenCode_scoped_read_rules_filter_list_search_and_direct_file_ var search = GetUntrustedContent(await ExecuteAsync(executor, "search_files", """{"query":"needle"}""")); var allowedRead = await ExecuteAsync(executor, "read_file", """{"relative_path":"docs/guide.md"}"""); var deniedRead = await ExecuteAsync(executor, "read_file", """{"relative_path":"docs/private/secret.md"}"""); + var traversalDeniedRead = await ExecuteAsync(executor, "read_file", """{"relative_path":"docs/../docs/private/secret.md"}"""); var unmatchedRead = await ExecuteAsync(executor, "read_file", """{"relative_path":"README.md"}"""); Assert.Contains(Path.Combine("docs", "guide.md"), listing, StringComparison.Ordinal); @@ -135,6 +136,7 @@ public async Task OpenCode_scoped_read_rules_filter_list_search_and_direct_file_ Assert.DoesNotContain("private needle", search, StringComparison.Ordinal); Assert.Contains("public needle", allowedRead, StringComparison.Ordinal); Assert.Contains("Denied by the selected agent profile", deniedRead, StringComparison.Ordinal); + Assert.Contains("Denied by the selected agent profile", traversalDeniedRead, StringComparison.Ordinal); Assert.Contains("Rejected by the selected agent profile", unmatchedRead, StringComparison.Ordinal); var semanticExecutor = new CodeTaskToolExecutor(new WorkspaceFileService(_root), _conversation, diff --git a/DESIGN.md b/DESIGN.md index 86540eac..09956110 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -353,7 +353,7 @@ X-items are from section 7. Items in phase 1 to 3 do not depend on the Avalonia |---|---|---|---|---| | D1 | Slash commands and file-based commands | S | none | Core · Avalonia implemented; project commands require trust; manual QA remains; The shared-core implementation remains in place | | D2 | Skills | M | D1 | Later · Avalonia discovers Codev and OpenCode/Claude/Agents-compatible user and trusted-project skill folders. It accepts OpenCode `name`, `license`, `compatibility`, and nested `metadata` frontmatter, validates the declared name against the directory, and keeps Codev's bounded prompt-only behavior. Project Codev skills win same-name conflicts, followed by OpenCode, Claude, and Agents folders; Codev user skills win over shared user folders. Code task loaders re-read the original file and recheck trust. The portable suite now passes 822 tests with one opt-in live-model test skipped; manual UI checks remain | -| D3 | Agent definition files | M | B4 | Core · Avalonia supports built-in Build/Plan primary agents switchable during Code tasks, Markdown profiles, a validated user profile editor, prompt/tool policies with wildcard matching, edit-path and command allow/deny globs, and model/temperature/step settings. It imports legacy and current V2 OpenCode Markdown profiles from the standard user and trusted-project folders, maps ordered wildcard, shell-command, read-path, and edit-path permissions without overriding Codev's project policy, and warns when a model preference is unsupported. Scoped read rules are checked for direct reads and filter list/search results before they reach the model; relative file candidates are normalized lexically before matching, and rooted or escaping paths cannot inherit a narrower directory allow; they also constrain automatically attached source excerpts, relevance-preflight candidate paths, and optional repo-map entries. An explicit empty filtered selection stays empty instead of falling back to unrestricted automatic context. Resource-scoped V2 `glob`/`grep` rules now map to bounded path-glob and regex-search tools, with requested patterns checked against ordered permission rules and read-path scope still filtering returned files. Path-scoped edit `ask` rules now reach per-proposal approval outside Auto while matching denies remain hard blocks; the shared Auto/project policy still governs asks in Auto. Resource-scoped V2 `task`/`subagent` rules now target specific installed profiles through isolated child worktrees; target denies are hard blocks, asks follow the Auto/project policy, and only root profiles with explicit task permission may delegate. Resource-scoped V2 skill rules filter each discovered skill by OpenCode skill-ID patterns, with deny hiding skills and ask checked when loading; resource-scoped custom-tool rules remain unsupported and are rejected with a warning. Headless UI tests reject invalid profile Markdown, save valid Markdown through the real store and verify reload, select a saved profile in the real picker while verifying active-conversation persistence and label updates, and switch Build/Plan from the picker and Ctrl+Shift+A in both directions. Persisted profile references use validated display names (including spaces); profile filenames remain short slugs. Portable tests assert Plan schemas omit edit, shell, MCP, delegation, and background operations for both Ollama and OpenAI; executor tests reject Plan edits and commands before project review or command approval. A Windows x64 preview smoke verified that a multi-word `Smoke QA` selection remains visible after restart. Current-source Windows/.NET 10 recheck on PR head `3ce4814` passed all 30 `AgentProfileCatalogTests` and `Code_task_profile_picker_selects_a_saved_profile_on_the_active_conversation` (1/1), including OpenCode-format frontmatter import and picker selection/persistence. Other native profile-editor checks remain; The shared policy engine remains in place | +| D3 | Agent definition files | M | B4 | Core · Avalonia supports built-in Build/Plan primary agents switchable during Code tasks, Markdown profiles, a validated user profile editor, prompt/tool policies with wildcard matching, edit-path and command allow/deny globs, and model/temperature/step settings. It imports legacy and current V2 OpenCode Markdown profiles from the standard user and trusted-project folders, maps ordered wildcard, shell-command, read-path, and edit-path permissions without overriding Codev's project policy, and warns when a model preference is unsupported. Scoped read rules are checked for direct reads and filter list/search results before they reach the model; relative file candidates are normalized lexically before matching, and rooted or escaping paths cannot inherit a narrower directory allow. Regression tests verify that traversal normalizing into a nested explicit Deny remains denied by both the matcher and direct-read executor. Scoped rules also constrain automatically attached source excerpts, relevance-preflight candidate paths, and optional repo-map entries. An explicit empty filtered selection stays empty instead of falling back to unrestricted automatic context. Resource-scoped V2 `glob`/`grep` rules now map to bounded path-glob and regex-search tools, with requested patterns checked against ordered permission rules and read-path scope still filtering returned files. Path-scoped edit `ask` rules now reach per-proposal approval outside Auto while matching denies remain hard blocks; the shared Auto/project policy still governs asks in Auto. Resource-scoped V2 `task`/`subagent` rules now target specific installed profiles through isolated child worktrees; target denies are hard blocks, asks follow the Auto/project policy, and only root profiles with explicit task permission may delegate. Resource-scoped V2 skill rules filter each discovered skill by OpenCode skill-ID patterns, with deny hiding skills and ask checked when loading; resource-scoped custom-tool rules remain unsupported and are rejected with a warning. Headless UI tests reject invalid profile Markdown, save valid Markdown through the real store and verify reload, select a saved profile in the real picker while verifying active-conversation persistence and label updates, and switch Build/Plan from the picker and Ctrl+Shift+A in both directions. Persisted profile references use validated display names (including spaces); profile filenames remain short slugs. Portable tests assert Plan schemas omit edit, shell, MCP, delegation, and background operations for both Ollama and OpenAI; executor tests reject Plan edits and commands before project review or command approval. A Windows x64 preview smoke verified that a multi-word `Smoke QA` selection remains visible after restart. Current-source Windows/.NET 10 recheck on PR head `3ce4814` passed all 30 `AgentProfileCatalogTests` and `Code_task_profile_picker_selects_a_saved_profile_on_the_active_conversation` (1/1), including OpenCode-format frontmatter import and picker selection/persistence. Other native profile-editor checks remain; The shared policy engine remains in place | | D4 | Hooks (formatter first) | M | B1 | Avalonia formatter hook implemented · general hooks remain Later; manual UI/security review pending | | D5 | Subagents with child sessions | L | D3, section 3 worktrees | Later · Avalonia implements isolated child conversations, Orchestrator delegation, child diff review/merge, and missing-worktree recovery. Codev-managed file operations and Git history are isolated in per-child worktrees; shell commands still run with the user's account permissions and can affect paths or services outside the worktree. Child UI/status and delegation output disclose this boundary. Parallel worktree mutation is serialized, and creation/recovery disable checkout hooks, fsmonitor, and configured smudge/process filters; child merge suppresses hooks and refuses external merge drivers or checkout filters only when they apply to reviewed files (or when a changed `.gitattributes` could affect them). Git LFS/custom-filter files are consequently unexpanded during child checkout and this is reported to the user. Smudge filters are disabled with an empty command so checkout does not launch a replacement executable from PATH. Regression tests cover helper suppression, concurrency, atomic permission-policy inheritance, recovery, parent-stop scoping, and stopping one selected child without canceling siblings. Child mode and eligible rules are persisted in one update, so interruption cannot leave Auto active without inherited exact denies. **Current-checkout validation (2026-10-07, code head `415a171`):** the live Qwen3.6 `Live_orchestrator_starts_two_read_only_children_in_parallel_worktrees` test passed in 49 seconds, verifying two overlapping Ask children in distinct managed worktrees, exact fixture reads/results, and a clean parent Git tree. The two stop-scope regression tests passed 2/2, and the full Avalonia Release suite passed 56 tests with 12 opt-in live tests skipped. At PR #10 head `af41fc9` on 2026-10-08, focused local Windows .NET 10 Release runs passed all 11 `GitChildWorktreeManagerTests` and four `ChildDelegationTests`; the opt-in live Orchestrator/Qwen3.6 test was skipped because `CODEV_OLLAMA_LIVE_TESTS` was unset. Full OS-level shell confinement remains dependent on B7; packaged UI stop/cancel controls, native interaction with review/merge/recovery, and independent security review remain release gaps. D5's workflow is implemented ahead of phase 5; the remaining safety and manual-verification work is not complete. | | D6 | MCP client | L | B1 | Core · Avalonia implemented for Ollama and OpenAI Code tasks (stdio and auto-detected Streamable HTTP with legacy SSE fallback, user settings, tool/prompt/resource/resource-template discovery and calls, untrusted results, per-project permissions); diagnostics persist per conversation. On 2026-10-01, the focused MCP suite passed 49/49 on Windows Release, including a cross-origin redirect regression proving custom environment-backed secret headers reach only the configured endpoint. The mock OAuth/PKCE integration covers browser callback, token exchange, authenticated MCP call, persistence, and refresh. Opt-in native-vault OAuth smoke now passes with Windows Credential Manager, Linux Secret Service, and macOS Keychain (CI [#36849825001](https://github.com/jay23606/codev/actions/runs/36849825001)); four-RID package/launch validation passed in [#36849824882](https://github.com/jay23606/codev/actions/runs/36849824882). Latest Avalonia UI plus portable CI passed on all three OSes in [#36853446126](https://github.com/jay23606/codev/actions/runs/36853446126), with four-RID packaging and launch in [#36853446114](https://github.com/jay23606/codev/actions/runs/36853446114). Live provider sign-in, native interactive macOS/Linux UI smoke, and independent security review remain; external server configuration and an optional global connection-history view remain future work; The shared-core implementation remains in place |