> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox Security Guide

> Isolation models, key handling, and hardening for the sandbox.

# Security Best Practices Guide

This guide covers security considerations for deploying and using StateSet Sandbox, including isolation options, secret management, network policies, and compliance.

## Security Architecture

```
┌──────────────────────────────────────────────────────────────────────────┐
│                            API Gateway                                    │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ TLS Termination │ Rate Limiting │ API Key Auth │ CORS │ WAF        │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                         Controller                                        │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌───────────────────┐  │
│  │ Input      │  │ Auth/Authz │  │ Secret     │  │ Audit Logging     │  │
│  │ Validation │  │            │  │ Management │  │                   │  │
│  └────────────┘  └────────────┘  └────────────┘  └───────────────────┘  │
└──────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                         Sandbox Isolation                                 │
│  ┌─────────────────────┐  ┌─────────────────────────────────────────┐   │
│  │ Container           │  │ gVisor / Kata / Firecracker             │   │
│  │ ├─ seccomp          │  │ ├─ Separate kernel/VM per sandbox       │   │
│  │ ├─ AppArmor         │  │ ├─ Memory isolation                     │   │
│  │ ├─ Resource limits  │  │ └─ Full syscall filtering               │   │
│  │ └─ Network policies │  │                                         │   │
│  └─────────────────────┘  └─────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────────────────┘
```

## Isolation Levels

Isolation is enforced at the Kubernetes pod level via RuntimeClass (and your cluster's configured runtimes).

* **Container**: default runtime (no RuntimeClass)
* **gVisor**: a gVisor RuntimeClass (often named `gvisor`)
* **MicroVM**: a Kata/Firecracker RuntimeClass (often named `kata-qemu`)

In the Node controller, `SANDBOX_RUNTIME_CLASS` is applied to all sandbox pods:

```bash theme={null}
# Example: run sandboxes under gVisor
SANDBOX_RUNTIME_CLASS=gvisor
```

The sandbox create API accepts an `isolation` field (`container` | `gvisor` | `microvm`) which is currently used
for warm pool profile matching and labeling; the actual isolation boundary is provided by the RuntimeClass.

## API Key Security

### Key Management

1. **Generate strong keys**:
   ```bash theme={null}
   openssl rand -base64 32
   ```

2. **Use environment variables** (never hardcode):
   ```bash theme={null}
   export STATESET_API_KEY="sk_..."
   ```

3. **Rotate keys regularly**:
   ```bash theme={null}
   # Create new key
   curl -X POST https://api.sandbox.stateset.com/api/v1/api-keys \
     -H "Authorization: ApiKey $OLD_KEY" \
     -d '{"name": "production-v2"}'

   # Update your systems with new key

   # Revoke old key
   curl -X DELETE https://api.sandbox.stateset.com/api/v1/api-keys/$OLD_KEY_ID \
     -H "Authorization: ApiKey $NEW_KEY"
   ```

4. **Use scoped keys** when possible:
   * Read-only keys for monitoring
   * Limited keys for specific sandboxes

### Key Storage

| Environment | Recommendation                                                  |
| ----------- | --------------------------------------------------------------- |
| Development | `.env` file (gitignored)                                        |
| CI/CD       | Pipeline secrets (GitHub Actions, GitLab CI)                    |
| Production  | Secret manager (Vault, AWS Secrets Manager, GCP Secret Manager) |
| Kubernetes  | Kubernetes Secrets (encrypted at rest)                          |

## Secret Management

StateSet stores secrets at the organization level and injects them into sandbox pods as environment variables at creation time.

### Create / Update Secrets (BYOK)

```bash theme={null}
# Create a secret (values are never returned after creation)
curl -X POST https://api.sandbox.stateset.com/api/v1/secrets \
  -H "Authorization: ApiKey $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ANTHROPIC_API_KEY",
    "value": "sk-ant-REDACTED",
    "scope": "sandbox"
  }'

# Update a secret
curl -X PUT https://api.sandbox.stateset.com/api/v1/secrets/ANTHROPIC_API_KEY \
  -H "Authorization: ApiKey $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "value": "sk-ant-REDACTED" }'
```

Security properties:

* Encrypted at rest in Postgres using AES-256-GCM (`DATABASE_ENCRYPTION_KEY` is required in production when `DATABASE_URL` is set)
* Never logged or returned by list endpoints
* Persist until deleted (not tied to a single sandbox lifetime)
* Can be restricted to specific sandboxes using `allowed_sandbox_patterns`

### Secret Access Logging

Enable audit logging for secret access:

```bash theme={null}
AUDIT_LOGS_ENABLED=true
```

Query audit logs:

```sql theme={null}
SELECT * FROM secret_access_log
WHERE secret_name = 'OPENAI_API_KEY'
ORDER BY accessed_at DESC;
```

## Network Security

### Default Network Policy

In the provided Kubernetes manifests (`k8s/network-policy.yaml`), sandbox pods:

* Allow ingress only from the controller
* Allow egress only to:
  * DNS (kube-dns)
  * TCP 443 to a Cloudflare IP allowlist (to reach `api.anthropic.com`)

This is intentionally restrictive and IP-based (NetworkPolicy cannot match by DNS name). If you need broader egress or
DNS-aware allowlisting, use an egress proxy/service mesh or a CNI with DNS-aware policies (e.g., Cilium).

### Custom Network Policies

Create Kubernetes NetworkPolicy for additional restrictions:

```yaml theme={null}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: sandbox-egress
  namespace: stateset-sandbox
spec:
  podSelector:
    matchLabels:
      app: stateset-sandbox
  policyTypes:
    - Egress
  egress:
    # Allow DNS
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
    # Allow HTTPS only
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 10.0.0.0/8
              - 172.16.0.0/12
              - 192.168.0.0/16
              - 169.254.0.0/16
      ports:
        - protocol: TCP
          port: 443
```

### Webhook URL Validation

Webhooks cannot target:

* localhost
* Private IP addresses
* Cluster-internal services

Webhook requests:

* Follow up to 3 redirects
* Block cross-host redirects
* Block HTTPS→HTTP downgrades

## Input Validation

### Path Traversal Prevention

All file paths are validated:

* No `..` components allowed
* Must be within sandbox workspace
* Absolute paths normalized

```python theme={null}
# Rejected
"/workspace/../etc/passwd"
"../../secrets"

# Allowed
"/workspace/src/app.py"
"/workspace/data.json"
```

### Command Injection Prevention

Prefer passing commands as arrays to avoid shell interpolation. If you send a string command, the controller executes it via `sh -c`.

```python theme={null}
# Recommended (no shell)
sdk.sandbox.execute(sandbox_id, ["ls", "-la", "/workspace"])

# String form is supported, but uses a shell:
sdk.sandbox.execute(sandbox_id, "ls -la /workspace")
```

### Request Size Limits

| Limit                   | Default | Max                                  |
| ----------------------- | ------- | ------------------------------------ |
| File content (per file) | 10MB    | 10MB (hard limit)                    |
| JSON request body       | 10MB    | 10MB (hard limit)                    |
| Files per request       | 100     | 100 (hard limit)                     |
| Stripe webhook payload  | 1MB     | 5MB (via `STRIPE_WEBHOOK_MAX_BYTES`) |

## Pod Security

### Security Context

Sandbox pods run with restricted security context:

```yaml theme={null}
securityContext:
  runAsUser: 1001
  runAsGroup: 1001
  runAsNonRoot: true
  readOnlyRootFilesystem: true  # When enabled
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
```

### Service Account

Sandboxes use a dedicated service account without cluster access:

```yaml theme={null}
automountServiceAccountToken: false
enableServiceLinks: false
```

Configuration:

```bash theme={null}
SANDBOX_AUTOMOUNT_SERVICE_ACCOUNT_TOKEN=false
SANDBOX_ENABLE_SERVICE_LINKS=false
```

### Resource Limits

All sandboxes have enforced limits:

```yaml theme={null}
resources:
  limits:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  requests:
    cpu: "100m"
    memory: "128Mi"
```

## Database Security

### Encryption at Rest

Sensitive values stored in the database (e.g., organization secrets and webhook secrets) are encrypted with AES-256-GCM.
API keys are stored as one-way hashes (not reversible); only prefixes are returned for display.

Configuration:

```bash theme={null}
DATABASE_ENCRYPTION_KEY=<64-hex-character-key>
```

Generate key:

```bash theme={null}
openssl rand -hex 32
```

### Connection Security

```bash theme={null}
# Use SSL for database connections
DATABASE_URL="postgres://user:pass@host:5432/sandbox?sslmode=require"
```

### Access Control

Use separate database users:

* Application user with limited permissions
* Migration user for schema changes
* Read-only user for analytics

## Authentication & Authorization

### JWT Configuration

```bash theme={null}
# Strong secret (minimum 32 characters)
JWT_SECRET=$(openssl rand -base64 32)

# Short token lifetime for security
JWT_EXPIRY=3600  # 1 hour
```

### Admin Access

Protect admin endpoints:

```bash theme={null}
ADMIN_API_KEY=<strong-unique-key>
# Or use username/password
ADMIN_USERNAME=admin
ADMIN_PASSWORD_HASH=<bcrypt-hash>
```

Generate bcrypt hash:

```javascript theme={null}
const bcrypt = require('bcrypt');
console.log(bcrypt.hashSync('your-password', 10));
```

### Internal Endpoints

Protect internal endpoints (/metrics, /health/detailed):

```bash theme={null}
INTERNAL_API_KEY=<internal-key>
```

## Audit Logging

### Enable Audit Logs

```bash theme={null}
AUDIT_LOGS_ENABLED=true
```

### Logged Events

* Sandbox creation/termination
* Command execution
* File operations
* Secret access
* API key operations
* Admin actions

### Log Format

```json theme={null}
{
  "timestamp": "2024-01-21T12:00:00.000Z",
  "event": "sandbox.created",
  "org_id": "org-123",
  "user_id": "user-456",
  "sandbox_id": "sb-abc789",
  "ip_address": "203.0.113.50",
  "user_agent": "StateSet-SDK/1.0.0",
  "details": {
    "cpus": "2",
    "memory": "4Gi"
  }
}
```

### Log Retention

Configure retention policy based on compliance requirements:

* SOC2: 1 year minimum
* HIPAA: 6 years
* GDPR: Document justification for retention period

## Production Hardening Checklist

### Environment Configuration

* [ ] `NODE_ENV=production`
* [ ] `DEV_MODE=false`
* [ ] `LOG_LEVEL=info` (not debug/trace)

### Authentication

* [ ] `JWT_SECRET` is unique, 32+ characters
* [ ] `ADMIN_API_KEY` is set and strong
* [ ] `INTERNAL_API_KEY` is set for metrics
* [ ] API keys are rotated regularly

### Database

* [ ] `DATABASE_ENCRYPTION_KEY` is set (64 hex chars)
* [ ] SSL enabled for database connection
* [ ] Database credentials are from secret manager
* [ ] Regular backups configured

### Network

* [ ] CORS restricted to your domains: `CORS_ORIGIN=https://app.example.com`
* [ ] TLS/HTTPS enforced
* [ ] `WEBHOOK_ALLOW_PRIVATE=false`
* [ ] `WS_ALLOW_QUERY_AUTH=false` (legacy/deprecated; query-token auth is disabled for Events/WebSocket)

### Sandbox Security

* [ ] `SANDBOX_READ_ONLY_ROOT=true`
* [ ] `SANDBOX_AUTOMOUNT_SERVICE_ACCOUNT_TOKEN=false`
* [ ] `SANDBOX_ENABLE_SERVICE_LINKS=false`
* [ ] Appropriate isolation level set
* [ ] Resource limits configured

### Monitoring

* [ ] `AUDIT_LOGS_ENABLED=true`
* [ ] Metrics collection configured
* [ ] Alerting for security events
* [ ] Log aggregation configured

## Incident Response

### Suspicious Activity

1. **Identify**: Check audit logs for anomalies
2. **Contain**: Suspend affected organization
   ```bash theme={null}
   curl -X POST https://api.sandbox.stateset.com/api/admin/orgs/$ORG_ID/suspend \
     -H "Authorization: Bearer $ADMIN_KEY"
   ```
3. **Investigate**: Review logs, metrics, sandbox contents
4. **Remediate**: Revoke compromised keys, patch vulnerabilities
5. **Document**: Post-incident report

### Key Compromise

1. **Revoke** the compromised key immediately
2. **Generate** new keys
3. **Audit** recent activity with the compromised key
4. **Notify** affected users
5. **Review** how the key was exposed

### Data Breach Response

1. **Assess** scope of breach
2. **Contain** the breach (suspend orgs, terminate sandboxes)
3. **Notify** affected parties per GDPR/regulatory requirements
4. **Document** timeline and response
5. **Improve** security controls

## Compliance Considerations

### SOC2

* Enable audit logging
* Encrypt data at rest
* Implement access controls
* Monitor for anomalies
* Document security policies

### GDPR

* Document data processing purposes
* Implement data retention policies
* Support data export and deletion
* Log consent and access
* Appoint DPO if required

### HIPAA

* Sign BAA with cloud providers
* Enable audit logging
* Encrypt all PHI
* Implement access controls
* Train staff on HIPAA

## Security Contacts

* Security issues: [security@stateset.com](mailto:security@stateset.com)
* Bug bounty: [https://stateset.com/security/bounty](https://stateset.com/security/bounty)
* Security documentation: [https://stateset.com/security](https://stateset.com/security)
