Bug: Non-interactive channels always fail with shell_no_trust_zone_roots
Description
All non-interactive channel types — Headless (netclaw chat -p), Reminder, and Webhook — fail to execute any shell commands with shell_no_trust_zone_roots, regardless of verb pre-approval. This makes shell execution impossible in automated contexts.
The problem has two independent root causes that combine to make this unavoidable:
1. Trust zone enforcement runs before the approval gate
In ToolAccessPolicy.AuthorizeInvocation, the trust zone check executes before CheckApprovalGate. Pre-approved verbs never reach the matcher:
// Line 156-166: Trust zone enforcement — runs first
if (context?.SupportsInteractiveApproval == false && shellCommand is not null)
{
if (_shellTrustZonePolicy is null)
{
if (ShellCommandHasTrustZoneSensitiveInputs(shellCommand, workingDirectory))
return ToolAccessDecision.Deny("shell_trust_zone_policy_not_configured");
}
else
{
var trustZoneDeny = EnforceShellTrustZones(shellCommand, workingDirectory, context);
if (trustZoneDeny is not null)
return trustZoneDeny; // ← blocks here
}
}
// Line 171: Approval gate — never reached
return CheckApprovalGate(toolName, context, arguments, ShellApprovalMatcher.Instance);
2. Personal audience resolves to zero trust zone roots
ShellTrustZonePolicy.GetTrustZoneRoots() calls ScopedFileAccessPolicy.GetRootsForContext(context, AccessKind.Write). For the Personal profile, WriteFiles.Mode is "All", which causes ResolveRoots to short-circuit:
// ToolAudienceProfileResolver.cs:34-35
if (access.Mode != ToolFilesystemMode.Roots)
return []; // ← Personal has Mode: "All" → returns empty
Then EnforceShellTrustZones immediately denies on the empty set:
// ToolAccessPolicy.cs:183-184
var roots = _shellTrustZonePolicy!.GetTrustZoneRoots(context);
if (roots.Count == 0)
return ToolAccessDecision.Deny("shell_no_trust_zone_roots"); // ← hits here
Affected Channels
All channels where SupportsInteractiveApproval == false:
| Channel |
Use Case |
Blocked? |
Headless (netclaw chat -p) |
User at terminal, single prompt |
✅ |
Reminder |
Automated tasks with pre-approved verbs |
✅ |
Webhook |
Inbound event processing |
✅ |
Reproduction
# 1. Pre-approve a verb
netclaw approvals trust-verb netclaw
# 2. Run in headless mode
echo "Run 'netclaw stats'" | netclaw chat -p -
# Result: shell_no_trust_zone_roots
# 3. Create a reminder with channel delivery
set_reminder(id="test", prompt="Run 'netclaw stats'", schedule="2m",
deliveryKind="channel", deliveryTransport="slack", deliveryAddress="#channel")
# Result: shell_no_trust_zone_roots
Source References (commit 76ea1c3)
Proposed Fix
Option A — Return meaningful roots for Personal audience
ShellTrustZonePolicy should return sensible defaults for the Personal audience even when WriteFiles.Mode == "All":
// Pseudocode
if (audience == TrustAudience.Personal)
{
roots = [session_dir, workspaces_dir, identity_dir];
}
Option B — Check pre-approval before trust zone enforcement
If the verb is globally pre-approved, skip trust zone path validation. Pre-approval is the explicit authorization mechanism for unattended execution.
Option C — Distinguish automated channel types
Not all non-interactive channels have the same threat model:
- Webhooks — external payloads, stay strict
- Reminders — user-created, inherit creator's pre-approved verbs
- Headless — user at terminal, deserves interactive-level trust
This would require per-channel-type trust zone configuration rather than a binary SupportsInteractiveApproval check.
Questions for Discussion
- Should pre-approved verbs bypass trust zone path validation, or should trust zones always apply?
- What are the correct trust zone roots for Personal audience when
WriteFiles.Mode == "All"?
- Should
Headless, Reminder, and Webhook share the same security posture, or should they be distinguished?
Bug: Non-interactive channels always fail with
shell_no_trust_zone_rootsDescription
All non-interactive channel types —
Headless(netclaw chat -p),Reminder, andWebhook— fail to execute any shell commands withshell_no_trust_zone_roots, regardless of verb pre-approval. This makes shell execution impossible in automated contexts.The problem has two independent root causes that combine to make this unavoidable:
1. Trust zone enforcement runs before the approval gate
In
ToolAccessPolicy.AuthorizeInvocation, the trust zone check executes beforeCheckApprovalGate. Pre-approved verbs never reach the matcher:2. Personal audience resolves to zero trust zone roots
ShellTrustZonePolicy.GetTrustZoneRoots()callsScopedFileAccessPolicy.GetRootsForContext(context, AccessKind.Write). For the Personal profile,WriteFiles.Modeis"All", which causesResolveRootsto short-circuit:Then
EnforceShellTrustZonesimmediately denies on the empty set:Affected Channels
All channels where
SupportsInteractiveApproval == false:Headless(netclaw chat -p)ReminderWebhookReproduction
Source References (commit
76ea1c3)Proposed Fix
Option A — Return meaningful roots for Personal audience
ShellTrustZonePolicyshould return sensible defaults for the Personal audience even whenWriteFiles.Mode == "All":Option B — Check pre-approval before trust zone enforcement
If the verb is globally pre-approved, skip trust zone path validation. Pre-approval is the explicit authorization mechanism for unattended execution.
Option C — Distinguish automated channel types
Not all non-interactive channels have the same threat model:
This would require per-channel-type trust zone configuration rather than a binary
SupportsInteractiveApprovalcheck.Questions for Discussion
WriteFiles.Mode == "All"?Headless,Reminder, andWebhookshare the same security posture, or should they be distinguished?