Skip to content

Consolidate shell policy path facts - #1945

Merged
Aaronontheweb merged 13 commits into
devfrom
refactor/shell-policy-path-facts
Aug 14, 2026
Merged

Consolidate shell policy path facts#1945
Aaronontheweb merged 13 commits into
devfrom
refactor/shell-policy-path-facts

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

Summary

  • project parser-owned shell path facts once per source occurrence
  • preserve distinct real, intent, and fallback resolution bases
  • retain path origin, domain kind, redirect semantics, and unknown-versus-invalid state
  • route causal protected-path and reviewed-safe stages through typed facts
  • keep raw authorization defense scanning and both execution-time denied-path/symlink rechecks

Security boundaries

  • no executable-specific parsing
  • no public or durable API change
  • no parser occurrence or command text crosses actor/persistence boundaries
  • protected paths, symlinks, dynamic values, mixed path styles, and malformed facts remain fail-closed

Validation

  • all three commit patch IDs preserved across rebase; range-diff reports exact identity
  • post-rebase actor policy set: 410 passed, 1 expected Windows-only skip
  • post-rebase security path/matcher/evidence set: 246 passed
  • pre-rebase full solution: 7,415 passed, 15 expected skips
  • Release build: 0 warnings/errors
  • strict OpenSpec, headers, format, diff, PII, and changed-file Slopwatch checks
  • independent adversarial review: PASS with no warnings

This is the typed-fact consolidation slice for simplify-shell-policy-evaluator; obsolete compatibility helpers are removed in the following net-negative slice.

@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) August 14, 2026 06:49

Assert.Same(candidate.SourceOccurrence, facts.SourceOccurrence);
Assert.Equal(ShellPolicyPathResolutionState.Known, facts.RealScope.State);
Assert.Equal("/work", facts.RealScope.Path?.Value);

var facts = evaluation.Projection.PathFacts.For(candidate.Id);

Assert.Equal("/work/sub", facts.RealScope.Path?.Value);
var facts = evaluation.Projection.PathFacts.For(candidate.Id);

Assert.Equal("/work/sub", facts.RealScope.Path?.Value);
Assert.Equal("/work", facts.Real?.ResolutionBase.Path?.Value);

var facts = evaluation.Projection.PathFacts.For(candidate.Id);

Assert.Equal("/tmp", facts.IntentScope?.Path?.Value);
var facts = evaluation.Projection.PathFacts.For(candidate.Id);

Assert.Equal("/tmp", facts.IntentScope?.Path?.Value);
Assert.Contains(facts.FallbackScopes, scope => scope.Path?.Value == "/work");
Comment on lines +310 to +326
foreach (var fact in resolvedPaths.Facts.Where(static fact =>
fact.Source.Origin == ShellPolicyPathOrigin.AuthoredArgument))
{
if (fact.Source.AuthoredPathShape == ShellPathShape.Posix
&& pathStyle != ShellPathStyle.Posix
|| fact.Source.AuthoredPathShape == ShellPathShape.Windows
&& pathStyle != ShellPathStyle.Windows
|| fact.Source.DomainKind is not
(ShellPolicyPathDomainKind.Exact or ShellPolicyPathDomainKind.FiniteSet)
|| fact.State != ShellPolicyPathResolutionState.Known
|| fact.Paths.Count == 0
|| fact.Paths.Any(path =>
!safeRoots.Any(root => IsSafePath(path.Value, root))))
{
return false;
}
}
Comment on lines +338 to +349
foreach (var fact in resolvedPaths.Facts.Where(static fact =>
fact.Source.Origin == ShellPolicyPathOrigin.EffectiveArgument))
{
if (fact.Source.DomainKind is not
(ShellPolicyPathDomainKind.Exact or ShellPolicyPathDomainKind.FiniteSet)
|| fact.State != ShellPolicyPathResolutionState.Known
|| fact.Paths.Count == 0
|| fact.Paths.Any(path => !IsSafePath(path.Value, intentDirectory)))
{
return false;
}
}
Comment on lines +351 to +362
foreach (var fact in resolvedPaths.Facts.Where(static fact =>
fact.Source.Origin == ShellPolicyPathOrigin.Redirect))
{
if (fact.Source.RedirectMode != FileRedirectMode.Input
|| fact.Source.DomainKind != ShellPolicyPathDomainKind.Exact
|| fact.State != ShellPolicyPathResolutionState.Known
|| fact.Paths.Count != 1
|| !IsSafePath(fact.Paths[0].Value, intentDirectory))
{
return false;
}
}
var root = Directory.CreateTempSubdirectory("netclaw-shell-recheck-");
try
{
var deniedDirectory = Directory.CreateDirectory(Path.Combine(root.FullName, "denied"));
Comment thread src/Netclaw.Actors.Tests/Tools/ShellToolTests.cs Fixed
Comment on lines +152 to +167
foreach (var segment in path.Split(separator, StringSplitOptions.RemoveEmptyEntries))
{
if (segment == ".")
continue;

if (segment == "..")
{
if (segments.Count == 0)
return string.Empty;

segments.RemoveAt(segments.Count - 1);
continue;
}

segments.Add(segment);
}
@Aaronontheweb
Aaronontheweb merged commit 7bf6af2 into dev Aug 14, 2026
23 checks passed
@Aaronontheweb
Aaronontheweb deleted the refactor/shell-policy-path-facts branch August 14, 2026 09:57
Aaronontheweb added a commit that referenced this pull request Aug 20, 2026
* Sync delta specs for the last two completed changes

Apply the redirect-shared-temp-to-session-scratch delta to session-cwd and
tool-approval-gates. The OpenSpec CLI applied both files.

Merge the surface-reminder-schedule-failures delta into netclaw-scheduling by
hand. PR #2024 already corrected that requirement against the merged code, so a
programmatic apply would undo those corrections. Keep the corrected text and add
the scheduling-failure content:

- An execution failure and a scheduling failure share one ConsecutiveFailures
  count.
- A successful reschedule alone does not reset the count.
- An unattended reschedule site emits ReminderScheduleFailed at Warning severity,
  and ReminderAutoDisabled at Critical severity on the threshold.
- The set_reminder create path and update path return the error to the caller
  and emit no alert.

Drop three stale delta claims. The merged code disables a reminder. It does not
set a "paused" status and it does not write tasks.json. ReminderExecutionActor
has no ExecutionTimeoutSeconds constant, so the one-hour absolute limit stands.

* Archive the last two completed OpenSpec changes

Move each change to openspec/changes/archive/2026-08-19-<name>/. The code of
both changes is merged on dev.

- surface-reminder-schedule-failures: PR #1886.
- redirect-shared-temp-to-session-scratch: PRs #1890, #1925, #1926, #1945, and
  #1956.

Tick task 4.3 of redirect-shared-temp-to-session-scratch. The full pr_validation
suite ran green on Windows against current dev.

Leave the other boxes unticked. The maintainer accepts tasks 4.5 and 4.6 of
surface-reminder-schedule-failures as guaranteed by construction. The maintainer
accepts task 6.4 of that change and task 4.4 of
redirect-shared-temp-to-session-scratch as an eval-credentials gap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant