Private
Public Access
0
0

fix(tier2): top-level permission allowlist - sandbox paths now enforced

Regression: a Tier 2 session was denied access to
C:\\projects\\manual_slop_tier2\\scripts\\run_tests_batched.py
with 'Allowed base directories are: gencpp, manual_slop'. The
tier2-autonomous agent had a correct permission.read allowlist, but
the top-level permission block (inherited from the main repo's
opencode.json via 'git clone') had no read/write keys, and OpenCode
uses the top-level for the default agent path. The agent's
permission.read was merged but apparently not enforced for the
default-agent access check.

Fix:
1. Add a top-level 'permission' block to
   conductor/tier2/opencode.json.fragment with:
   - permission.edit: 'deny' (default agents locked down)
   - permission.read: deny *, allow sandbox clone + app-data dirs
   - permission.write: same
   - permission.bash: deny *, allowlist of read-only git commands +
     uv run python scripts/{run_tests_batched.py,tier2/*} + basic
     shell commands. git push/checkout/restore/reset remain denied.

2. Update setup_tier2_clone.ps1 to also patch the top-level
   'permission' block (was only merging the tier2-autonomous agent
   block). The script preserves the user's mcp, model, instructions,
   watcher, and plugin settings from the inherited opencode.json.

3. Update test_tier2_slash_command_spec.py:
   - Rename test_command_fetches_origin_main -> ..._master (we
     changed the slash command on 2026-06-17).
   - Add test_config_fragment_has_top_level_permission to assert
     the new top-level permission block has the right deny-all +
     allowlist shape.

The tier2-autonomous agent's permission block is unchanged; it
overrides the top-level for that agent's tool calls.
This commit is contained in:
2026-06-17 13:43:53 -04:00
parent ee75660834
commit 9cd8536455
3 changed files with 84 additions and 3 deletions
+47
View File
@@ -1,6 +1,53 @@
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "tier2-autonomous",
"permission": {
"edit": "deny",
"read": {
"*": "deny",
"C:\\projects\\manual_slop_tier2\\**": "allow",
"C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2\\**": "allow",
"C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2_failures\\**": "allow"
},
"write": {
"*": "deny",
"C:\\projects\\manual_slop_tier2\\**": "allow",
"C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2\\**": "allow",
"C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2_failures\\**": "allow"
},
"bash": {
"*": "deny",
"git status*": "allow",
"git diff*": "allow",
"git log*": "allow",
"git add*": "allow",
"git commit*": "allow",
"git switch*": "allow",
"git branch*": "allow",
"git fetch*": "allow",
"git remote*": "allow",
"git rev-parse*": "allow",
"git show*": "allow",
"git config --get*": "allow",
"ls*": "allow",
"cat*": "allow",
"head*": "allow",
"tail*": "allow",
"find*": "allow",
"echo*": "allow",
"mkdir*": "allow",
"cp*": "allow",
"mv*": "allow",
"rm*": "allow",
"uv run python scripts/run_tests_batched.py*": "allow",
"uv run python scripts/tier2/*": "allow",
"pwsh -File scripts/tier2/*": "allow",
"git push*": "deny",
"git checkout*": "deny",
"git restore*": "deny",
"git reset*": "deny"
}
},
"agent": {
"tier2-autonomous": {
"model": "minimax-coding-plan/MiniMax-M3",
+12 -1
View File
@@ -61,13 +61,24 @@ if ($PSCmdlet.ShouldProcess("Bootstrap Tier 2 clone at $Tier2ClonePath")) {
Copy-Item -Force "$MainRepoPath\conductor\tier2\agents\tier2-autonomous.md" "$Tier2ClonePath\.opencode\agents\tier2-autonomous.md"
Copy-Item -Force "$MainRepoPath\conductor\tier2\commands\tier-2-auto-execute.md" "$Tier2ClonePath\.opencode\commands\tier-2-auto-execute.md"
# Merge opencode.json.fragment into the clone's opencode.json
# Merge opencode.json.fragment into the clone's opencode.json.
# The clone inherits a copy of the main repo's opencode.json (via
# `git clone`), which has top-level `permission.edit: ask` and
# `permission.bash: ask`. Those would be unsafe in the sandbox: the
# build/plan default agents could read/write anywhere on disk, and
# there is no file-system allowlist at the top level. We replace
# the top-level `permission` with the hardened sandbox version
# (deny-all + allowlist for the sandbox dirs + the tier2-autonomous
# agent's permission block). The agent's `permission` overrides the
# top-level for that agent's tool calls.
$cloneConfig = "$Tier2ClonePath\opencode.json"
$fragment = Get-Content "$MainRepoPath\conductor\tier2\opencode.json.fragment" -Raw | ConvertFrom-Json
if (Test-Path $cloneConfig) {
$existing = Get-Content $cloneConfig -Raw | ConvertFrom-Json
if (-not $existing.agent) { $existing | Add-Member -MemberType NoteProperty -Name agent -Value ([PSCustomObject]@{}) }
$existing.agent | Add-Member -MemberType NoteProperty -Name "tier2-autonomous" -Value $fragment.agent."tier2-autonomous" -Force
if (-not $existing.permission) { $existing | Add-Member -MemberType NoteProperty -Name permission -Value ([PSCustomObject]@{}) }
$existing.permission = $fragment.permission
$existing | Add-Member -MemberType NoteProperty -Name default_agent -Value "tier2-autonomous" -Force
$existing | ConvertTo-Json -Depth 10 | Set-Content $cloneConfig
} else {
+25 -2
View File
@@ -42,9 +42,9 @@ def test_command_uses_git_switch_not_checkout() -> None:
assert all("checkout" not in line for line in shell_lines), f"protocol uses git checkout: {shell_lines}"
def test_command_fetches_origin_main() -> None:
def test_command_fetches_origin_master() -> None:
content = COMMAND_PATH.read_text(encoding="utf-8")
assert "git fetch origin main" in content
assert "git fetch origin master" in content
def test_command_initializes_failcount_state() -> None:
@@ -87,3 +87,26 @@ def test_config_fragment_valid_json() -> None:
assert "git checkout*" in perms["bash"]
assert "git restore*" in perms["bash"]
assert "git reset*" in perms["bash"]
def test_config_fragment_has_top_level_permission() -> None:
"""Top-level permission.read/write MUST allow the sandbox dirs (added
2026-06-17 after the bug where the agent's permission.read was not
enforced for the default agent, leading to ACCESS DENIED on
manual_slop_tier2 paths)."""
data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
assert "permission" in data
top = data["permission"]
assert "read" in top, "top-level permission.read is required"
assert top["read"].get("*") == "deny", "top-level permission.read MUST deny *"
assert top["read"].get("C:\\projects\\manual_slop_tier2\\**") == "allow", "sandbox clone path must be allowlisted"
assert "write" in top
assert top["write"].get("*") == "deny"
assert top["write"].get("C:\\projects\\manual_slop_tier2\\**") == "allow"
assert "bash" in top
assert top["bash"].get("*") == "deny", "top-level bash MUST deny * (default agents are locked down)"
assert top["bash"].get("git status*") == "allow", "read-only git commands must be in the allowlist"
assert top["bash"].get("git push*") == "deny"
assert top["bash"].get("git checkout*") == "deny"
assert top["bash"].get("git restore*") == "deny"
assert top["bash"].get("git reset*") == "deny"