Skip to content

Add shell approval disposition matrix - #1750

Merged
Aaronontheweb merged 5 commits into
devfrom
test/approval-matrix-contract
Aug 4, 2026
Merged

Add shell approval disposition matrix#1750
Aaronontheweb merged 5 commits into
devfrom
test/approval-matrix-contract

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add one xUnit theory with 59 Bash approval cases
  • use the Personal policy that a new installation stores
  • use the real approval actor, service, store, policy, and dispatcher seam
  • use the built-in safe verbs and hard-deny rules
  • add a review snapshot with outcomes, reasons, candidate verbs, and complex-command status
  • keep narrow unit tests for explicit Auto, Deny, and missing-policy configuration

Corpus

  • cover command chains, pipelines, redirects, subshells, wrappers, multiline input, heredocs, and malformed input
  • cover session and persistent approvals
  • cover directory and audience scope
  • cover interactive and non-interactive calls
  • remove all custom policy and safe-verb data from the matrix

Current parser gaps

The snapshot records current results that PR #1733 should change.

  • a hard-deny pipeline tail requests approval instead of a deny
  • echo $(git push) runs through the side-effect exemption
  • git status & git push runs through the safe-verb grant
  • bash -lc "git push" uses bash as its approval candidate

These rows make each future result change explicit in the snapshot review.

Shared install policy

ToolAudienceProfileDefaults.CreateProfilesForPosture now owns the installed audience profiles.

The setup wizard, the security editor, and the matrix use this factory.

No OpenSpec or product specification file changed.

Security fixes

  • require approval for side-effect commands that redirect output
  • evaluate path operands and redirect targets before a safe-verb grant
  • evaluate every pipeline clause so an unsafe tail cannot hide behind a safe head
  • resolve relative redirect targets from the invocation working directory

Validation

  • 2,719 actor tests passed
  • 60 approval matrix tests passed
  • 30 focused CLI tests passed
  • 6 focused configuration tests passed
  • all 22 native smoke tapes passed
  • all 9 smoke scenarios passed
  • dotnet slopwatch analyze
  • pwsh ./scripts/Add-FileHeaders.ps1 -Verify
  • git diff --check

Copilot AI review requested due to automatic review settings August 4, 2026 01:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment on lines +314 to +319
foreach (var redirect in clause.Redirects)
{
var directory = ResolveRedirectDirectory(redirect);
if (directory is not null)
directories.Add(directory);
}
Comment on lines +62 to +65
var rootDirectory = Path.Combine(
Path.GetTempPath(),
"netclaw-approval-matrix",
Guid.NewGuid().ToString("N"));
Path.GetTempPath(),
"netclaw-approval-matrix",
Guid.NewGuid().ToString("N"));
var projectDirectory = Path.Combine(rootDirectory, "project");
"netclaw-approval-matrix",
Guid.NewGuid().ToString("N"));
var projectDirectory = Path.Combine(rootDirectory, "project");
var sessionDirectory = Path.Combine(rootDirectory, "session");
Guid.NewGuid().ToString("N"));
var projectDirectory = Path.Combine(rootDirectory, "project");
var sessionDirectory = Path.Combine(rootDirectory, "session");
var externalDirectory = Path.Combine(rootDirectory, "external");
Directory.CreateDirectory(sessionDirectory);
Directory.CreateDirectory(externalDirectory);

var store = new ToolApprovalStore(Path.Combine(rootDirectory, "tool-approvals.json"));
Comment thread src/Netclaw.Actors.Tests/Tools/ShellApprovalHarness.cs Fixed
Comment thread src/Netclaw.Security.Tests/ShellApprovalMatcherTests.cs Fixed
Copilot AI review requested due to automatic review settings August 4, 2026 02:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

=> isMessy switch
{
true => "Yes",
false => "No",
{
true => "Yes",
false => "No",
null => "Not applicable"
@Aaronontheweb Aaronontheweb added tests All issues related to testing, quality assurance, and smoke testing. security Security-related changes shell Issues related to the shell tool, since it has the largest security perimeter. labels Aug 4, 2026
Copilot AI review requested due to automatic review settings August 4, 2026 02:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 4, 2026 02:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

if (!OperatingSystem.IsWindows())
{
foreach (var unit in ExtractApprovalUnitsViaBashParser(command))
foreach (var unit in ExtractApprovalUnitsViaBashParser(command, workingDirectory))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - better to pass this in explicitly

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

/// Creates the audience profiles that a new installation stores for the selected posture.
/// Personal installations require approval for shell commands unless another authorization gate permits the command.
/// </summary>
public static ToolAudienceProfiles CreateProfilesForPosture(DeploymentPosture posture)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - shell is denied in other security postures by default.

/// </summary>
public bool ShortCircuitsApproval(string candidateVerb, string? cwd, ToolInvocationContext context)
=> AllShortCircuit([candidateVerb], cwd, context);
=> AllShortCircuit([new ApprovalCandidate(candidateVerb, Directory: null)], cwd, context);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible to have verbs without a specific CWD

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - has good coverage of built in defaults / likely user-defined pairings

@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) August 4, 2026 02:58
@Aaronontheweb
Aaronontheweb merged commit c1ce6ba into dev Aug 4, 2026
21 checks passed
@Aaronontheweb
Aaronontheweb deleted the test/approval-matrix-contract branch August 4, 2026 03:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security Security-related changes shell Issues related to the shell tool, since it has the largest security perimeter. tests All issues related to testing, quality assurance, and smoke testing.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants