Zerotrust vs Semitrusted: Choosing Your Network Mode
When you execute code on unsandbox, you’re running inside an isolated LXC container. But isolation means different things depending on your use case. Sometimes you want maximum security with no network access. Sometimes you need to pip install a package or call an API.
That’s why unsandbox offers two network modes: zerotrust and semitrusted.
The Two Modes at a Glance
| Mode | Network Access | Default | Use Case |
|---|---|---|---|
| zerotrust | None | Yes | Untrusted code, security-critical |
| semitrusted | Via egress proxy | No | Package installation, API calls |
Let’s dig into each.
Zerotrust: Complete Network Isolation
Zerotrust is the default mode. When you execute code without specifying a network mode, you get zerotrust.
# These are equivalent - both use zerotrust
un script.py
un -n zerotrust script.py
What Zerotrust Means
Your container has no network interfaces connected to the outside world. Period.
$ un -s bash 'curl https://google.com'
curl: (6) Could not resolve host: google.com
$ un -s bash 'ping 8.8.8.8'
ping: connect: Network is unreachable
DNS doesn’t resolve. TCP connections fail. UDP packets go nowhere. Your code runs in a network vacuum.
Why This Matters
Consider what happens when you run untrusted code:
- Malicious script tries to exfiltrate data - Can’t. No network.
- Cryptominer wants to phone home - Can’t. No network.
- Reverse shell attempts to connect out - Can’t. No network.
- Supply chain attack in a dependency - Can’t exfiltrate. No network.
Zerotrust isn’t about trusting the network. It’s about not needing to trust the code.
When to Use Zerotrust
- Running user-submitted code - Students, code golf, online judges
- Executing AI-generated code - LLM tool use where you can’t verify safety
- Testing untrusted snippets - Code from the internet, Stack Overflow, forums
- Security-sensitive operations - Processing secrets, cryptographic operations
- Reproducible builds - No network means deterministic execution
The Trade-Off
Zerotrust containers can’t:
-
Fetch packages (
pip install,npm install, etc.) - Make HTTP requests to external APIs
- Download files from the internet
- Resolve DNS queries
- Connect to databases hosted elsewhere
If your code needs any of these, you need semitrusted.
Semitrusted: Internet Through a Proxy
Semitrusted mode gives your container internet access, but all traffic flows through our egress proxy.
# Enable network access
un -n semitrusted script.py
# With inline code
un -s python -n semitrusted 'import requests; print(requests.get("https://httpbin.org/ip").text)'
How It Works
Your container connects to the internet through a transparent proxy:
Container → Egress Proxy → Internet
The proxy:
- Allows outbound HTTP/HTTPS (ports 80, 443)
- Allows outbound DNS (port 53)
- Blocks inbound connections - Nothing can connect TO your container
- Logs traffic metadata - We can audit what domains are contacted
- Rate limits egress - Prevents abuse
What You Can Do
# Install packages
$ un -s bash -n semitrusted 'pip install requests && python -c "import requests; print(requests.__version__)"'
2.31.0
# Call APIs
$ un -s python -n semitrusted 'import urllib.request; print(urllib.request.urlopen("https://api.github.com").read()[:100])'
b'{"current_user_url":"https://api.github.com/user",...
# Download files
$ un -s bash -n semitrusted 'curl -O https://example.com/data.json && ls -la data.json'
-rw-r--r-- 1 root root 1256 Jan 15 10:00 data.json
Why “Semitrusted”?
We call it semitrusted because you’re extending partial trust to the code:
- You trust it won’t abuse network access - Spam, attacks, illegal content
- You trust it won’t exfiltrate sensitive data - If you’re passing secrets
- You accept visibility into what it does - Traffic is logged
The container still can’t:
- Accept inbound connections (no reverse shells)
- Access your local network
- Bypass the proxy
- Make unlimited requests (rate limits apply)
When to Use Semitrusted
- Development with dependencies - Installing packages, testing integrations
- API integrations - Code that needs to call external services
- Web scraping - Fetching data from websites
- Testing networking code - Verifying HTTP clients work
- Long-running services - Apps that need to serve or consume APIs
CLI Examples
Basic Execution
# Zerotrust (default) - no network
un script.py
# Semitrusted - with network
un -n semitrusted script.py
Interactive Sessions
# Zerotrust session
un session
# Semitrusted session - install packages as needed
un session -n semitrusted
Services
Services can also specify network mode in their configuration:
# Create a service with network access
un service --name my-api --ports 8080 --network semitrusted --bootstrap "pip install flask && python app.py"
API Examples
Execute Endpoint
POST /execute
{
"language": "python",
"code": "import requests\nprint(requests.get('https://httpbin.org/ip').text)",
"network": "semitrusted"
}
For zerotrust, omit the network field or set it to "zerotrust":
POST /execute
{
"language": "python",
"code": "print(2 + 2)"
}
Session Endpoint
POST /session
{
"network": "semitrusted"
}
Security Comparison
| Threat | Zerotrust | Semitrusted |
|---|---|---|
| Data exfiltration | Impossible | Possible (logged) |
| Cryptomining | Can’t phone home | Could attempt |
| Reverse shell | Can’t connect out | Blocked by proxy |
| Supply chain attack | Can’t fetch | Could fetch malicious packages |
| DDoS participation | No network | Rate limited |
| Port scanning | No network | Blocked by proxy |
Making the Choice
Use Zerotrust When:
- You don’t control the code - User submissions, AI output, untrusted sources
- Security is paramount - Processing secrets, financial data, PII
- You need reproducibility - Same inputs = same outputs, no network variance
- The code is self-contained - No external dependencies or API calls needed
Use Semitrusted When:
-
You need packages -
pip install,npm install,cargo build - You’re calling APIs - HTTP requests to external services
- You control the code - Your own scripts, trusted sources
- You’re developing/testing - Interactive development with network
The Middle Ground
Often the best approach is layered:
# Step 1: Install dependencies in a semitrusted session
un session -n semitrusted
> pip install numpy pandas requests
> # save snapshot
# Step 2: Run untrusted code from that snapshot in zerotrust
un session --snapshot my-deps-snapshot
> # Now you have packages but no network
Or use snapshots:
# Create a base image with all dependencies
un session -n semitrusted --name setup
> pip install -r requirements.txt
> exit
# Snapshot it
un session --snapshot setup
# Run zerotrust sessions from that snapshot
un session --restore my-snapshot
# Has packages, no network access
FAQ
Q: Can I switch modes mid-session? A: No. Network mode is set when the container starts. Create a new session with the desired mode.
Q: Does semitrusted cost more? A: No. Both modes are included in all plans.
Q: Can I whitelist specific domains? A: Not currently. Semitrusted allows all outbound HTTP/HTTPS.
Q: Is zerotrust really zero network? A: Yes. No loopback exceptions, no local network, no DNS. The container has no route to anywhere.
Q: What about localhost connections? A: Localhost within the container works (127.0.0.1). But the container can’t reach your host machine’s localhost.
Q: Can I run a web server in zerotrust? A: You can run it, but nothing external can connect to it. Use services with semitrusted for web apps.
Conclusion
Zerotrust and semitrusted represent a fundamental trade-off in sandbox design:
- Zerotrust: Maximum security, minimum capability
- Semitrusted: Network access, reduced isolation
There’s no wrong choice - only the right choice for your use case. Run untrusted code in zerotrust. Develop and test in semitrusted. Use snapshots to bridge the gap.
The network mode is just a flag. The security model is what matters.
# Maximum security
un script.py
# When you need the network
un -n semitrusted script.py
Choose wisely. Execute safely.