mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 18:00:54 -10:00
* Prompt for shell network permissions Route network-risk shell calls through the existing permission prompt instead of hard-denying them at sandbox preflight. Approved network prompts apply the current turn/session request-permissions overlay so the effective sandbox policy is widened only after user approval. Move network command detection into the shell AST analyzer for package installers, gh/git network subcommands, and python http.server so quoted process patterns such as pkill -f "python3 -m http.server 8000" are not misclassified as network activity. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestAnalyzeCommand|TestClassifyDoesNotFlagQuotedHttpServerPattern|TestEvaluateExemptsNetworkToolsFromDenyByDefault|TestEngineClassifiesNetworkAndDestructiveShellCommands|TestGrantRequestPermissionsNetworkOverlaysPolicyForTurn' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestBash|TestRegistryRunsBashThroughSandboxEngine' Tested: GOCACHE=/tmp/zero-go-cache go test ./... * Auto-allow native-sandboxed shell commands Remove the old hidden bash auto-allow environment/config path. Ordinary shell commands now use the active native sandbox as the permission boundary by default, while network, destructive, and workspace policy checks still run first. Policy-only and disabled sandbox backends continue to require approval for shell commands, so unsandboxed execution is not silently allowed. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestSandboxedBash|TestShellSandboxActive|TestEvaluateExemptsNetworkToolsFromDenyByDefault|TestGrantRequestPermissionsNetworkOverlaysPolicyForTurn|TestAnalyzeCommand|TestClassifyDoesNotFlagQuotedHttpServerPattern' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestBashAuto|TestBashStillPromptsWithoutActiveSandbox|TestRegistryRunsBashThroughSandboxEngine' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/cli -run 'TestApplyConfiguredSandboxPolicy|TestApplyConfiguredAutonomyCeiling' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/daemon -run 'TestScrubWorkerEnvRemovesReentrancyMarkers' Tested: GOCACHE=/tmp/zero-go-cache go test ./... * Prompt for destructive shell approval Remove the separate DenyDestructiveShell policy layer from active sandbox decisions. Destructive shell classification now produces an approval prompt in ask/auto flows instead of a sandbox hard-deny, while no-approval paths remain blocked. Drop the removed destructive-shell policy field from sandbox policy output, backend capabilities, snapshots, and Windows golden data. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPromptsForDestructiveShellInsteadOfSandboxDeny|TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestEngineClassifiesNetworkAndDestructiveShellCommands|TestBackendCapabilitiesReflectDisabledPolicy' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/cli -run 'TestRunSandboxPolicy|TestEffectiveSandboxPolicy' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/zerocommands -run 'TestSandboxPolicySnapshotFromPolicyFillsEffectiveMode' Tested: GOCACHE=/tmp/zero-go-cache go test ./... * Remove web-tool network policy switch Remove the old EnforceToolNetwork policy option and the NetworkHostAllowed per-tool gate so sandbox network policy only controls sandboxed shell egress. Keep web_search and web_fetch on their own tool permission and URL safety paths, with tests proving deny/scoped shell network policy no longer blocks in-process web tools. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestEvaluateExemptsNetworkToolsFromShellNetworkPolicy|TestEngineAllowsNetworkSideEffectWhenShellPolicyBlocksNetwork|TestGrantRequestPermissionsNetworkOverlaysPolicyForTurn|TestScopedNetworkGateInEvaluate' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestWebSearchRunWithSandbox|TestWebFetchRunWithSandbox|TestRegistryRoutesWebFetchThroughSandboxPath' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant' Tested: GOCACHE=/tmp/zero-go-cache go test ./... Tested: git diff --check * Prompt for outside-workspace file access Convert recoverable outside-workspace file path violations into permission prompts instead of hard-denying in ask/auto modes. Approved prompts now grant the requested filesystem scope through the existing request_permissions overlay for the current turn or session, so the follow-up sandbox evaluation and scoped file tools use the same policy path. Keep unsafe mode, apply_patch escapes, symlink traversal, and non-recoverable path cases hard-denied. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestEngineAutoAllowsWorkspaceFileMutationTools|TestEngineDeniesOutOfWorkspacePaths|TestGrantRequestPermissionsReadDoesNotGrantWrite|TestGrantRequestPermissionsSessionPersists' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPromptsAndAllowsOutsideWorkspaceWrite|TestRunSessionAllowsLaterOutsideWorkspaceWrite|TestRunAppliesSandboxEvenInUnsafeMode' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestRegistryAppliesSandboxBeforeToolExecution|TestRegistrySandboxGatesPathAliasKeys|TestRegistryAllowsPromptToolWithPersistentSandboxGrant' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/cli -run 'TestRunSandboxCheckJSONDeniesOutOfWorkspaceWrite' Tested: GOCACHE=/tmp/zero-go-cache go test ./... Tested: git diff --check * Fix Windows network prompt test fixture Use a Windows curl.cmd shim and cmd.exe PATH syntax for TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant while keeping the POSIX fake curl fixture for Linux and macOS. This keeps the test focused on network prompt approval and turn network grants instead of relying on a POSIX shell script being executable on Windows. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant|TestRunPersistentCommandPrefixStillPromptsForNetwork' Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestClassifyASTCatchesNetworkProgramsRegexMisses|TestClassifyFlagsPipedInstallerVariants' Tested: GOCACHE=/tmp/zero-go-cache go test ./... Tested: git diff --check * Remove sandbox autonomy ceilings Sandbox permission decisions now match grants by tool and scope only, without MaxAutonomy or request autonomy checks. Persistent/session grants, permission approvals, workspace-write auto-allow, and native-sandbox shell auto-allow no longer route through an autonomy ceiling. Remove the sandbox.maxAutonomy config and CLI surfaces, including grant --auto, policy output, sandbox check autonomy input, and sandbox grant snapshots. MCP permission autonomy is intentionally unchanged because it is a separate MCP permission model. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/agent ./internal/tools ./internal/zerocommands ./internal/config ./internal/cli ./internal/tui Tested: GOCACHE=/tmp/zero-go-cache go test ./... * Fail closed without native sandbox Remove AllowPolicyOnlyRunner from sandbox policy and snapshots. A restricted/managed command plan now errors when native sandbox support is unavailable instead of running through a policy-only direct runner. Policy-only backend data remains as diagnostics for capability/reporting paths, but it no longer authorizes command execution when a platform sandbox is required. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/zerocommands ./internal/cli ./internal/tui Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent ./internal/tools Tested: GOCACHE=/tmp/zero-go-cache go test ./... * Remove scoped shell network policy Drop the domain-scoped shell network mode and the local scoped-egress proxy path from the active sandbox model. Shell network policy now serializes and evaluates only deny or allow. Request-permissions network grants widen to allow without domain lists, and platform profiles consume the binary mode directly. The Linux helper no longer accepts proxy-route flags, Windows network hashes only the binary mode, and web tool tests assert they remain separate from shell network policy. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/tools ./internal/notify Tested: GOCACHE=/tmp/zero-go-cache go test ./... * Align permission grant ordering and prompt text Move persistent and session grant matching ahead of recoverable sandbox prompts while keeping explicit denies and non-recoverable path violations fail-closed. Clean TUI permission transcript/card text so normal prompts show action, reason, scope, and readable block details instead of raw permission/risk/violation key-value metadata. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/tui ./internal/agent ./internal/tools Tested: GOCACHE=/tmp/zero-go-cache go test ./... * Remove policy-only sandbox terminology Replace the unavailable native-backend diagnostic path with explicit unavailable support/backend states, and stop presenting missing native isolation as a policy-only fallback. Rename structured sandbox denial metadata from violation to block across permission events, stream-json, snapshots, TUI rendering, and sandbox-check output so user-facing flow matches blocked action language. Update README, work split docs, smoke test selectors, and Windows sandbox policy golden output for the new backend/support names and block payloads. Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/agent ./internal/tools ./internal/tui ./internal/cli ./internal/doctor ./internal/zerocommands ./internal/streamjson Tested: GOCACHE=/tmp/zero-go-cache go test ./... Tested: scripts/sandbox-smoke.sh Tested: git diff --check * Fix sandbox review follow-ups Fail closed for unknown network modes, keep network prompts ahead of bash allow grants, preserve v1 grant-file reads, and classify unparseable commands with obvious network programs as network-risk. Bump stream-json schema to v2 and document the block metadata field after the violation-to-block rename. Tests: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/streamjson ./internal/specialist ./internal/cli -run 'TestExec.*Stream|TestStreamJSON|TestExecProtocol|TestExecReadsStreamJSONImages|TestExecAcceptsImageOnlyStreamJSONMessage|TestExecInputFormatStreamJSON' Tests: GOCACHE=/tmp/zero-go-cache go test ./... Tests: scripts/sandbox-smoke.sh Tests: git diff --check
420 lines
12 KiB
Go
420 lines
12 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type PermissionGrantScope string
|
|
|
|
const (
|
|
PermissionGrantScopeTurn PermissionGrantScope = "turn"
|
|
PermissionGrantScopeSession PermissionGrantScope = "session"
|
|
)
|
|
|
|
type RequestPermissionProfile struct {
|
|
Network *NetworkPermissions `json:"network,omitempty"`
|
|
FileSystem *FileSystemPermissions `json:"file_system,omitempty"`
|
|
}
|
|
|
|
type RequestPermissionsResponse struct {
|
|
Permissions RequestPermissionProfile `json:"permissions"`
|
|
Scope PermissionGrantScope `json:"scope"`
|
|
StrictAutoReview bool `json:"strict_auto_review,omitempty"`
|
|
}
|
|
|
|
type NetworkPermissions struct {
|
|
Enabled *bool `json:"enabled,omitempty"`
|
|
}
|
|
|
|
type FileSystemPermissions struct {
|
|
Read []string `json:"read,omitempty"`
|
|
Write []string `json:"write,omitempty"`
|
|
DenyRead []string `json:"deny_read,omitempty"`
|
|
GlobScanMaxDepth *int `json:"glob_scan_max_depth,omitempty"`
|
|
Entries []FileSystemSandboxEntry `json:"entries,omitempty"`
|
|
}
|
|
|
|
type FileSystemSandboxEntry struct {
|
|
Path FileSystemPath `json:"path"`
|
|
Access FileSystemAccessMode `json:"access"`
|
|
}
|
|
|
|
type FileSystemAccessMode string
|
|
|
|
const (
|
|
FileSystemAccessRead FileSystemAccessMode = "read"
|
|
FileSystemAccessWrite FileSystemAccessMode = "write"
|
|
FileSystemAccessDeny FileSystemAccessMode = "deny"
|
|
)
|
|
|
|
type FileSystemPath struct {
|
|
Kind string `json:"kind,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
Pattern string `json:"pattern,omitempty"`
|
|
}
|
|
|
|
func (path *FileSystemPath) UnmarshalJSON(data []byte) error {
|
|
var rawString string
|
|
if err := json.Unmarshal(data, &rawString); err == nil {
|
|
path.Kind = "path"
|
|
path.Type = "path"
|
|
path.Path = rawString
|
|
return nil
|
|
}
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return err
|
|
}
|
|
_ = json.Unmarshal(raw["kind"], &path.Kind)
|
|
_ = json.Unmarshal(raw["type"], &path.Type)
|
|
_ = json.Unmarshal(raw["path"], &path.Path)
|
|
_ = json.Unmarshal(raw["pattern"], &path.Pattern)
|
|
return nil
|
|
}
|
|
|
|
func (profile RequestPermissionProfile) Empty() bool {
|
|
networkEmpty := profile.Network == nil || profile.Network.Enabled == nil || !*profile.Network.Enabled
|
|
fs := profile.FileSystem
|
|
fileSystemEmpty := fs == nil ||
|
|
(len(fs.Read) == 0 && len(fs.Write) == 0 && len(fs.DenyRead) == 0 && len(fs.Entries) == 0)
|
|
return networkEmpty && fileSystemEmpty
|
|
}
|
|
|
|
func NormalizeRequestPermissionProfile(profile RequestPermissionProfile, basePath string) (RequestPermissionProfile, error) {
|
|
var normalized RequestPermissionProfile
|
|
if profile.Network != nil && profile.Network.Enabled != nil && *profile.Network.Enabled {
|
|
enabled := true
|
|
normalized.Network = &NetworkPermissions{Enabled: &enabled}
|
|
}
|
|
if profile.FileSystem != nil {
|
|
fs, err := normalizeFileSystemPermissions(*profile.FileSystem, basePath)
|
|
if err != nil {
|
|
return RequestPermissionProfile{}, err
|
|
}
|
|
if len(fs.Read) > 0 || len(fs.Write) > 0 || len(fs.DenyRead) > 0 {
|
|
normalized.FileSystem = &fs
|
|
}
|
|
}
|
|
return normalized, nil
|
|
}
|
|
|
|
// RequestPermissionGrantProfile returns the effective grant profile the engine
|
|
// can enforce. File-like read/write requests become their normalized parent
|
|
// directory roots, matching GrantRequestPermissions and native sandbox profiles.
|
|
func RequestPermissionGrantProfile(profile RequestPermissionProfile) (RequestPermissionProfile, error) {
|
|
var grant RequestPermissionProfile
|
|
if profile.Network != nil && profile.Network.Enabled != nil && *profile.Network.Enabled {
|
|
enabled := true
|
|
grant.Network = &NetworkPermissions{Enabled: &enabled}
|
|
}
|
|
if profile.FileSystem != nil {
|
|
read, err := grantPermissionRoots(profile.FileSystem.Read)
|
|
if err != nil {
|
|
return RequestPermissionProfile{}, err
|
|
}
|
|
write, err := grantPermissionRoots(profile.FileSystem.Write)
|
|
if err != nil {
|
|
return RequestPermissionProfile{}, err
|
|
}
|
|
fs := FileSystemPermissions{
|
|
Read: read,
|
|
Write: write,
|
|
DenyRead: append([]string{}, profile.FileSystem.DenyRead...),
|
|
}
|
|
if len(fs.Read) > 0 || len(fs.Write) > 0 || len(fs.DenyRead) > 0 {
|
|
grant.FileSystem = &fs
|
|
}
|
|
}
|
|
return grant, nil
|
|
}
|
|
|
|
func grantPermissionRoots(paths []string) ([]string, error) {
|
|
roots := make([]string, 0, len(paths))
|
|
for _, path := range paths {
|
|
if strings.TrimSpace(path) == "" {
|
|
continue
|
|
}
|
|
root, err := normalizeScopeRoot(permissionRoot(path))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
roots = append(roots, root)
|
|
}
|
|
return dedupeStrings(roots), nil
|
|
}
|
|
|
|
func normalizeFileSystemPermissions(fs FileSystemPermissions, basePath string) (FileSystemPermissions, error) {
|
|
var out FileSystemPermissions
|
|
var err error
|
|
if out.Read, err = normalizePermissionPaths(fs.Read, basePath); err != nil {
|
|
return FileSystemPermissions{}, err
|
|
}
|
|
if out.Write, err = normalizePermissionPaths(fs.Write, basePath); err != nil {
|
|
return FileSystemPermissions{}, err
|
|
}
|
|
if out.DenyRead, err = normalizePermissionPaths(fs.DenyRead, basePath); err != nil {
|
|
return FileSystemPermissions{}, err
|
|
}
|
|
for _, entry := range fs.Entries {
|
|
path, ok := entry.Path.pathString()
|
|
if !ok {
|
|
continue
|
|
}
|
|
normalized, err := normalizePermissionPath(path, basePath)
|
|
if err != nil {
|
|
return FileSystemPermissions{}, err
|
|
}
|
|
switch normalizeFileSystemAccess(entry.Access) {
|
|
case FileSystemAccessRead:
|
|
out.Read = append(out.Read, normalized)
|
|
case FileSystemAccessWrite:
|
|
out.Write = append(out.Write, normalized)
|
|
case FileSystemAccessDeny:
|
|
out.DenyRead = append(out.DenyRead, normalized)
|
|
}
|
|
}
|
|
out.Read = dedupeStrings(out.Read)
|
|
out.Write = dedupeStrings(out.Write)
|
|
out.DenyRead = dedupeStrings(out.DenyRead)
|
|
return out, nil
|
|
}
|
|
|
|
func (path FileSystemPath) pathString() (string, bool) {
|
|
kind := strings.TrimSpace(firstNonEmpty(path.Type, path.Kind))
|
|
if kind == "" || kind == "path" {
|
|
value := strings.TrimSpace(path.Path)
|
|
return value, value != ""
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func normalizeFileSystemAccess(access FileSystemAccessMode) FileSystemAccessMode {
|
|
switch strings.ToLower(strings.TrimSpace(string(access))) {
|
|
case "read":
|
|
return FileSystemAccessRead
|
|
case "write":
|
|
return FileSystemAccessWrite
|
|
case "deny", "none":
|
|
return FileSystemAccessDeny
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func normalizePermissionPaths(paths []string, basePath string) ([]string, error) {
|
|
out := make([]string, 0, len(paths))
|
|
for _, path := range paths {
|
|
normalized, err := normalizePermissionPath(path, basePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if normalized != "" {
|
|
out = append(out, normalized)
|
|
}
|
|
}
|
|
return dedupeStrings(out), nil
|
|
}
|
|
|
|
func normalizePermissionPath(path string, basePath string) (string, error) {
|
|
trimmed := strings.TrimSpace(path)
|
|
if trimmed == "" {
|
|
return "", nil
|
|
}
|
|
if !filepath.IsAbs(trimmed) {
|
|
base := strings.TrimSpace(basePath)
|
|
if base == "" {
|
|
var err error
|
|
base, err = os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
trimmed = filepath.Join(base, trimmed)
|
|
}
|
|
absolute, err := filepath.Abs(trimmed)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if resolved, err := filepath.EvalSymlinks(absolute); err == nil {
|
|
return filepath.Clean(resolved), nil
|
|
}
|
|
return filepath.Clean(absolute), nil
|
|
}
|
|
|
|
func (engine *Engine) GrantRequestPermissions(profile RequestPermissionProfile, scope PermissionGrantScope) (func(), error) {
|
|
if engine == nil {
|
|
return nil, errors.New("sandbox engine is not configured")
|
|
}
|
|
if scope == "" {
|
|
scope = PermissionGrantScopeTurn
|
|
}
|
|
var cleanups []func()
|
|
cleanup := func() {
|
|
for i := len(cleanups) - 1; i >= 0; i-- {
|
|
cleanups[i]()
|
|
}
|
|
}
|
|
if engine.scope != nil && profile.FileSystem != nil {
|
|
for _, path := range profile.FileSystem.Read {
|
|
root := permissionRoot(path)
|
|
var undo func()
|
|
var err error
|
|
if scope == PermissionGrantScopeSession {
|
|
_, err = engine.scope.AddRead(root)
|
|
} else {
|
|
_, undo, err = engine.scope.AddTemporaryRead(root)
|
|
if undo != nil {
|
|
cleanups = append(cleanups, undo)
|
|
}
|
|
}
|
|
if err != nil {
|
|
cleanup()
|
|
return nil, fmt.Errorf("grant read permission %s: %w", path, err)
|
|
}
|
|
}
|
|
for _, path := range profile.FileSystem.Write {
|
|
root := permissionRoot(path)
|
|
var undo func()
|
|
var err error
|
|
if scope == PermissionGrantScopeSession {
|
|
_, err = engine.scope.Add(root)
|
|
} else {
|
|
_, undo, err = engine.scope.AddTemporaryWrite(root)
|
|
if undo != nil {
|
|
cleanups = append(cleanups, undo)
|
|
}
|
|
}
|
|
if err != nil {
|
|
cleanup()
|
|
return nil, fmt.Errorf("grant write permission %s: %w", path, err)
|
|
}
|
|
}
|
|
}
|
|
switch scope {
|
|
case PermissionGrantScopeSession:
|
|
engine.sessionProfiles.add(profile)
|
|
return func() {}, nil
|
|
case PermissionGrantScopeTurn:
|
|
remove := engine.turnProfiles.add(profile)
|
|
cleanups = append(cleanups, remove)
|
|
return cleanup, nil
|
|
default:
|
|
cleanup()
|
|
return nil, fmt.Errorf("unsupported permission grant scope %q", scope)
|
|
}
|
|
}
|
|
|
|
func permissionRoot(path string) string {
|
|
clean := filepath.Clean(path)
|
|
if info, err := os.Stat(clean); err == nil && info.IsDir() {
|
|
return clean
|
|
}
|
|
return filepath.Dir(clean)
|
|
}
|
|
|
|
func (engine *Engine) effectivePolicy(policy Policy) Policy {
|
|
if engine == nil {
|
|
return policy
|
|
}
|
|
for _, profile := range engine.sessionProfiles.list() {
|
|
policy = applyRequestPermissionProfile(policy, profile)
|
|
}
|
|
for _, profile := range engine.turnProfiles.list() {
|
|
policy = applyRequestPermissionProfile(policy, profile)
|
|
}
|
|
return policy
|
|
}
|
|
|
|
func applyRequestPermissionProfile(policy Policy, profile RequestPermissionProfile) Policy {
|
|
if profile.Network != nil && profile.Network.Enabled != nil && *profile.Network.Enabled {
|
|
policy.Network = NetworkAllow
|
|
}
|
|
if profile.FileSystem != nil {
|
|
policy.AllowRead = dedupeStrings(append(policy.AllowRead, profile.FileSystem.Read...))
|
|
policy.AllowWrite = dedupeStrings(append(policy.AllowWrite, profile.FileSystem.Write...))
|
|
policy.DenyRead = dedupeStrings(append(policy.DenyRead, profile.FileSystem.DenyRead...))
|
|
}
|
|
return policy
|
|
}
|
|
|
|
type memoryGrantSet struct {
|
|
mu sync.Mutex
|
|
grants map[string][]Grant
|
|
}
|
|
|
|
func newMemoryGrantSet() *memoryGrantSet {
|
|
return &memoryGrantSet{grants: map[string][]Grant{}}
|
|
}
|
|
|
|
func (set *memoryGrantSet) add(grant Grant) {
|
|
if set == nil {
|
|
return
|
|
}
|
|
set.mu.Lock()
|
|
defer set.mu.Unlock()
|
|
bucket := set.grants[grant.ToolName]
|
|
for index := range bucket {
|
|
if bucket[index].Scope == grant.Scope && bucket[index].ScopeKind == grant.ScopeKind {
|
|
bucket[index] = grant
|
|
set.grants[grant.ToolName] = bucket
|
|
return
|
|
}
|
|
}
|
|
set.grants[grant.ToolName] = append(bucket, grant)
|
|
}
|
|
|
|
func (set *memoryGrantSet) lookup(toolName string, reqScope string) GrantLookup {
|
|
if set == nil {
|
|
return GrantLookup{}
|
|
}
|
|
set.mu.Lock()
|
|
defer set.mu.Unlock()
|
|
return lookupGrantBucket(set.grants[strings.TrimSpace(toolName)], reqScope)
|
|
}
|
|
|
|
type permissionProfileGrantSet struct {
|
|
mu sync.Mutex
|
|
nextID int
|
|
profiles map[int]RequestPermissionProfile
|
|
}
|
|
|
|
func newPermissionProfileGrantSet() *permissionProfileGrantSet {
|
|
return &permissionProfileGrantSet{profiles: map[int]RequestPermissionProfile{}}
|
|
}
|
|
|
|
func (set *permissionProfileGrantSet) add(profile RequestPermissionProfile) func() {
|
|
if set == nil {
|
|
return func() {}
|
|
}
|
|
set.mu.Lock()
|
|
set.nextID++
|
|
id := set.nextID
|
|
set.profiles[id] = profile
|
|
set.mu.Unlock()
|
|
return func() {
|
|
set.mu.Lock()
|
|
delete(set.profiles, id)
|
|
set.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
func (set *permissionProfileGrantSet) list() []RequestPermissionProfile {
|
|
if set == nil {
|
|
return nil
|
|
}
|
|
set.mu.Lock()
|
|
defer set.mu.Unlock()
|
|
out := make([]RequestPermissionProfile, 0, len(set.profiles))
|
|
for _, profile := range set.profiles {
|
|
out = append(out, profile)
|
|
}
|
|
return out
|
|
}
|