This implementation provides cross-platform subprocess sandboxing for the StellarFlow backend, enhancing system safety by constraining subprocess execution through OS-level security mechanisms.
The sandboxing system adapts to the host operating system:
- Linux: Uses seccomp filters for syscall restriction
- Windows: Uses PowerShell resource limits and Job Objects
- macOS: Uses ulimit-based resource constraints
- Fallback: Graceful degradation to basic resource limits
src/security/sandbox.ts- Main sandboxing modulesrc/utils/dbValidator.ts- Integration point for database operationssrc/config/configWatcher.ts- Configuration managementconfig.json- Runtime configuration
- Execution Timeout: Prevents runaway processes
- Memory Limits: Constrains process memory usage
- Syscall Filtering (Linux): Restricts kernel syscall access
- Network Control: Optional network access restriction
- File System Control: Optional file write restriction
- Working Directory: Optional directory confinement
Sandboxing is controlled via config.json:
{
"sandbox": {
"enabled": true,
"timeoutMs": 30000,
"maxMemoryMb": 512,
"allowNetwork": true,
"allowFileWrites": true
}
}- enabled (
boolean): Enable/disable sandboxing globally - timeoutMs (
number): Maximum execution time in milliseconds (0 = unlimited) - maxMemoryMb (
number): Maximum memory allocation in MB (0 = unlimited) - allowNetwork (
boolean): Allow network access in subprocess - allowFileWrites (
boolean): Allow file system writes
Configuration changes are applied immediately without restart via the configWatcher system.
import { dbSandbox } from "./security/sandbox";
// Execute a command with sandboxing
const result = dbSandbox.execSync("npx prisma validate");
if (result.success) {
console.log("Command succeeded:", result.stdout);
} else {
console.error("Command failed:", result.error);
}import { createSandbox } from "./security/sandbox";
const customSandbox = createSandbox({
enabled: true,
timeoutMs: 10000,
maxMemoryMb: 256,
allowNetwork: false,
allowFileWrites: false,
});
const result = customSandbox.execSync("some-command");// Update sandbox policy at runtime
dbSandbox.updatePolicy({
timeoutMs: 60000,
allowNetwork: false,
});On Linux systems, the sandbox uses seccomp (Secure Computing Mode) to filter system calls:
const allowedSyscalls = [
"read",
"write",
"open",
"close",
"execve",
"exit",
"exit_group",
"socket",
"connect",
"bind",
// ... more syscalls
];Requirements:
- Kernel with seccomp support (Linux 3.5+)
- Optional:
libseccomp-toolsfor advanced filtering
Installation:
sudo apt-get install libseccomp-toolsOn Windows, the sandbox uses PowerShell to enforce resource limits:
Start-Process -FilePath "cmd.exe" -ArgumentList "/c command" -Wait -Timeout 30On macOS, the sandbox uses ulimit-style resource constraints via Node.js maxBuffer option.
The sandboxing system mitigates:
- Command Injection: Through strict syscall filtering
- Resource Exhaustion: Through memory and timeout limits
- Privilege Escalation: Through syscall restriction
- Data Exfiltration: Through network and file system controls
- Platform Dependencies: Full seccomp support only on Linux
- Root Access: Some features may require elevated privileges
- Performance: Slight overhead from sandboxing enforcement
- Compatibility: May break commands requiring unrestricted access
- Enable in Production: Always enable sandboxing in production environments
- Test Thoroughly: Validate all commands work under sandboxing
- Monitor Logs: Watch for sandbox-related errors in production logs
- Update Regularly: Keep syscall lists updated for your use cases
- Defense in Depth: Use alongside other security measures (rate limiting, input validation)
The primary integration is in src/utils/dbValidator.ts:
// Before: Direct execution
execSync("npx prisma validate", { stdio: "pipe" });
// After: Sandboxed execution
const result = dbSandbox.execSync("npx prisma validate");
if (!result.success) {
throw new Error(`Validation failed: ${result.error}`);
}Potential areas for sandboxing integration:
- Script execution in
scripts/directory - External API calls via subprocess
- File processing operations
- System maintenance tasks
npm run test:jest sandbox.test.tsThe test suite covers:
- Platform detection
- Policy management
- Command execution (success/failure)
- Timeout enforcement
- Resource limits
- Error handling
- Cross-platform behavior
Symptom: [Sandbox] seccomp not available, using resource limits only
Solution:
- Verify Linux kernel version (3.5+)
- Install seccomp tools:
sudo apt-get install libseccomp-tools - Check
/proc/self/statusfor Seccomp field
Symptom: Commands fail with timeout error
Solution:
- Increase
timeoutMsin config - Optimize command execution time
- Check for resource constraints
Symptom: Commands fail with memory error
Solution:
- Increase
maxMemoryMbin config - Optimize command memory usage
- Check for memory leaks in subprocess
Symptom: Commands work on one platform but not another
Solution:
- Check platform-specific requirements
- Review platform detection logic
- Test on target platform
Enable detailed logging:
import { logger } from "./utils/logger";
logger.level = "debug";- Linux (seccomp): ~1-5% overhead
- Windows: ~2-8% overhead
- macOS: ~1-3% overhead
- Disable for Trusted Commands: Disable sandboxing for trusted internal commands
- Adjust Limits: Set appropriate timeout and memory limits
- Cache Results: Cache command results where appropriate
- Batch Operations: Combine multiple operations into single command
Before:
import { execSync } from "child_process";
const output = execSync("some-command");
console.log(output.toString());After:
import { dbSandbox } from "./security/sandbox";
const result = dbSandbox.execSync("some-command");
if (result.success) {
console.log(result.stdout);
} else {
console.error(result.error);
}- Phase 1: Enable sandboxing in development
- Phase 2: Test with staging environment
- Phase 3: Enable in production with monitoring
- Phase 4: Tighten restrictions based on observations
constructor(policy?: Partial<SandboxPolicy>)- execSync(command, options?): Execute command synchronously
- updatePolicy(newPolicy): Update sandbox policy
- getPolicy(): Get current policy
- getPlatform(): Get detected platform
interface SandboxPolicy {
enabled: boolean;
timeoutMs: number;
maxMemoryMb: number;
allowNetwork: boolean;
allowFileWrites: boolean;
restrictToDirectory?: string;
allowedSyscalls?: string[];
}interface SandboxResult {
success: boolean;
stdout: string;
stderr: string;
exitCode: number | null;
error?: string;
sandboxApplied: boolean;
platform: Platform;
}- Sandbox enabled in production config
- Appropriate timeout limits set
- Memory limits configured
- Network access restricted where possible
- File write access restricted where possible
- Syscall lists reviewed for Linux
- Logging enabled for sandbox events
- Monitoring configured for sandbox failures
- Incident response plan for sandbox bypasses
- Regular security reviews scheduled
- Initial implementation
- Cross-platform support (Linux, Windows, macOS)
- Seccomp filter support for Linux
- Configuration-based policy management
- Integration with dbValidator
- Comprehensive test suite
- Hot-reload configuration support
For issues or questions:
- Check this documentation
- Review test cases for examples
- Check application logs for detailed errors
- Open an issue with platform and configuration details
Untrusted feed payloads are parsed inside a short-lived child python3 process
spawned by run_parser(). Communication uses JSON over stdin/stdout:
host process child process
────────────────────────────── ────────────────────────────────
run_parser(parser, payload) ──► stdin: JSON {parser, payload, limits}
↓ sets RLIMIT_AS / RLIMIT_CPU
↓ imports ingestion.parser
↓ calls requested function
stdout: JSON {ok, data/error} ◄── writes result, then exits
The host reads the child's response with subprocess.communicate(timeout=).
If the child does not finish before the wall-clock deadline it is killed with
proc.kill(). In every failure mode (timeout, crash, bad output) the host
receives a SandboxResult dataclass – no exception propagates to the caller.
Set inside the child process before any parser code runs (POSIX resource module):
| Limit | Default | Constant |
|---|---|---|
Virtual address space (RLIMIT_AS) |
256 MiB | DEFAULT_MEMORY_BYTES |
CPU time (RLIMIT_CPU) |
5 s | DEFAULT_CPU_SECS |
| Wall-clock timeout | 10 s | DEFAULT_TIMEOUT_SECS |
All limits can be overridden per call via keyword arguments to run_parser().
| Scenario | SandboxResult fields |
|---|---|
| Success | ok=True, data=<parsed>, exit_code=0 |
| Parser raises an exception | ok=False, error=<message>, exit_code=1 |
| Wall-clock timeout | ok=False, timed_out=True, error="sandbox timeout" |
| Child crash / empty output | ok=False, error=<message>, exit_code=<n> |
| Unknown parser name | ok=False, error="Unknown parser: ..." |
- Parser failures are contained. A malicious payload that triggers an uncaught exception, an infinite loop, or a segfault in a C extension can only affect the child process; the host process is unaffected.
- CPU and memory are bounded.
RLIMIT_CPUprevents runaway computation;RLIMIT_ASprevents memory-exhaustion attacks. - No shared state. Each sandboxed call creates an independent process with its own interpreter, heap, and file descriptors. There is no shared mutable state between invocations.
- Minimal attack surface. The child imports only the modules it needs for the requested parser; the host's in-memory state (connections, secrets, etc.) is not accessible to the child.
from utils.sandbox import run_parser, SandboxResult
result: SandboxResult = run_parser(
parser_name, # "flatten_telemetry_frames" | "build_telemetry_segments" | …
payload, # JSON-serialisable feed payload
timeout=10.0, # wall-clock seconds
memory_bytes=256*1024*1024,
cpu_secs=5,
kwargs={}, # forwarded to the parser function
)
if result.ok:
process(result.data)
else:
log.error("sandbox error: %s (timed_out=%s)", result.error, result.timed_out)PYTHONPATH=src pytest tests/test_sandbox.py -v