# Security Architecture

rdev implements defense in depth with multiple security layers.

## Authentication

### API Keys

All API requests (except health checks) require authentication:

```
┌────────────┐     ┌────────────┐     ┌────────────┐     ┌────────────┐
│   Client   │────▶│    Auth    │────▶│    Auth    │────▶│   Handler  │
│            │     │ Middleware │     │  Service   │     │            │
└────────────┘     └────────────┘     └────────────┘     └────────────┘
                         │                   │
                         │                   ▼
                         │            ┌────────────┐
                         │            │  Postgres  │
                         │            │   (keys)   │
                         │            └────────────┘
                         ▼
                   Check IP Allowlist
```

### Key Format

```
rdev_<random_32_chars>
```

Keys are stored as SHA-256 hashes, never in plaintext.

### Authentication Flow

1. Extract key from `X-API-Key` header or `Authorization: Bearer` header
2. Hash the key with SHA-256
3. Look up hash in database
4. Verify key is not revoked or expired
5. Check IP allowlist (if configured)
6. Add key to request context

### Scopes

| Scope | Description |
|-------|-------------|
| `projects:read` | List and view projects |
| `projects:execute` | Execute commands |
| `keys:read` | List API keys |
| `keys:write` | Create/revoke keys |
| `admin` | Full access |

### IP Allowlisting

API keys can be restricted to specific IP addresses or CIDR ranges:

```go
type APIKey struct {
    // ...
    AllowedIPs []string // CIDR notation: ["192.168.1.0/24", "10.0.0.0/8"]
}

func (k *APIKey) IsIPAllowed(clientIP string) bool {
    if len(k.AllowedIPs) == 0 {
        return true // No restriction
    }
    for _, cidr := range k.AllowedIPs {
        _, network, _ := net.ParseCIDR(cidr)
        if network.Contains(net.ParseIP(clientIP)) {
            return true
        }
    }
    return false
}
```

## Command Sanitization

All commands are sanitized before execution to prevent:

### Shell Injection Protection

```go
// internal/sanitize/command.go

func ShellCommand(cmd string) error {
    // Block command chaining
    dangerous := []string{";", "&&", "||", "|", "`", "$(", "${"}
    for _, d := range dangerous {
        if strings.Contains(cmd, d) {
            return fmt.Errorf("command chaining not allowed")
        }
    }

    // Block redirects
    if strings.ContainsAny(cmd, "<>") {
        return fmt.Errorf("redirects not allowed")
    }

    // Block destructive commands
    if isDestructiveRm(cmd) {
        return fmt.Errorf("destructive rm not allowed")
    }

    return nil
}
```

### Blocked Patterns

| Category | Examples |
|----------|----------|
| Command chaining | `; && || \| $() \`\`` |
| Redirects | `> >> < <<` |
| Destructive | `rm -rf /`, `dd if=` |
| Escape sequences | Null bytes, control chars |

### Git Command Restrictions

```go
func GitArgs(args []string) error {
    if len(args) == 0 {
        return errors.New("no git subcommand")
    }

    blocked := map[string]bool{
        "config": true,  // Could change credentials
        "remote": true,  // Could add malicious remotes
    }

    if blocked[args[0]] {
        return fmt.Errorf("git %s not allowed", args[0])
    }

    // Block force push
    if args[0] == "push" {
        for _, arg := range args {
            if arg == "-f" || arg == "--force" {
                return errors.New("force push not allowed")
            }
        }
    }

    return nil
}
```

### Claude Prompt Sanitization

```go
func ClaudePrompt(prompt string) error {
    // Check for null bytes
    if strings.ContainsRune(prompt, 0) {
        return errors.New("null bytes not allowed")
    }

    // Check for control characters
    for _, r := range prompt {
        if r < 32 && r != '\n' && r != '\r' && r != '\t' {
            return errors.New("control characters not allowed")
        }
    }

    return nil
}
```

## Rate Limiting

### Request Rate Limiting

Token bucket algorithm limits requests per API key:

```go
type RateLimiter struct {
    rate      rate.Limit  // Requests per second
    burst     int         // Maximum burst size
    limiters  sync.Map    // Per-key limiters
}

func (l *RateLimiter) Allow(key string) bool {
    limiter := l.getLimiter(key)
    return limiter.Allow()
}
```

### Concurrent Command Limiting

Limits active commands per project:

```go
type CommandLimiter struct {
    maxConcurrent int
    active        map[string]int
    mu            sync.Mutex
}

func (l *CommandLimiter) TryAcquire(projectID string) bool {
    l.mu.Lock()
    defer l.mu.Unlock()

    if l.active[projectID] >= l.maxConcurrent {
        return false
    }
    l.active[projectID]++
    return true
}
```

### Rate Limit Headers

Responses include rate limit information:

```
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1642089600
```

## Network Security

### Kubernetes Network Policy

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: rdev-api-policy
spec:
  podSelector:
    matchLabels:
      app: rdev-api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: databases
      ports:
        - protocol: TCP
          port: 5432
```

### Pod Security

```yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
```

## RBAC

### Service Account Permissions

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: rdev-api-role
rules:
  # Read pods for project discovery
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  # Execute commands in pods
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create"]
  # Read ConfigMaps for project config
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
```

## Security Checklist

### Development
- [ ] All inputs sanitized before use
- [ ] No secrets in code or logs
- [ ] SQL injection protection (parameterized queries)
- [ ] No command injection vectors

### Deployment
- [ ] TLS termination at ingress
- [ ] Network policies applied
- [ ] Pod security context configured
- [ ] RBAC minimized to required permissions

### Operations
- [ ] API keys rotated regularly
- [ ] Audit logs enabled
- [ ] Rate limits configured appropriately
- [ ] IP allowlists for sensitive keys

## Incident Response

### Key Compromise

1. Revoke the compromised key immediately
2. Review audit logs for unauthorized access
3. Issue new key to affected user
4. Investigate source of compromise

### Rate Limit Abuse

1. Identify abusing key from metrics
2. Temporarily lower key's rate limit
3. Contact key owner
4. Consider IP-based blocking if severe
