Skip to content

Commit 2d26098

Browse files
committed
Scaffold macOS/Linux sandbox support template
1 parent b932308 commit 2d26098

3 files changed

Lines changed: 54 additions & 0 deletions

File tree

process_worker_factory.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ type ProcessFactory struct {
166166
healthPath string // path to poll for liveness; defaults to "/health"
167167
startTimeout time.Duration // maximum time to wait for the first successful health check
168168
startHealthCheckDelay time.Duration // delay the health check for the first time.
169+
enableSandbox bool // true by default for isolation
169170
counter atomic.Int64
170171
}
171172

@@ -183,6 +184,7 @@ func NewProcessFactory(binary string, args ...string) *ProcessFactory {
183184
healthPath: "/health",
184185
startTimeout: 30 * time.Second,
185186
startHealthCheckDelay: 1 * time.Second,
187+
enableSandbox: true,
186188
}
187189
}
188190

@@ -229,6 +231,14 @@ func (f *ProcessFactory) WithStartHealthCheckDelay(d time.Duration) *ProcessFact
229231
return f
230232
}
231233

234+
// WithInsecureSandbox disables the namespace/cgroup sandbox.
235+
// Use only for local debugging on non-Linux systems or when you explicitly
236+
// trust the spawned processes.
237+
func (f *ProcessFactory) WithInsecureSandbox() *ProcessFactory {
238+
f.enableSandbox = false
239+
return f
240+
}
241+
232242
func streamLogs(workerID string, pipe io.ReadCloser, isError bool) {
233243
// bufio.Scanner guarantees we read line-by-line, preventing torn logs.
234244
scanner := bufio.NewScanner(pipe)
@@ -272,6 +282,14 @@ func (f *ProcessFactory) Spawn(ctx context.Context) (Worker[*http.Client], error
272282
cmd := exec.Command(f.binary, resolvedArgs...)
273283
cmd.Env = append(os.Environ(), append([]string{"PORT=" + portStr}, resolvedEnv...)...)
274284

285+
if f.enableSandbox {
286+
if err := applySandboxFlags(cmd); err != nil {
287+
return nil, fmt.Errorf("herd: ProcessFactory: failed to apply sandbox: %w", err)
288+
}
289+
} else {
290+
log.Printf("[%s] WARNING: running UN-SANDBOXED. Not recommended for production.", id)
291+
}
292+
275293
stdout, err := cmd.StdoutPipe()
276294
if err != nil {
277295
return nil, fmt.Errorf("herd: ProcessFactory: stdout pipe: %w", err)

sandbox_linux.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build linux
2+
3+
package herd
4+
5+
import (
6+
"os/exec"
7+
)
8+
9+
// applySandboxFlags applies Linux-specific sandbox isolation (Namespaces, Cgroups, Seccomp)
10+
// to the given command before it is started.
11+
func applySandboxFlags(cmd *exec.Cmd) error {
12+
// TODO: Phase 1 (Namespaces) - inject syscall.SysProcAttr
13+
// TODO: Phase 2 (Cgroups) - setup and apply cgroup constraints
14+
// TODO: Phase 3 (Seccomp) - load BPF filters
15+
16+
return nil
17+
}

sandbox_unsupported.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//go:build !linux
2+
3+
package herd
4+
5+
import (
6+
"errors"
7+
"os/exec"
8+
"runtime"
9+
)
10+
11+
// ErrSandboxUnsupported is returned when sandbox mode is requested on a non-Linux OS.
12+
var ErrSandboxUnsupported = errors.New("herd: security sandbox relies on Linux cgroups and namespaces, which are not supported on " + runtime.GOOS)
13+
14+
// applySandboxFlags applies Linux-specific sandbox isolation.
15+
// On non-Linux systems, this returns an error if sandbox mode is enabled,
16+
// forcing a loud failure instead of a false sense of security.
17+
func applySandboxFlags(cmd *exec.Cmd) error {
18+
return ErrSandboxUnsupported
19+
}

0 commit comments

Comments
 (0)