Add environment sanitization
This commit is contained in:
@@ -1,23 +1,28 @@
|
||||
# Architecture
|
||||
|
||||
Fence restricts network and filesystem access for arbitrary commands. It works by:
|
||||
Fence restricts network, filesystem, and command access for arbitrary commands. It works by:
|
||||
|
||||
1. **Intercepting network traffic** via HTTP/SOCKS5 proxies that filter by domain
|
||||
2. **Sandboxing processes** using OS-native mechanisms (macOS sandbox-exec, Linux bubblewrap)
|
||||
3. **Bridging connections** to allow controlled inbound/outbound traffic in isolated namespaces
|
||||
1. **Blocking commands** via configurable deny/allow lists before execution
|
||||
2. **Intercepting network traffic** via HTTP/SOCKS5 proxies that filter by domain
|
||||
3. **Sandboxing processes** using OS-native mechanisms (macOS sandbox-exec, Linux bubblewrap)
|
||||
4. **Sanitizing environment** by stripping dangerous variables (LD_PRELOAD, DYLD_INSERT_LIBRARIES, etc.)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Fence
|
||||
Config["Config<br/>(JSON)"]
|
||||
Manager
|
||||
CmdCheck["Command<br/>Blocking"]
|
||||
EnvSanitize["Env<br/>Sanitization"]
|
||||
Sandbox["Platform Sandbox<br/>(macOS/Linux)"]
|
||||
HTTP["HTTP Proxy<br/>(filtering)"]
|
||||
SOCKS["SOCKS5 Proxy<br/>(filtering)"]
|
||||
end
|
||||
|
||||
Config --> Manager
|
||||
Manager --> Sandbox
|
||||
Manager --> CmdCheck
|
||||
CmdCheck --> EnvSanitize
|
||||
EnvSanitize --> Sandbox
|
||||
Manager --> HTTP
|
||||
Manager --> SOCKS
|
||||
```
|
||||
@@ -42,6 +47,8 @@ fence/
|
||||
│ ├── linux_features.go # Kernel feature detection
|
||||
│ ├── linux_*_stub.go # Non-Linux build stubs
|
||||
│ ├── monitor.go # macOS log stream violation monitoring
|
||||
│ ├── command.go # Command blocking/allow lists
|
||||
│ ├── hardening.go # Environment sanitization
|
||||
│ ├── dangerous.go # Protected file/directory lists
|
||||
│ ├── shell.go # Shell quoting utilities
|
||||
│ └── utils.go # Path normalization
|
||||
@@ -59,12 +66,13 @@ Handles loading and validating sandbox configuration:
|
||||
type Config struct {
|
||||
Network NetworkConfig // Domain allow/deny lists
|
||||
Filesystem FilesystemConfig // Read/write restrictions
|
||||
Command CommandConfig // Command deny/allow lists
|
||||
AllowPty bool // Allow pseudo-terminal allocation
|
||||
}
|
||||
```
|
||||
|
||||
- Loads from `~/.fence.json` or custom path
|
||||
- Falls back to restrictive defaults (block all network)
|
||||
- Falls back to restrictive defaults (block all network, default command deny list)
|
||||
- Validates paths and normalizes them
|
||||
|
||||
### Platform (`internal/platform/`)
|
||||
@@ -108,8 +116,27 @@ Orchestrates the sandbox lifecycle:
|
||||
|
||||
1. Initializes HTTP and SOCKS proxies
|
||||
2. Sets up platform-specific bridges (Linux)
|
||||
3. Wraps commands with sandbox restrictions
|
||||
4. Handles cleanup on exit
|
||||
3. Checks command against deny/allow lists
|
||||
4. Wraps commands with sandbox restrictions
|
||||
5. Handles cleanup on exit
|
||||
|
||||
#### Command Blocking (`command.go`)
|
||||
|
||||
Blocks commands before they run based on configurable policies:
|
||||
|
||||
- **Default deny list**: Dangerous system commands (`shutdown`, `reboot`, `mkfs`, `rm -rf`, etc.)
|
||||
- **Custom deny/allow**: User-configured prefixes (e.g., `git push`, `npm publish`)
|
||||
- **Chain detection**: Parses `&&`, `||`, `;`, `|` to catch blocked commands in pipelines
|
||||
- **Nested shells**: Detects `bash -c "blocked_cmd"` patterns
|
||||
|
||||
#### Environment Sanitization (`hardening.go`)
|
||||
|
||||
Strips dangerous environment variables before command execution:
|
||||
|
||||
- Linux: `LD_PRELOAD`, `LD_LIBRARY_PATH`, `LD_AUDIT`, etc.
|
||||
- macOS: `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH`, etc.
|
||||
|
||||
This prevents library injection attacks where a sandboxed process writes a malicious `.so`/`.dylib` and uses `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` in a subsequent command.
|
||||
|
||||
#### macOS Implementation (`macos.go`)
|
||||
|
||||
@@ -229,15 +256,18 @@ flowchart TD
|
||||
|
||||
D1 & D2 & D3 & D4 --> E["5. Manager.WrapCommand()"]
|
||||
|
||||
E --> E1["[macOS] Generate Seatbelt profile"]
|
||||
E --> E2["[Linux] Generate bwrap command"]
|
||||
E --> E0{"Check command<br/>deny/allow lists"}
|
||||
E0 -->|blocked| ERR["Return error"]
|
||||
E0 -->|allowed| E1["[macOS] Generate Seatbelt profile"]
|
||||
E0 -->|allowed| E2["[Linux] Generate bwrap command"]
|
||||
|
||||
E1 & E2 --> F["6. Execute wrapped command"]
|
||||
F --> G["7. Manager.Cleanup()"]
|
||||
E1 & E2 --> F["6. Sanitize env<br/>(strip LD_*/DYLD_*)"]
|
||||
F --> G["7. Execute wrapped command"]
|
||||
G --> H["8. Manager.Cleanup()"]
|
||||
|
||||
G --> G1["Kill socat processes"]
|
||||
G --> G2["Remove Unix sockets"]
|
||||
G --> G3["Stop proxy servers"]
|
||||
H --> H1["Kill socat processes"]
|
||||
H --> H2["Remove Unix sockets"]
|
||||
H --> H3["Stop proxy servers"]
|
||||
```
|
||||
|
||||
## Platform Comparison
|
||||
@@ -251,6 +281,7 @@ flowchart TD
|
||||
| Syscall filtering | Implicit (Seatbelt) | seccomp BPF |
|
||||
| Inbound connections | Profile rules (`network-bind`) | Reverse socat bridges |
|
||||
| Violation monitoring | log stream + proxy | eBPF + proxy |
|
||||
| Env sanitization | Strips DYLD_* | Strips LD_* |
|
||||
| Requirements | Built-in | bwrap, socat |
|
||||
|
||||
### Linux Security Layers
|
||||
@@ -269,7 +300,7 @@ See [Linux Security Features](./docs/linux-security-features.md) for details.
|
||||
|
||||
## Violation Monitoring
|
||||
|
||||
The `-m` (monitor) flag enables real-time visibility into blocked operations.
|
||||
The `-m` (monitor) flag enables real-time visibility into blocked operations. These only apply to filesystem and network operations, not blocked commands.
|
||||
|
||||
### Output Prefixes
|
||||
|
||||
|
||||
@@ -185,7 +185,15 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
||||
fmt.Fprintf(os.Stderr, "[fence] Sandboxed command: %s\n", sandboxedCommand)
|
||||
}
|
||||
|
||||
hardenedEnv := sandbox.GetHardenedEnv()
|
||||
if debug {
|
||||
if stripped := sandbox.GetStrippedEnvVars(os.Environ()); len(stripped) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "[fence] Stripped dangerous env vars: %v\n", stripped)
|
||||
}
|
||||
}
|
||||
|
||||
execCmd := exec.Command("sh", "-c", sandboxedCommand) //nolint:gosec // sandboxedCommand is constructed from user input - intentional
|
||||
execCmd.Env = hardenedEnv
|
||||
execCmd.Stdin = os.Stdin
|
||||
execCmd.Stdout = os.Stdout
|
||||
execCmd.Stderr = os.Stderr
|
||||
@@ -318,8 +326,11 @@ parseCommand:
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Exec: %s %v\n", execPath, command[1:])
|
||||
}
|
||||
|
||||
// Sanitize environment (strips LD_PRELOAD, etc.)
|
||||
hardenedEnv := sandbox.FilterDangerousEnv(os.Environ())
|
||||
|
||||
// Exec the command (replaces this process)
|
||||
err = syscall.Exec(execPath, command, os.Environ()) //nolint:gosec
|
||||
err = syscall.Exec(execPath, command, hardenedEnv) //nolint:gosec
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Exec failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -43,6 +43,15 @@ Localhost is separate from "external domains":
|
||||
- **denyRead** can block reads from sensitive paths.
|
||||
- Fence includes an internal list of always-protected targets (e.g. shell configs, git hooks) to reduce common persistence vectors.
|
||||
|
||||
### Environment sanitization
|
||||
|
||||
Fence strips dangerous environment variables before passing them to sandboxed commands:
|
||||
|
||||
- `LD_*` (Linux): `LD_PRELOAD`, `LD_LIBRARY_PATH`, etc.
|
||||
- `DYLD_*` (macOS): `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH`, etc.
|
||||
|
||||
This prevents a library injection attack where a sandboxed process writes a malicious `.so`/`.dylib` and then uses `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` in a subsequent command to load it.
|
||||
|
||||
## Visibility / auditing
|
||||
|
||||
- `-m/--monitor` helps you discover what a command *tries* to access (blocked only).
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
@@ -18,9 +17,9 @@ type CommandBlockedError struct {
|
||||
|
||||
func (e *CommandBlockedError) Error() string {
|
||||
if e.IsDefault {
|
||||
return fmt.Sprintf("command blocked by default policy: %q matches %q", e.Command, e.BlockedPrefix)
|
||||
return fmt.Sprintf("command blocked by default sandbox command policy: %q matches %q", e.Command, e.BlockedPrefix)
|
||||
}
|
||||
return fmt.Sprintf("command blocked by policy: %q matches %q", e.Command, e.BlockedPrefix)
|
||||
return fmt.Sprintf("command blocked by sandbox command policy: %q matches %q", e.Command, e.BlockedPrefix)
|
||||
}
|
||||
|
||||
// CheckCommand checks if a command is allowed by the configuration.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build !linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build !linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
// LinuxFeatures describes available Linux sandboxing features.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build !linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import "github.com/Use-Tusk/fence/internal/config"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//go:build !linux
|
||||
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
// SeccompFilter is a stub for non-Linux platforms.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
114
internal/sandbox/sanitize.go
Normal file
114
internal/sandbox/sanitize.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DangerousEnvPrefixes lists environment variable prefixes that can be used
|
||||
// to subvert library loading and should be stripped from sandboxed processes.
|
||||
//
|
||||
// - LD_* (Linux): LD_PRELOAD, LD_LIBRARY_PATH can inject malicious shared libraries
|
||||
// - DYLD_* (macOS): DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH can inject dylibs
|
||||
var DangerousEnvPrefixes = []string{
|
||||
"LD_", // Linux dynamic linker
|
||||
"DYLD_", // macOS dynamic linker
|
||||
}
|
||||
|
||||
// DangerousEnvVars lists specific environment variables that should be stripped.
|
||||
var DangerousEnvVars = []string{
|
||||
"LD_PRELOAD",
|
||||
"LD_LIBRARY_PATH",
|
||||
"LD_AUDIT",
|
||||
"LD_DEBUG",
|
||||
"LD_DEBUG_OUTPUT",
|
||||
"LD_DYNAMIC_WEAK",
|
||||
"LD_ORIGIN_PATH",
|
||||
"LD_PROFILE",
|
||||
"LD_PROFILE_OUTPUT",
|
||||
"LD_SHOW_AUXV",
|
||||
"LD_TRACE_LOADED_OBJECTS",
|
||||
"DYLD_INSERT_LIBRARIES",
|
||||
"DYLD_LIBRARY_PATH",
|
||||
"DYLD_FRAMEWORK_PATH",
|
||||
"DYLD_FALLBACK_LIBRARY_PATH",
|
||||
"DYLD_FALLBACK_FRAMEWORK_PATH",
|
||||
"DYLD_IMAGE_SUFFIX",
|
||||
"DYLD_FORCE_FLAT_NAMESPACE",
|
||||
"DYLD_PRINT_LIBRARIES",
|
||||
"DYLD_PRINT_APIS",
|
||||
}
|
||||
|
||||
// GetHardenedEnv returns a copy of the current environment with dangerous
|
||||
// variables removed. This prevents library injection attacks where a malicious
|
||||
// agent writes a .so/.dylib and then uses LD_PRELOAD/DYLD_INSERT_LIBRARIES
|
||||
// in a subsequent command.
|
||||
func GetHardenedEnv() []string {
|
||||
return FilterDangerousEnv(os.Environ())
|
||||
}
|
||||
|
||||
// FilterDangerousEnv filters out dangerous environment variables from the given slice.
|
||||
func FilterDangerousEnv(env []string) []string {
|
||||
filtered := make([]string, 0, len(env))
|
||||
for _, e := range env {
|
||||
if !isDangerousEnvVar(e) {
|
||||
filtered = append(filtered, e)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// isDangerousEnvVar checks if an environment variable entry (KEY=VALUE) is dangerous.
|
||||
func isDangerousEnvVar(entry string) bool {
|
||||
// Split on first '=' to get the key
|
||||
key := entry
|
||||
if idx := strings.Index(entry, "="); idx != -1 {
|
||||
key = entry[:idx]
|
||||
}
|
||||
|
||||
// Check against known dangerous prefixes
|
||||
for _, prefix := range DangerousEnvPrefixes {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check against specific dangerous vars
|
||||
for _, dangerous := range DangerousEnvVars {
|
||||
if key == dangerous {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetStrippedEnvVars returns a list of environment variable names that were
|
||||
// stripped from the given environment. Useful for debug logging.
|
||||
func GetStrippedEnvVars(env []string) []string {
|
||||
var stripped []string
|
||||
for _, e := range env {
|
||||
if isDangerousEnvVar(e) {
|
||||
// Extract just the key
|
||||
if idx := strings.Index(e, "="); idx != -1 {
|
||||
stripped = append(stripped, e[:idx])
|
||||
} else {
|
||||
stripped = append(stripped, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return stripped
|
||||
}
|
||||
|
||||
// HardeningFeatures returns a description of environment sanitization applied on this platform.
|
||||
func HardeningFeatures() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return "env-filter(LD_*)"
|
||||
case "darwin":
|
||||
return "env-filter(DYLD_*)"
|
||||
default:
|
||||
return "env-filter"
|
||||
}
|
||||
}
|
||||
156
internal/sandbox/sanitize_test.go
Normal file
156
internal/sandbox/sanitize_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsDangerousEnvVar(t *testing.T) {
|
||||
tests := []struct {
|
||||
entry string
|
||||
dangerous bool
|
||||
}{
|
||||
// Linux LD_* variables
|
||||
{"LD_PRELOAD=/tmp/evil.so", true},
|
||||
{"LD_LIBRARY_PATH=/tmp", true},
|
||||
{"LD_AUDIT=/tmp/audit.so", true},
|
||||
{"LD_DEBUG=all", true},
|
||||
|
||||
// macOS DYLD_* variables
|
||||
{"DYLD_INSERT_LIBRARIES=/tmp/evil.dylib", true},
|
||||
{"DYLD_LIBRARY_PATH=/tmp", true},
|
||||
{"DYLD_FRAMEWORK_PATH=/tmp", true},
|
||||
{"DYLD_FORCE_FLAT_NAMESPACE=1", true},
|
||||
|
||||
// Safe variables
|
||||
{"PATH=/usr/bin:/bin", false},
|
||||
{"HOME=/home/user", false},
|
||||
{"USER=user", false},
|
||||
{"SHELL=/bin/bash", false},
|
||||
{"HTTP_PROXY=http://localhost:8080", false},
|
||||
{"HTTPS_PROXY=http://localhost:8080", false},
|
||||
|
||||
// Edge cases - variables that start with similar prefixes but aren't dangerous
|
||||
{"LDFLAGS=-L/usr/lib", false}, // Not LD_ prefix
|
||||
{"DISPLAY=:0", false},
|
||||
|
||||
// Empty and malformed
|
||||
{"LD_PRELOAD", true}, // No value but still dangerous
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.entry, func(t *testing.T) {
|
||||
got := isDangerousEnvVar(tt.entry)
|
||||
if got != tt.dangerous {
|
||||
t.Errorf("isDangerousEnvVar(%q) = %v, want %v", tt.entry, got, tt.dangerous)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterDangerousEnv(t *testing.T) {
|
||||
env := []string{
|
||||
"PATH=/usr/bin:/bin",
|
||||
"LD_PRELOAD=/tmp/evil.so",
|
||||
"HOME=/home/user",
|
||||
"DYLD_INSERT_LIBRARIES=/tmp/evil.dylib",
|
||||
"HTTP_PROXY=http://localhost:8080",
|
||||
"LD_LIBRARY_PATH=/tmp",
|
||||
}
|
||||
|
||||
filtered := FilterDangerousEnv(env)
|
||||
|
||||
// Should have 3 safe vars
|
||||
if len(filtered) != 3 {
|
||||
t.Errorf("expected 3 safe vars, got %d: %v", len(filtered), filtered)
|
||||
}
|
||||
|
||||
// Verify the safe vars are present
|
||||
expected := map[string]bool{
|
||||
"PATH=/usr/bin:/bin": true,
|
||||
"HOME=/home/user": true,
|
||||
"HTTP_PROXY=http://localhost:8080": true,
|
||||
}
|
||||
|
||||
for _, e := range filtered {
|
||||
if !expected[e] {
|
||||
t.Errorf("unexpected var in filtered env: %s", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify dangerous vars are gone
|
||||
for _, e := range filtered {
|
||||
if isDangerousEnvVar(e) {
|
||||
t.Errorf("dangerous var not filtered: %s", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStrippedEnvVars(t *testing.T) {
|
||||
env := []string{
|
||||
"PATH=/usr/bin",
|
||||
"LD_PRELOAD=/tmp/evil.so",
|
||||
"DYLD_INSERT_LIBRARIES=/tmp/evil.dylib",
|
||||
"HOME=/home/user",
|
||||
}
|
||||
|
||||
stripped := GetStrippedEnvVars(env)
|
||||
|
||||
if len(stripped) != 2 {
|
||||
t.Errorf("expected 2 stripped vars, got %d: %v", len(stripped), stripped)
|
||||
}
|
||||
|
||||
// Should contain just the keys, not values
|
||||
found := make(map[string]bool)
|
||||
for _, s := range stripped {
|
||||
found[s] = true
|
||||
}
|
||||
|
||||
if !found["LD_PRELOAD"] {
|
||||
t.Error("expected LD_PRELOAD to be in stripped list")
|
||||
}
|
||||
if !found["DYLD_INSERT_LIBRARIES"] {
|
||||
t.Error("expected DYLD_INSERT_LIBRARIES to be in stripped list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterDangerousEnv_EmptyInput(t *testing.T) {
|
||||
filtered := FilterDangerousEnv(nil)
|
||||
if filtered == nil {
|
||||
t.Error("expected non-nil slice for nil input")
|
||||
}
|
||||
if len(filtered) != 0 {
|
||||
t.Errorf("expected empty slice, got %v", filtered)
|
||||
}
|
||||
|
||||
filtered = FilterDangerousEnv([]string{})
|
||||
if len(filtered) != 0 {
|
||||
t.Errorf("expected empty slice, got %v", filtered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterDangerousEnv_AllDangerous(t *testing.T) {
|
||||
env := []string{
|
||||
"LD_PRELOAD=/tmp/evil.so",
|
||||
"LD_LIBRARY_PATH=/tmp",
|
||||
"DYLD_INSERT_LIBRARIES=/tmp/evil.dylib",
|
||||
}
|
||||
|
||||
filtered := FilterDangerousEnv(env)
|
||||
if len(filtered) != 0 {
|
||||
t.Errorf("expected all vars to be filtered, got %v", filtered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterDangerousEnv_AllSafe(t *testing.T) {
|
||||
env := []string{
|
||||
"PATH=/usr/bin",
|
||||
"HOME=/home/user",
|
||||
"USER=test",
|
||||
}
|
||||
|
||||
filtered := FilterDangerousEnv(env)
|
||||
if len(filtered) != 3 {
|
||||
t.Errorf("expected all 3 vars to pass through, got %d", len(filtered))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// Package sandbox provides sandboxing functionality for macOS and Linux.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user