返回 DeepSeek-Reasonix
denial_ledger.go
根目录 / internal / sandbox / denial_ledger.go
1 package sandbox
2
3 import (
4 "crypto/rand"
5 "crypto/sha256"
6 "encoding/hex"
7 "strings"
8 "sync"
9 "time"
10 )
11
12 const denialLifetime = 10 * time.Minute
13
14 type denialRecord struct {
15 commandHash [32]byte
16 preset string
17 expires time.Time
18 }
19
20 var sandboxDenials = struct {
21 sync.Mutex
22 records map[string]denialRecord
23 }{records: make(map[string]denialRecord)}
24
25 // IssueDenial records a concrete sandbox failure and returns an opaque,
26 // short-lived single-use identifier. The identifier is safe to show in tool
27 // output; it carries no path or command content.
28 func IssueDenial(command, preset string) string {
29 command = strings.TrimSpace(command)
30 if command == "" {
31 return ""
32 }
33 random := make([]byte, 16)
34 if _, err := rand.Read(random); err != nil {
35 return ""
36 }
37 id := hex.EncodeToString(random)
38 now := time.Now()
39 sandboxDenials.Lock()
40 for key, record := range sandboxDenials.records {
41 if now.After(record.expires) {
42 delete(sandboxDenials.records, key)
43 }
44 }
45 sandboxDenials.records[id] = denialRecord{
46 commandHash: sha256.Sum256([]byte(command)),
47 preset: strings.TrimSpace(preset),
48 expires: now.Add(denialLifetime),
49 }
50 sandboxDenials.Unlock()
51 return id
52 }
53
54 // ConsumeDenial validates and consumes a denial for an exact command. A token
55 // cannot be replayed for a different command or after it expires.
56 func ConsumeDenial(id, command string) bool {
57 id = strings.TrimSpace(id)
58 command = strings.TrimSpace(command)
59 if id == "" || command == "" {
60 return false
61 }
62 now := time.Now()
63 sandboxDenials.Lock()
64 defer sandboxDenials.Unlock()
65 record, ok := sandboxDenials.records[id]
66 delete(sandboxDenials.records, id)
67 return ok && now.Before(record.expires) && record.commandHash == sha256.Sum256([]byte(command)) && record.preset != "danger-full-access"
68 }
69
69 lines GO