返回 DeepSeek-Reasonix
bash_readonly.go
根目录 / internal / permission / bash_readonly.go
1 package permission
2
3 import (
4 "encoding/json"
5 "strings"
6
7 "reasonix/internal/shellsafe"
8 )
9
10 // BashCommandIsReadOnly reports whether a bash tool call is a known foreground
11 // read-only command. Capability-restricted runners use this directly instead of
12 // depending on Plan mode: Plan is a collaboration workflow, while this check is
13 // an execution permission boundary.
14 func BashCommandIsReadOnly(args json.RawMessage) bool {
15 var p struct {
16 Command string `json:"command"`
17 RunInBackground bool `json:"run_in_background"`
18 PreserveBackgroundProcesses bool `json:"preserve_background_processes"`
19 }
20 if err := json.Unmarshal(args, &p); err != nil || strings.TrimSpace(p.Command) == "" {
21 return false
22 }
23 if p.RunInBackground || p.PreserveBackgroundProcesses {
24 return false
25 }
26 return isReadOnlyBashSubject(p.Command)
27 }
28
29 // isReadOnlyBashSubject returns true when a bash command is a known read-only
30 // operation. The subject is the JSON arg value extracted by Subject() — for bash
31 // it is the raw command string. Both command membership and argument effects
32 // come from shellsafe so permission and mutation accounting cannot drift.
33 func isReadOnlyBashSubject(subject string) bool {
34 return shellsafe.ClassifyBash(subject).IsPermissionReader()
35 }
36
37 // containsShellSyntax delegates to the shared classifier; retained for the other
38 // permission call sites (permission.go).
39 func containsShellSyntax(cmd string) bool {
40 return shellsafe.ContainsShellSyntax(cmd)
41 }
42
43 // dangerousBashPatterns are glob-like patterns that match destructive
44 // commands. Used only for a UI warning — the deny list is the actual
45 // enforcement mechanism.
46 var dangerousBashPatterns = []struct {
47 pattern string
48 label string
49 }{
50 {"rm -rf*", "recursive delete"},
51 {"rm -r *", "recursive delete"},
52 {"rm -fr*", "recursive delete"},
53 {"git push*--force*", "force push"},
54 {"git push*-f*", "force push"},
55 {"git reset --hard*", "hard reset"},
56 {"git clean -f*", "force clean"},
57 {"git restore*", "discards uncommitted changes"},
58 {"git checkout -- *", "discards uncommitted changes"},
59 {"git checkout .*", "discards uncommitted changes"},
60 {"git stash drop*", "drops stashed changes"},
61 {"git stash clear*", "drops stashed changes"},
62 {"chmod 777*", "world-writable"},
63 {"chmod -R 777*", "world-writable recursive"},
64 {"chown *", "ownership change"},
65 {"sudo *", "superuser"},
66 {"mkfs*", "filesystem format"},
67 {"dd if=*", "raw device write"},
68 {"fdisk*", "partition table"},
69 {"> /dev/*", "device overwrite"},
70 }
71
72 // BashDangerWarning returns a short label if subject matches a known
73 // dangerous pattern, or "" when the command looks safe. This is a visual
74 // hint only — the Policy rules are the authority.
75 func BashDangerWarning(subject string) string {
76 s := strings.TrimSpace(subject)
77 for _, d := range dangerousBashPatterns {
78 if matchGlob(d.pattern, s) {
79 return d.label
80 }
81 }
82 return ""
83 }
84
84 lines GO