Skip to content

Commit 37daf23

Browse files
authored
feat: implement network and resource enforcement in policy engine (#5)
1 parent 207e5da commit 37daf23

3 files changed

Lines changed: 104 additions & 4 deletions

File tree

ROADMAP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ This roadmap tracks the progress of the Sandforge Agent Sandbox based on [ARCHIT
99
- [x] **1.2 Core API Contracts**: Define `SandboxSpec`, `ExecRequest`, and `SandboxBackend` interfaces.
1010
- [ ] **1.3 Policy Engine**:
1111
- [x] Filesystem path validation (whitelist logic) [#1](https://github.com/yanurag-dev/sandforge/issues/1).
12-
- [ ] Network mode enforcement (Offline/Fetch/Full) [#2](https://github.com/yanurag-dev/sandforge/issues/2).
13-
- [ ] Resource limit validation (CPU/Memory/Disk) [#2](https://github.com/yanurag-dev/sandforge/issues/2).
12+
- [x] Network mode enforcement (Offline/Fetch/Full) [#2](https://github.com/yanurag-dev/sandforge/issues/2).
13+
- [x] Resource limit validation (CPU/Memory/Disk) [#2](https://github.com/yanurag-dev/sandforge/issues/2).
1414
- [ ] Command family filtering [#3](https://github.com/yanurag-dev/sandforge/issues/3).
1515
- [ ] **1.4 Testing**: Unit tests for policy enforcement.
1616

internal/policy/engine.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ import (
99
)
1010

1111
var (
12-
ErrForbiddenHostPath = errors.New("requested host path is forbidden by policy")
13-
ErrPathNotAbs = errors.New("host path must be an absolute path")
12+
ErrForbiddenHostPath = errors.New("requested host path is forbidden by policy")
13+
ErrPathNotAbs = errors.New("host path must be an absolute path")
14+
ErrResourceLimitExceeded = errors.New("requested resource exceeds policy limits")
15+
ErrInvalidNetworkMode = errors.New("requested network mode is not allowed")
1416
)
1517

1618
type Engine struct {
1719
AllowedHostPrefixes []string
1820
BlockedHostPatterns []string
21+
MaxCPU int
22+
MaxMemoryMb int
23+
MaxDiskGb int
24+
AllowedNetworkModes []string
1925
}
2026

2127
func (e *Engine) EvaluateMount(mount api.WorkspaceMount) error {
@@ -59,3 +65,28 @@ func (e *Engine) EvaluateMount(mount api.WorkspaceMount) error {
5965
}
6066
return nil
6167
}
68+
69+
func (e *Engine) EvaluateSandbox(spec api.SandboxSpec) error {
70+
if spec.CPU > e.MaxCPU {
71+
return ErrResourceLimitExceeded
72+
}
73+
if spec.MemoryMb > e.MaxMemoryMb {
74+
return ErrResourceLimitExceeded
75+
}
76+
if spec.DiskGb > e.MaxDiskGb {
77+
return ErrResourceLimitExceeded
78+
}
79+
80+
allowed := false
81+
for _, mode := range e.AllowedNetworkModes {
82+
if spec.NetworkMode == mode {
83+
allowed = true
84+
break
85+
}
86+
}
87+
88+
if !allowed {
89+
return ErrInvalidNetworkMode
90+
}
91+
return nil
92+
}

internal/policy/engine_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,72 @@ func TestEvaluateMount(t *testing.T) {
115115
})
116116
}
117117
}
118+
119+
func TestEvaluateSandbox(t *testing.T) {
120+
engine := &Engine{
121+
MaxCPU: 2,
122+
MaxMemoryMb: 2048,
123+
MaxDiskGb: 10,
124+
AllowedNetworkModes: []string{"offline", "fetch"},
125+
}
126+
127+
tests := []struct {
128+
name string
129+
spec api.SandboxSpec
130+
wantError error
131+
}{
132+
{
133+
name: "Valid spec",
134+
spec: api.SandboxSpec{
135+
CPU: 1,
136+
MemoryMb: 1024,
137+
DiskGb: 5,
138+
NetworkMode: "offline",
139+
},
140+
wantError: nil,
141+
},
142+
{
143+
name: "CPU limit exceeded",
144+
spec: api.SandboxSpec{
145+
CPU: 4,
146+
},
147+
wantError: ErrResourceLimitExceeded,
148+
},
149+
{
150+
name: "Memory limit exceeded",
151+
spec: api.SandboxSpec{
152+
CPU: 1,
153+
MemoryMb: 4096,
154+
},
155+
wantError: ErrResourceLimitExceeded,
156+
},
157+
{
158+
name: "Disk limit exceeded",
159+
spec: api.SandboxSpec{
160+
CPU: 1,
161+
MemoryMb: 1024,
162+
DiskGb: 20,
163+
},
164+
wantError: ErrResourceLimitExceeded,
165+
},
166+
{
167+
name: "Forbidden network mode",
168+
spec: api.SandboxSpec{
169+
CPU: 1,
170+
MemoryMb: 1024,
171+
DiskGb: 5,
172+
NetworkMode: "full",
173+
},
174+
wantError: ErrInvalidNetworkMode,
175+
},
176+
}
177+
178+
for _, tt := range tests {
179+
t.Run(tt.name, func(t *testing.T) {
180+
err := engine.EvaluateSandbox(tt.spec)
181+
if err != tt.wantError {
182+
t.Errorf("EvaluateSandbox() error = %v, wantError %v", err, tt.wantError)
183+
}
184+
})
185+
}
186+
}

0 commit comments

Comments
 (0)