Files
DeepSeek-Reasonix/internal/shellsafe/write_paths.go
T
SivanColaandCursor 4588919849 fix(agent): decouple read coverage from write evidence recovery
Problem: Partial reads froze independent operations and final answers, while
rejected edits left stale path blocks after successful read/retry cycles.

Root cause: Read coverage was merged into a global write blocker. Rechecking
old edit arguments and suppressing fresh observations after writes prevented
obsolete operations from reaching a terminal state.

Fix: Track immutable operation/version/range requirements and batch-scoped
verdicts, preserve explicit full-read completion, re-deliver current evidence,
guard source/presence races, and retain host-only recovery diagnostics. Keep
permissions, opaque command boundaries and provider schema bytes unchanged.

Verification: Owning-package tests and race suites, vet, lint/repolint,
frontend typecheck/read-status tests/production budgets, desktop read tests,
and Windows test cross-compilation passed. Full-root migration and inventory
failures also reproduce on the unchanged base; one Goal timeout passed ten
isolated reruns. Native Windows and remote CI are separate gates.

Adapt the real Build regression from PR #9992 and retain its contributor credit.

Co-authored-by: Cursor <[email protected]>
2026-09-10 00:31:44 +08:00

47 lines
1.2 KiB
Go

package shellsafe
import (
"mvdan.cc/sh/v3/syntax"
"reasonix/internal/shellparse"
"strings"
)
// StaticWritePaths proves the complete write surface of a deliberately small
// shell subset. Directory permissions and model-provided scopes are never
// evidence of the actual command's targets. Unrecognized forms stay opaque.
func StaticWritePaths(command string) ([]string, bool) {
f, err := shellparse.ParseBash(command)
if err != nil || len(f.Stmts) != 1 {
return nil, false
}
s := f.Stmts[0]
if s.Background || s.Coprocess || s.Disown || s.Negated {
return nil, false
}
c, ok := s.Cmd.(*syntax.CallExpr)
if !ok || len(c.Assigns) > 0 || len(c.Args) == 0 || len(s.Redirs) == 0 {
return nil, false
}
name, ok := shellparse.StaticWord(c.Args[0])
if !ok || (name != "echo" && name != "printf") {
return nil, false
}
for _, arg := range c.Args {
if _, ok := shellparse.StaticWord(arg); !ok {
return nil, false
}
}
var paths []string
for _, r := range s.Redirs {
if r.Op != syntax.RdrOut && r.Op != syntax.AppOut {
return nil, false
}
path, ok := shellparse.StaticWord(r.Word)
if !ok || path == "" || strings.ContainsAny(path, "*?[{~") {
return nil, false
}
paths = append(paths, path)
}
return paths, true
}