# Proxy Permissions

**Page:** foundation/proxy/permissions

[Download Raw Markdown](./foundation/proxy/permissions.md)

---

# Proxy Permissions

**The Hoody Proxy enforces authentication and authorization for ALL container services.** Configure who can access what, at what level, with complete granularity.

After understanding [the proxy architecture](./) and [creating aliases](./aliases/), you need to understand **how to control access** to your container services.

---

## API Endpoints Summary

**Official Technical Reference:**

This Foundation page explains permission concepts and configuration strategies. For complete endpoint documentation:

**Project-Level Permissions:**
- **[GET /api/v1/projects/\{id\}/proxy/permissions](/api/proxy-permissions/)** - Get project permissions
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions](/api/proxy-permissions/)** - Set project permissions
- **[DELETE /api/v1/projects/\{id\}/proxy/permissions](/api/proxy-permissions/)** - Remove project permissions
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions/default](/api/proxy-permissions/)** - Update default policy
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions/state](/api/proxy-permissions/)** - Toggle permissions on/off
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions/groups/\{name\}/ip](/api/proxy-permissions/)** - Add/update IP auth group
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions/groups/\{name\}/jwt](/api/proxy-permissions/)** - Add/update JWT auth group
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions/groups/\{name\}/password](/api/proxy-permissions/)** - Add/update password auth group
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions/groups/\{name\}/token](/api/proxy-permissions/)** - Add/update token auth group
- **[DELETE /api/v1/projects/\{id\}/proxy/permissions/groups/\{name\}](/api/proxy-permissions/)** - Remove auth group
- **[PATCH /api/v1/projects/\{id\}/proxy/permissions/permissions/\{name\}](/api/proxy-permissions/)** - Set group program permissions
- **[DELETE /api/v1/projects/\{id\}/proxy/permissions/permissions/\{name\}](/api/proxy-permissions/)** - Remove group permissions
- **[DELETE /api/v1/projects/\{id\}/proxy/permissions/permissions/\{name\}/\{program\}](/api/proxy-permissions/)** - Remove per-program permission

**Container-Level Permissions:**
- **[GET /api/v1/containers/\{id\}/proxy/permissions](/api/proxy-permissions/)** - Get container permissions
- **[PATCH /api/v1/containers/\{id\}/proxy/permissions](/api/proxy-permissions/)** - Set container permissions (overrides project)
- **[DELETE /api/v1/containers/\{id\}/proxy/permissions](/api/proxy-permissions/)** - Remove container permissions
- **[PATCH /api/v1/containers/\{id\}/proxy/permissions/state](/api/proxy-permissions/)** - Toggle container permissions on/off
- **[PATCH /api/v1/containers/\{id\}/proxy/permissions/default](/api/proxy-permissions/)** - Update container default policy
- **[DELETE /api/v1/containers/\{id\}/proxy/permissions/groups/\{name\}](/api/proxy-permissions/)** - Remove specific group
- **[PATCH /api/v1/containers/\{id\}/proxy/permissions/groups/\{name\}/ip](/api/proxy-permissions/)** - Add/update container IP auth group
- **[PATCH /api/v1/containers/\{id\}/proxy/permissions/groups/\{name\}/jwt](/api/proxy-permissions/)** - Add/update container JWT auth group
- **[PATCH /api/v1/containers/\{id\}/proxy/permissions/groups/\{name\}/password](/api/proxy-permissions/)** - Add/update container password auth group
- **[PATCH /api/v1/containers/\{id\}/proxy/permissions/groups/\{name\}/token](/api/proxy-permissions/)** - Add/update container token auth group

**Container Proxy Hooks (MITM traffic interception):**
- **[GET /api/v1/containers/\{id\}/proxy/hooks](/foundation/proxy/hooks/)** — List all hooks grouped by service
- **[POST /api/v1/containers/\{id\}/proxy/hooks/\{service\}](/foundation/proxy/hooks/)** — Append or insert a hook
- **[PATCH /api/v1/containers/\{id\}/proxy/hooks/\{service\}/\{hookId\}](/foundation/proxy/hooks/)** — Replace a hook
- **[DELETE /api/v1/containers/\{id\}/proxy/hooks/\{service\}/\{hookId\}](/foundation/proxy/hooks/)** — Remove a hook
- **[PATCH /api/v1/containers/\{id\}/proxy/hooks/\{service\}/\{hookId\}/position](/foundation/proxy/hooks/)** — Move a hook
- See [Proxy Hooks](/foundation/proxy/hooks/) for the full endpoint list + semantics.

**Container Proxy Settings (root enable/default policy):**
- **[GET /api/v1/containers/\{id\}/proxy/settings](/foundation/proxy/settings/)** — Get `enable_proxy` + `default`
- **[PATCH /api/v1/containers/\{id\}/proxy/settings](/foundation/proxy/settings/)** — Update `enable_proxy` and/or `default`


Your container's proxy configuration is split across four resources: **permissions** (this page — groups + per-program access), **hooks** ([MITM scripts](/foundation/proxy/hooks/) that run inside your own `hoody-exec`), **settings** ([root enable/default policy](/foundation/proxy/settings/) with ETag concurrency), and **aliases** ([custom subdomains pointing to this container](/foundation/proxy/aliases/)). When you write a `permissions` document, you can embed `hooks` inline as a field — but the dedicated hook endpoints above are the preferred surface for day-to-day hook CRUD.


---

## The Permission System

**Hoody's permission system is multi-layered:**

```
┌─────────────────────────────────────┐
│  Hoody API Authentication           │  ← User login, API tokens
│  (api.hoody.icu)                    │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│  Hoody Proxy Permissions            │  ← THIS PAGE
│  (Container access control)         │
│                                     │
│  ├─ Project-Level Permissions       │  ← Apply to all containers
│  └─ Container-Level Permissions     │  ← Override for specific containers
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│  Container Services                 │
│  (terminal, display, files, etc.)   │
└─────────────────────────────────────┘
```

**Two separate systems:**
1. **Hoody API Auth** - Access to platform management (create containers, configure firewall)
2. **Proxy Permissions** - Access to container services (execute commands, read files, view displays)

This page covers **Proxy Permissions** - how to control who can use your container's terminal, files, displays, and other services.

---

## Core Concepts

### 1. Groups (WHO Can Access)

**Groups define authentication methods.**

Each group specifies HOW users prove their identity:

- **JWT** - Token-based with secret verification and claims validation
- **Password** - Username/password via HTTP Basic Auth
- **IP** - Allow/deny based on client IP address or CIDR range
- **Token** - Bearer token validation

**Example:**

```json
{
  "groups": {
    "developers": {
      "type": "ip",
      "range": "203.0.113.0/24"
    },
    "customers": {
      "type": "jwt",
      "secret": "your-jwt-secret",
      "sources": ["header:Authorization"]
    },
    "admin": {
      "type": "password",
      "username": "admin",
      "password": "hashed-password",
      "salt": "unique-salt"
    }
  }
}
```

### 2. Permissions (WHAT They Can Access)

**Permissions map groups to programs with flexible instance control.**

Each group gets specific access to container programs using:
- `true` - Allow ALL instances
- `false` - Deny ALL instances
- `number` - Allow ONLY this instance (e.g., `1` allows instance 1)
- `array` - Allow SPECIFIC instances (e.g., `[1, 2]` allows instances 1 and 2)

```json
{
  "permissions": {
    "developers": {
      "terminal": [1, 2],    // Allow terminal instances 1 and 2 only
      "files": true,         // Allow all file service instances
      "display": 1,          // Allow only display instance 1
      "http": true          // Allow all HTTP services
    },
    "customers": {
      "http": true,          // Allow all HTTP services
      "terminal": false,     // Deny all terminal access
      "files": false         // Deny all file access
    },
    "admin": {
      "terminal": true,      // Full access to all terminals
      "files": true,         // Full access to files
      "display": true,       // Full access to all displays
      "http": true,         // Full access to HTTP
      "ssh": true           // SSH access
    }
  }
}
```

### 3. Default Policy (Fallback)

**What happens when no group matches?**

```json
{
  "default": "deny"  // or "allow"
}
```

- **"deny"** - Block access if no group matches (secure by default)
- **"allow"** - Permit access if no group matches (open by default)

### 4. Hierarchy (Project vs Container)

**Permissions can be set at two levels:**

```
Project Level
  ├─ Applies to ALL containers in project
  └─ Good for consistent team access
      ↓
Container Level
  ├─ Overrides project settings for specific container
  └─ Good for exceptions (public container in private project)
```

**If both configured:** Container-level takes precedence.

---

## Default State: Open by Cryptographic URL

**By default, containers have NO proxy permissions configured.**

This means:
- **Anyone with the URL can access** all services
- No authentication required
- No group matching
- Pure "security by unguessable URL"

**The URL contains:**
```
https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-1.node-us.containers.hoody.icu
       └────────24-char hex────┘ └────────24-char hex────┘
```

**2^96 × 2^96 = 2^192 possible combinations** for project+container pair.

**Practically unguessable.** Share URL = grant access — but anyone the URL reaches (pasted into Slack, captured in a screenshot, logged by a referer header, cached by a browser extension) can hit the container directly. Unguessability prevents blind discovery; it does not contain leaks or replace access control.

**This enables:**
- ✅ Instant collaboration (just share URL)
- ✅ Zero configuration (works immediately)
- ✅ Multiplayer by default (anyone with URL can join)

**When you need real access control:** Add [proxy permissions](#adding-permissions) — don't rely on URL secrecy for anything sensitive, and never paste container URLs into public channels, public dashboards, or untrusted third-party tools.

---

## Configuring Permissions

### Project-Level Permissions

**Apply authentication to ALL containers in a project:**


  
    ```bash
    # Set project-level proxy permissions (IP-restricted team access)
    # The If-Match ETag (file:v) comes from a prior `permissions get`.
    hoody projects proxy permissions replace --project $PROJECT_ID \
      --if-match file:v \
      --groups team='{"type": "ip", "range": "203.0.113.0/24"}' \
      --permissions team='{"terminal": [1,2], "files": true, "display": 1, "http": true}' \
      --default deny
    ```
  
  
    ```typescript
    await client.api.proxyPermissionsProject.replace(PROJECT_ID, {
      groups: {
        team: { type: 'ip', range: '203.0.113.0/24' }
      },
      permissions: {
        team: { terminal: [1, 2], files: true, display: 1, http: true }
      },
      default: 'deny'
    });
    ```
  
  
    ```bash
    # The If-Match ETag (file:v) comes from a prior GET of the permissions document.
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{
        "project": "'$PROJECT_ID'",
        "groups": {
          "team": { "type": "ip", "range": "203.0.113.0/24" }
        },
        "permissions": {
          "team": { "terminal": [1, 2], "files": true, "display": 1, "http": true }
        },
        "default": "deny"
      }'
    ```
  




**Now ALL containers in this project:**
- Require IP from `203.0.113.0/24` range
- Grant access to terminal, files, display, http
- Deny all other access

### Container-Level Permissions

**Override project settings for specific container:**


  
    ```bash
    # Override permissions for a specific container (public HTTP only)
    # The If-Match ETag (file:v) comes from a prior `permissions get`.
    hoody containers proxy permissions replace --container $CONTAINER_ID \
      --if-match file:v \
      --groups public='{"type": "ip", "range": "0.0.0.0/0"}' \
      --permissions public='{"http": true, "terminal": false, "files": false}' \
      --default deny
    ```
  
  
    ```typescript
    await client.api.proxyPermissionsContainer.replace(CONTAINER_ID, {
      groups: {
        public: { type: 'ip', range: '0.0.0.0/0' }
      },
      permissions: {
        public: { http: true, terminal: false, files: false }
      },
      default: 'deny'
    });
    ```
  
  
    ```bash
    # The If-Match ETag (file:v) comes from a prior GET of the permissions document.
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{
        "project": "'$PROJECT_ID'",
        "container": "'$CONTAINER_ID'",
        "groups": {
          "public": { "type": "ip", "range": "0.0.0.0/0" }
        },
        "permissions": {
          "public": { "http": true, "terminal": false, "files": false }
        },
        "default": "deny"
      }'
    ```
  




**This container now:**
- Accepts requests from any IP (public access)
- Only HTTP services allowed
- Terminal and files completely denied
- Overrides project-level restrictions

**Use case:** Public demo container in an otherwise private project.

---

## Authentication Groups

**Groups define HOW users authenticate.**

### JWT Authentication

**Validate JSON Web Tokens:**

```json
{
  "groups": {
    "authenticated_users": {
      "type": "jwt",
      "secret": "your-jwt-signing-secret",
      "algorithm": "HS256",
      "sources": ["header:Authorization", "cookie:auth_token"],
      "claims": {
        "iss": "mycompany.com",
        "aud": "production-api"
      }
    }
  }
}
```

**Parameters:**
- `secret` - JWT signing key (required)
- `algorithm` - HS256, RS256, or ES256 (required)
- `sources` - Where to look for the token (required). Each entry must match `header:Name` or `cookie:Name` — JWTs can only be read from a header or a cookie.
- `claims` - Required JWT claims to validate (optional)

**Token sources examples:**
- `header:Authorization` - Bearer token in Authorization header
- `cookie:session` - JWT in session cookie

**Use case:** Your application issues JWTs to users, Hoody validates them at the proxy level.

### Password Authentication

**HTTP Basic Auth with username/password:**

```json
{
  "groups": {
    "admin_access": {
      "type": "password",
      "username": "admin",
      "password": "hashed-password-here",
      "algorithm": "sha256",
      "salt": "unique-salt-value"
    }
  }
}
```

**Parameters:**
- `username` - Exact username match required
- `password` - Plain or hashed password
- `algorithm` - Hashing algorithm (sha256)
- `salt` - Salt for password hashing

**Browser behavior:** Browser will show HTTP Basic Auth prompt automatically.

**Why Password Auth is Underrated:**

While it's an old protocol, **password authentication is remarkably portable**:

- **Works everywhere** - No JWT libraries needed, no token management
- **Browser native** - Built-in auth prompts on all browsers
- **Zero dependencies** - Just username + password, works on any HTTP client
- **Perfect for humans** - Simple, intuitive, memorable credentials
- **URL embeddable** - `https://user:pass@domain.com` works in most contexts
- **Emergency access** - When OAuth is down, password auth still works
- **Low complexity** - No token expiration, no refresh flows, no claims validation

**Use cases:**
- Simple admin access
- Quick demos and prototypes
- Temporary contractor access
- Emergency backdoors
- Internal tools where JWT overhead isn't worth it
- Any scenario where portability > sophistication

### IP-Based Authentication

**Allow/deny by client IP address:**

```json
{
  "groups": {
    "office_network": {
      "type": "ip",
      "range": "203.0.113.0/24"
    },
    "vpn_users": {
      "type": "ip",
      "range": "198.51.100.0/24"
    }
  }
}
```

**Parameters:**
- `range` - IPv4 CIDR notation (e.g., `203.0.113.50/32` for single IP)

**Use case:** Restrict to office network, VPN, known IPs.

**Note:** The proxy sees real client IPs (not proxy IPs) thanks to Hoody's netfilter hooks, so IP-based auth works perfectly.

### Token Authentication

**Validate bearer tokens:**

```json
{
  "groups": {
    "api_partners": {
      "type": "token",
      "value": "token-abc-123",
      "header": "X-Api-Token"
    }
  }
}
```

A token group carries a single `value` plus exactly one of `header`, `cookie`, or `param` specifying where the token is read from.

**Use case:** Distribute tokens to API consumers, partners, integrations.

### Authentication Without Headers (hoody-curl Workaround)

**Problem:** Some environments can't send custom headers:
- Browser bookmarks (just URLs)
- QR codes (GET-only)
- Email links (no header control)
- Restricted platforms (iOS Shortcuts, some automation tools)

**Solution:** Use **hoody-curl** to transform authenticated requests into simple GET URLs.

**How it works:**


  
    ```bash
    # Requires ability to send Authorization header
    curl "https://67e89abc...890abc-terminal-1.node-us.containers.hoody.icu/execute" \
      -H "Authorization: Bearer jwt-token-here" \
      -H "Content-Type: application/json" \
      -d '{"command": "ls -la"}'
    
    # ❌ Can't do this from browser bookmark
    # ❌ Can't do this from QR code
    # ❌ Can't embed in simple URL
    ```
  
  
    ```
    # hoody-curl wraps the request as a GET URL
    https://67e89abc...890abc-curl-1.node-us.containers.hoody.icu/proxy?url=https://67e89abc...890abc-terminal-1.node-us.containers.hoody.icu/execute&method=POST&auth=Bearer%20jwt-token&body={"command":"ls -la"}
    
    # ✅ Works as browser bookmark
    # ✅ Works as QR code
    # ✅ Can be clicked from email
    # ✅ Works in restricted environments
    ```
    
    **The hoody-curl service:**
    1. Receives GET request with parameters
    2. Constructs proper POST request with headers
    3. Sends to target service
    4. Returns response
  


**Practical example - Bookmark to execute deployment:**

```javascript
// Create a bookmark URL that deploys your app
const curlService = "https://67e89abc...890abc-curl-1.node-us.containers.hoody.icu";
const targetService = "https://67e89abc...890abc-exec-1.node-us.containers.hoody.icu/api/deploy";
const authToken = "your-jwt-token";

const bookmarkUrl = `${curlService}/proxy?` + new URLSearchParams({
  url: targetService,
  method: 'POST',
  auth: `Bearer ${authToken}`,
  body: JSON.stringify({ environment: 'production' })
});

// Save as bookmark: "🚀 Deploy Production"
// Click bookmark → Deployment triggered
// No terminal needed, no curl command, just a click
```

**Use cases:**
- **Emergency deployments** - Bookmark for instant deploy
- **Mobile access** - QR code to trigger workflows
- **Email notifications** - "Click here to approve" links
- **Restricted automation** - iOS Shortcuts, Zapier webhooks
- **Simple sharing** - Send URL instead of curl command

**See:** [Hoody cURL →](/kit/curl/) for complete documentation on wrapping HTTP requests.


---

## Program-Specific Permissions

**Each program supports flexible instance control:**

### Permission Value Types

**Four types of access control:**


  
    ```json
    {
      "permissions": {
        "developers": {
          "terminal": true,      // Allow ALL terminal instances
          "files": false         // Deny ALL file service instances
        }
      }
    }
    ```
    
    **Use when:** Simple all-or-nothing access
  
  
    ```json
    {
      "permissions": {
        "customers": {
          "display": 1,          // Allow ONLY display instance 1
          "terminal": 2          // Allow ONLY terminal instance 2
        }
      }
    }
    ```
    
    **Use when:** Grant access to one specific instance
  
  
    ```json
    {
      "permissions": {
        "team": {
          "terminal": [1, 2, 3], // Allow terminal instances 1, 2, and 3
          "display": [1],        // Allow only display instance 1
          "exec": [1, 2]        // Allow exec instances 1 and 2
        }
      }
    }
    ```
    
    **Use when:** Grant access to specific subset of instances
  
  
    ```json
    {
      "permissions": {
        "developers": {
          "terminal": [1, 2],    // Terminals 1 and 2 only
          "files": true,         // All file instances
          "display": 1,          // Only display 1
          "http": true,         // All HTTP services
          "exec": false         // No exec access
        }
      }
    }
    ```
    
    **Use when:** Complex access patterns
  


### Available Programs

**Programs you can configure:**
- `http` - HTTP services (port-based: `http-80`, `http-3000`, etc.)
- `ssh` - SSH access to container
- `terminal` - Hoody Terminal service instances
- `display` - Hoody Display (desktop) instances
- `files` - Hoody Files service
- `exec` - Hoody Exec script execution
- `sqlite` - Hoody SQLite database instances
- `browser` - Hoody Browser automation instances
- `agent` - Hoody Agent AI instances
- Plus: `code`, `curl`, `daemon`, `notifications`

### Why Instance-Level Control Matters

**Real scenario:** Container with 5 terminal instances for different teams:

```json
{
  "permissions": {
    "frontend_team": {
      "terminal": [1, 2],    // Frontend devs get terminals 1 and 2
      "display": 1,
      "http": true
    },
    "backend_team": {
      "terminal": [3, 4],    // Backend devs get terminals 3 and 4
      "display": 2,
      "http": true
    },
    "ops_team": {
      "terminal": true,      // Ops gets ALL terminals
      "display": true,       // ALL displays
      "http": true,
      "ssh": true
    }
  }
}
```

**Each team isolated to specific instances while sharing the same container.**

---

## Complete Configuration Examples

### Example 1: Development Team Access

**Internal team, broad access, IP-restricted:**


  
    ```bash
    # Configure IP-restricted developer access for entire project
    # The If-Match ETag (file:v) comes from a prior `permissions get`.
    hoody projects proxy permissions replace --project $PROJECT_ID \
      --if-match file:v \
      --groups developers='{"type": "ip", "range": "203.0.113.0/24"}' \
      --permissions developers='{"terminal": true, "display": true, "files": true, "http": true, "ssh": true}' \
      --default deny
    ```
  
  
    ```typescript
    await client.api.proxyPermissionsProject.replace(PROJECT_ID, {
      groups: {
        developers: { type: 'ip', range: '203.0.113.0/24' }
      },
      permissions: {
        developers: { terminal: true, display: true, files: true, http: true, ssh: true }
      },
      default: 'deny'
    });
    ```
  
  
    ```bash
    # The If-Match ETag (file:v) comes from a prior GET of the permissions document.
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{
        "project": "'$PROJECT_ID'",
        "groups": {
          "developers": { "type": "ip", "range": "203.0.113.0/24" }
        },
        "permissions": {
          "developers": { "terminal": true, "display": true, "files": true, "http": true, "ssh": true }
        },
        "default": "deny"
      }'
    ```
  




**Result:**
- Developers from office network (203.0.113.0/24) get full access
- Everyone else denied
- Applies to all containers in project

### Example 2: Public API with Private Admin

**Container-level override for public API:**


  
    ```bash
    # Public API (JWT for customers) + private admin group (password)
    # The If-Match ETag (file:v) comes from a prior `permissions get`.
    hoody containers proxy permissions replace --container $CONTAINER_ID \
      --if-match file:v \
      --groups customers='{"type": "jwt", "secret": "customer-jwt-secret", "algorithm": "HS256", "sources": ["header:Authorization"]}' \
      --groups admin='{"type": "password", "username": "admin", "password": "hashed-admin-password", "algorithm": "sha256", "salt": "unique-salt"}' \
      --permissions customers='{"http": true}' \
      --permissions admin='{"terminal": true, "display": true, "files": true, "http": true, "ssh": true}' \
      --default deny
    ```
  
  
    ```typescript
    await client.api.proxyPermissionsContainer.replace(CONTAINER_ID, {
      groups: {
        customers: {
          type: 'jwt', secret: 'customer-jwt-secret',
          algorithm: 'HS256', sources: ['header:Authorization']
        },
        admin: {
          type: 'password', username: 'admin',
          password: 'hashed-admin-password', algorithm: 'sha256', salt: 'unique-salt'
        }
      },
      permissions: {
        customers: { http: true },
        admin: { terminal: true, display: true, files: true, http: true, ssh: true }
      },
      default: 'deny'
    });
    ```
  
  
    ```bash
    # The If-Match ETag (file:v) comes from a prior GET of the permissions document.
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{
        "project": "'$PROJECT_ID'",
        "container": "'$CONTAINER_ID'",
        "groups": {
          "customers": {
            "type": "jwt", "secret": "customer-jwt-secret",
            "algorithm": "HS256", "sources": ["header:Authorization"]
          },
          "admin": {
            "type": "password", "username": "admin",
            "password": "hashed-admin-password", "algorithm": "sha256", "salt": "unique-salt"
          }
        },
        "permissions": {
          "customers": { "http": true },
          "admin": { "terminal": true, "display": true, "files": true, "http": true, "ssh": true }
        },
        "default": "deny"
      }'
    ```
  




**Result:**
- Customers with valid JWT: HTTP API + read-only files
- Admin with password: Full access (terminal, display, all files)
- Everyone else: Denied

### Example 3: Multi-Tier Access

**Different access levels for different teams:**


  
    ```bash
    # Multi-tier: ops (full), developers (partial), readonly (HTTP only)
    # The If-Match ETag (file:v) comes from a prior `permissions get`.
    hoody projects proxy permissions replace --project $PROJECT_ID \
      --if-match file:v \
      --groups ops_team='{"type": "ip", "range": "203.0.113.0/24"}' \
      --groups developers='{"type": "ip", "range": "198.51.100.0/24"}' \
      --groups readonly_users='{"type": "password", "username": "viewer", "password": "hashed-pass", "salt": "salt"}' \
      --permissions ops_team='{"terminal": true, "display": true, "files": true, "http": true, "ssh": true}' \
      --permissions developers='{"terminal": true, "files": true, "http": true, "ssh": false}' \
      --permissions readonly_users='{"http": true, "terminal": false, "display": false, "files": false}' \
      --default deny
    ```
  
  
    ```typescript
    await client.api.proxyPermissionsProject.replace(PROJECT_ID, {
      groups: {
        ops_team: { type: 'ip', range: '203.0.113.0/24' },
        developers: { type: 'ip', range: '198.51.100.0/24' },
        readonly_users: { type: 'password', username: 'viewer', password: 'hashed-pass', salt: 'salt' }
      },
      permissions: {
        ops_team: { terminal: true, display: true, files: true, http: true, ssh: true },
        developers: { terminal: true, files: true, http: true, ssh: false },
        readonly_users: { http: true, terminal: false, display: false, files: false }
      },
      default: 'deny'
    });
    ```
  
  
    ```bash
    # The If-Match ETag (file:v) comes from a prior GET of the permissions document.
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{
        "project": "'$PROJECT_ID'",
        "groups": {
          "ops_team": { "type": "ip", "range": "203.0.113.0/24" },
          "developers": { "type": "ip", "range": "198.51.100.0/24" },
          "readonly_users": { "type": "password", "username": "viewer", "password": "hashed-pass", "salt": "salt" }
        },
        "permissions": {
          "ops_team": { "terminal": true, "display": true, "files": true, "http": true, "ssh": true },
          "developers": { "terminal": true, "files": true, "http": true, "ssh": false },
          "readonly_users": { "http": true, "terminal": false, "display": false, "files": false }
        },
        "default": "deny"
      }'
    ```
  




**Access levels:**
- **Ops team** (203.0.113.0/24): Full access to all programs
- **Developers** (198.51.100.0/24): Terminal, files, HTTP (no SSH, no display)
- **Read-only users** (password auth): Only HTTP access

---

## Permission Hierarchy

**When both project and container permissions exist:**

```
Request arrives at container service URL
        ↓
Check: Does container have permissions configured?
        ↓
   YES → Use container permissions
   NO  → Use project permissions (if configured)
        ↓
If no permissions at either level:
   → Default open (anyone with URL can access)
```

**Example scenario:**

```bash
# Project: Restrict all containers to office IP
PATCH /api/v1/projects/{id}/proxy/permissions
{
  "groups": { "office": { "type": "ip", "range": "203.0.113.0/24" } },
  "permissions": { "office": { "terminal": true, "http": true } },
  "default": "deny"
}

# Container: Override one container for public access
PATCH /api/v1/containers/{id}/proxy/permissions
{
  "groups": { "public": { "type": "ip", "range": "0.0.0.0/0" } },
  "permissions": { "public": { "http": true } },
  "default": "deny"
}
```

**Result:**
- Most containers: Office-only access (terminal + HTTP)
- Public container: Anyone can access HTTP
- Public container: Office can still access terminal (inherits project? No—container config replaces entirely)


**Container-level permissions REPLACE project permissions entirely—they don't merge.** If you want to keep some project permissions, you must re-include them in the container config.


---

## Configuration Endpoints

### Project-Level Operations


  
    ```bash
    # Get current config (read the file_version ETag for If-Match)
    hoody projects proxy permissions get --project $PROJECT_ID

    # Set project permissions
    hoody projects proxy permissions replace --project $PROJECT_ID \
      --if-match file:v \
      --groups <name>='{...}' --permissions <name>='{...}' --default deny

    # Delete all permissions (revert to open)
    hoody projects proxy permissions delete --project $PROJECT_ID --if-match file:v

    # Update default policy only
    hoody projects proxy default --project $PROJECT_ID --if-match file:v --default deny

    # Enable/disable proxy entirely (omit --enable-proxy to disable)
    hoody projects proxy state --project $PROJECT_ID --if-match file:v
    ```
  
  
    ```typescript
    // Get current config
    const config = await client.api.proxyPermissionsProject.get(PROJECT_ID);

    // Set project permissions
    await client.api.proxyPermissionsProject.replace(PROJECT_ID, { ...config });

    // Delete all permissions (revert to open)
    await client.api.proxyPermissionsProject.delete(PROJECT_ID);

    // Update default policy only
    await client.api.proxyPermissionsProject.updateDefault(PROJECT_ID, { default: 'deny' });
    ```
  
  
    ```bash
    # Get current config
    curl "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN"

    # Set project permissions (If-Match ETag from the GET above)
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{...}'

    # Delete all permissions (revert to open)
    curl -X DELETE "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "If-Match: file:v"

    # Update default policy only
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/default" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"default": "deny"}'

    # Enable/disable proxy entirely
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/state" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"enable_proxy": false}'
    ```
  


### Container-Level Operations


  
    ```bash
    # Get container config (read the file_version ETag for If-Match)
    hoody containers proxy permissions get --container $CONTAINER_ID

    # Set container permissions (override project)
    hoody containers proxy permissions replace --container $CONTAINER_ID \
      --if-match file:v \
      --groups <name>='{...}' --permissions <name>='{...}' --default deny

    # Delete container permissions (revert to project-level)
    hoody containers proxy permissions delete --container $CONTAINER_ID --if-match file:v

    # Update default policy
    hoody containers proxy default --container $CONTAINER_ID --if-match file:v --default allow
    ```
  
  
    ```typescript
    // Get container config
    const config = await client.api.proxyPermissionsContainer.get(CONTAINER_ID);

    // Set container permissions (override project)
    await client.api.proxyPermissionsContainer.replace(CONTAINER_ID, { ...config });

    // Delete container permissions (revert to project-level)
    await client.api.proxyPermissionsContainer.delete(CONTAINER_ID);

    // Update default policy
    await client.api.proxyPermissionsContainer.updateDefault(CONTAINER_ID, { default: 'allow' });
    ```
  
  
    ```bash
    # Get container config
    curl "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN"

    # Set container permissions (override project; If-Match ETag from the GET above)
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{...}'

    # Delete container permissions (revert to project-level)
    curl -X DELETE "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN" \
      -H "If-Match: file:v"

    # Update default policy
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions/default" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"default": "allow"}'
    ```
  


### Group Management

**Add/update/remove specific groups without replacing entire config:**


  
    ```bash
    # Add JWT group to project
    hoody projects proxy groups jwt set --project $PROJECT_ID --group-name api-users \
      --if-match file:v \
      --secret "jwt-secret" --algorithm HS256 --sources "header:Authorization"

    # Add IP group to project
    hoody projects proxy groups ip set --project $PROJECT_ID --group-name office \
      --if-match file:v --range "198.51.100.0/24"

    # Remove group entirely
    hoody projects proxy groups delete --project $PROJECT_ID --group-name office \
      --if-match file:v
    ```
  
  
    ```typescript
    // Add JWT group to project
    await client.api.proxyPermissionsProject.setJwtGroup(PROJECT_ID, 'api-users', {
      secret: 'jwt-secret',
      algorithm: 'HS256',
      sources: ['header:Authorization']
    });

    // Add IP group
    await client.api.proxyPermissionsProject.setIpGroup(PROJECT_ID, 'office', {
      range: '198.51.100.0/24'
    });

    // Remove group
    await client.api.proxyPermissionsProject.removeAuthGroup(PROJECT_ID, 'office');
    ```
  
  
    ```bash
    # Add JWT group to project
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/groups/api-users/jwt" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"secret": "jwt-secret", "algorithm": "HS256", "sources": ["header:Authorization"]}'

    # Add IP group
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/groups/office/ip" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"range": "198.51.100.0/24"}'

    # Remove group entirely
    curl -X DELETE "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/groups/office" \
      -H "Authorization: Bearer $TOKEN" \
      -H "If-Match: file:v"
    ```
  


### Permission Management

**Manage program permissions for groups:**


  
    ```bash
    # Set permissions for a group (boolean — all instances)
    hoody projects proxy groups permissions set --project $PROJECT_ID --group-name developers \
      --if-match file:v --program terminal --access true

    # Set permissions (specific instance)
    hoody projects proxy groups permissions set --project $PROJECT_ID --group-name developers \
      --if-match file:v --program display --access 1

    # Set permissions (multiple instances)
    hoody projects proxy groups permissions set --project $PROJECT_ID --group-name developers \
      --if-match file:v --program terminal --access "[1,2,3]"

    # Remove all permissions for a group
    hoody projects proxy groups permissions clear --project $PROJECT_ID --group-name developers \
      --if-match file:v

    # Remove specific program permission
    hoody projects proxy groups permissions delete --project $PROJECT_ID --group-name developers \
      --if-match file:v --program terminal
    ```
  
  
    ```typescript
    // Set permissions (all terminal instances)
    await client.api.proxyPermissionsProject.setGroup(PROJECT_ID, 'developers', {
      program: 'terminal', access: true
    });

    // Set permissions (specific instance)
    await client.api.proxyPermissionsProject.setGroup(PROJECT_ID, 'developers', {
      program: 'display', access: 1
    });

    // Set permissions (multiple instances)
    await client.api.proxyPermissionsProject.setGroup(PROJECT_ID, 'developers', {
      program: 'terminal', access: [1, 2, 3]
    });

    // Remove all permissions for a group
    await client.api.proxyPermissionsProject.removeGroup(PROJECT_ID, 'developers');
    ```
  
  
    ```bash
    # Set permissions for a group (boolean)
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/permissions/developers" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"program": "terminal", "access": true}'

    # Set permissions (specific instance)
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/permissions/developers" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"program": "display", "access": 1}'

    # Set permissions (multiple instances)
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/permissions/developers" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"program": "terminal", "access": [1, 2, 3]}'

    # Remove all permissions for a group
    curl -X DELETE "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/permissions/developers" \
      -H "Authorization: Bearer $TOKEN" \
      -H "If-Match: file:v"

    # Remove specific program permission
    curl -X DELETE "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/permissions/developers/terminal" \
      -H "Authorization: Bearer $TOKEN" \
      -H "If-Match: file:v"
    ```
  


---

## Real-World Scenarios

### Scenario 1: Open Development → Locked Production

**Development phase** (use cryptographic URLs):

```
No permissions configured
→ Anyone with URL can access
→ Share URLs with team for instant collaboration
→ Perfect for rapid iteration
```

**Staging phase** (add basic restrictions):

```bash
PATCH /api/v1/projects/{id}/proxy/permissions
{
  "groups": {
    "team": { "type": "ip", "range": "203.0.113.0/24" }
  },
  "permissions": {
    "team": { "terminal": true, "http": true, "display": true }
  },
  "default": "deny"
}
```

**Production phase** (strict JWT auth):

```bash
# Override production container only
PATCH /api/v1/containers/{prod_id}/proxy/permissions
{
  "groups": {
    "customers": {
      "type": "jwt",
      "secret": "production-jwt-secret",
      "algorithm": "HS256",
      "sources": ["header:Authorization"]
    }
  },
  "permissions": {
    "customers": { "http": true }
  },
  "default": "deny"
}
```

**Result:**
- Dev containers: Open (cryptographic URLs)
- Staging containers: Office IP only
- Production container: JWT required

### Scenario 2: Customer Support Access

**Give support team temporary terminal access:**

```bash
# Add support group to specific container
PATCH /api/v1/containers/{id}/proxy/permissions/groups/support/password
{
  "username": "support",
  "password": "temporary-password-hash",
  "salt": "salt"
}

# Grant specific access to support team
PATCH /api/v1/containers/{id}/proxy/permissions/permissions/support
{
  "program": "terminal",
  "access": 1                // Only terminal 1
}

PATCH /api/v1/containers/{id}/proxy/permissions/permissions/support
{
  "program": "display",
  "access": 1                // Only display 1
}

PATCH /api/v1/containers/{id}/proxy/permissions/permissions/support
{
  "program": "files",
  "access": true             // All file instances
}
```

**Support can:**
- ✅ Access terminal instance 1 only
- ✅ View display instance 1 only
- ✅ Use all file service instances

**Instance control prevents accidental access** to other terminals/displays used by your team.

**After support session:** Delete the support group or disable it.

### Scenario 3: API Partners with Rate Limiting

**Different token tiers for partners:**

```bash
# Mutating /proxy/permissions requires an If-Match: file:v header (ETag from a prior GET).
PATCH /api/v1/containers/{api_id}/proxy/permissions
{
  "groups": {
    "tier1_partners": {
      "type": "token",
      "value": "partner-abc-tier1",
      "header": "X-Api-Token"
    },
    "tier2_partners": {
      "type": "token",
      "value": "partner-def-tier2",
      "header": "X-Api-Token"
    }
  },
  "permissions": {
    "tier1_partners": {
      "http": true,            // All HTTP services
      "files": true           // All file instances
    },
    "tier2_partners": {
      "http": [80, 3000],     // Only HTTP on ports 80 and 3000
      "files": 1              // Only file instance 1
    }
  },
  "default": "deny"
}
```

---

## Permission Testing

### Verify Configuration

**After setting permissions, test each group:**


  
    ```bash
    # Get current config
    hoody projects proxy permissions get --project $PROJECT_ID

    # Test access from your machine (for IP groups)
    curl "https://67e89abc...890abc-terminal-1.node-us.containers.hoody.icu"

    # Test with Basic Auth (for password groups)
    curl -u "username:password" \
      "https://67e89abc...890abc-terminal-1.node-us.containers.hoody.icu"

    # Test with JWT (for JWT groups)
    curl -H "Authorization: Bearer eyJhbG..." \
      "https://67e89abc...890abc-http-8080.node-us.containers.hoody.icu/api/endpoint"
    ```
  
  
    ```typescript
    // Get current config to verify
    const config = await client.api.proxyPermissionsProject.get(PROJECT_ID);
    console.log(JSON.stringify(config.data, null, 2));

    // Test access programmatically via container client
    const containerClient = await client.withContainer({
      id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER
    });
    const result = await containerClient.terminal.execution.execute({
      command: 'echo "access works"'
    });
    ```
  
  
    ```bash
    # Get current config
    curl "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions" \
      -H "Authorization: Bearer $TOKEN"

    # Test with IP group (from allowed IP)
    curl "https://67e89abc...890abc-terminal-1.node-us.containers.hoody.icu"

    # Test with password group (Basic Auth)
    curl "https://67e89abc...890abc-terminal-1.node-us.containers.hoody.icu" \
      -u "username:password"

    # Test with JWT group (Bearer token)
    curl "https://67e89abc...890abc-http-8080.node-us.containers.hoody.icu/api/endpoint" \
      -H "Authorization: Bearer eyJhbG..."

    # From denied IP or without auth (should return 401/403)
    curl "https://67e89abc...890abc-terminal-1.node-us.containers.hoody.icu"
    ```
  


### Debugging Access Issues

**If access is denied unexpectedly:**

1. **Check group matches:**
   ```bash
   GET /api/v1/projects/{id}/proxy/permissions
   # Verify group exists and credentials/IP match
   ```

2. **Check program permissions:**
   ```json
   // Ensure the program is allowed for the group
   "permissions": {
     "yourgroup": {
       "terminal": true  // Must be explicitly true
     }
   }
   ```

3. **Check default policy:**
   ```json
   "default": "deny"  // If no group matches, deny
   ```

4. **Check container override:**
   ```bash
   GET /api/v1/containers/{id}/proxy/permissions
   # Container config might override project
   ```

5. **Check proxy enabled:**
   ```json
   "enable_proxy": true  // Must be true
   ```

---

## Security Best Practices

### 1. Start with Default Deny

```json
{
  "default": "deny"
}
```

**Why:** Explicit allow is more secure than implicit allow. If you add new programs later, they're denied by default until you explicitly permit them.

### 2. Use Least Privilege

**Grant minimum necessary access:**

```json
{
  "permissions": {
    "api_users": {
      "http": [80],           // Only HTTP on port 80 (public API)
      "terminal": false,      // No terminal access
      "files": false,         // No file access
      "display": false,       // No display access
      "exec": false          // No exec access
    }
  }
}
```

### 3. Layer Security

**Combine multiple authentication methods:**

```json
{
  "groups": {
    "secure_access": {
      "type": "ip",
      "range": "203.0.113.0/24"  // Must be from office
    },
    "with_jwt": {
      "type": "jwt",
      "secret": "jwt-secret",
      "sources": ["header:Authorization"]  // AND must have valid JWT
    }
  }
}
```

**Users must satisfy BOTH:** Be from office IP AND provide valid JWT.

### 4. Audit Permissions Regularly

```bash
# List all project permissions
curl "https://api.hoody.icu/api/v1/projects" \
  -H "Authorization: Bearer $HOODY_TOKEN"

# For each project, check permissions
curl "https://api.hoody.icu/api/v1/projects/{id}/proxy/permissions" \
  -H "Authorization: Bearer $HOODY_TOKEN"

# Look for:
# - Overly broad IP ranges (0.0.0.0/0)
# - Expired access that should be removed
# - Groups no longer needed
```

---

## Disabling the Proxy

**Completely disable proxy for a project/container:**


  
    ```bash
    # Disable at project level (all containers unreachable; omit --enable-proxy to disable)
    hoody projects proxy state --project $PROJECT_ID --if-match file:v

    # Disable at container level (this container unreachable)
    hoody containers proxy state --container $CONTAINER_ID --if-match file:v

    # Re-enable
    hoody containers proxy state --container $CONTAINER_ID --if-match file:v --enable-proxy
    ```
  
  
    ```typescript
    // Disable at project level
    await client.api.proxyPermissionsProject.updateState(PROJECT_ID, { enable_proxy: false });

    // Disable at container level
    await client.api.proxyPermissionsContainer.updateState(CONTAINER_ID, { enable_proxy: false });

    // Re-enable
    await client.api.proxyPermissionsContainer.updateState(CONTAINER_ID, { enable_proxy: true });
    ```
  
  
    ```bash
    # Disable at project level (all containers unreachable; If-Match ETag from a prior GET)
    curl -X PATCH "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/proxy/permissions/state" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"enable_proxy": false}'

    # Disable at container level (this container unreachable)
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions/state" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"enable_proxy": false}'

    # Re-enable
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions/state" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: file:v" \
      -d '{"enable_proxy": true}'
    ```
  


**When disabled:**
- All service URLs return 503 Service Unavailable
- Container still running (just not accessible via proxy)
- Useful for maintenance or security lockdown

---

## Permission Configuration Reference

### Full Configuration Structure

```json
{
  "project": "string (required, project ID)",
  "container": "string (optional, for container-level only)",
  "groups": {
    "{groupName}": {
      "type": "jwt" | "password" | "ip" | "token",
      // ... type-specific fields
    }
  },
  "permissions": {
    "{groupName}": {
      "terminal": true | false | number | [number],
      "display": true | false | number | [number],
      "files": true | false | number | [number],
      "http": true | false | number | [number],
      "ssh": true | false | number | [number],
      "exec": true | false | number | [number],
      "sqlite": true | false | number | [number],
      "browser": true | false | number | [number],
      "agent": true | false | number | [number]
    }
  },
  "default": "allow" | "deny",
  "enable_proxy": true | false  // optional, defaults to true
}
```

> `enable_proxy` is an **optional** field of the permissions document body (it defaults to `true` and is persisted in the document's settings alongside `default`). To flip it without rewriting the whole document, use the dedicated proxy state/settings endpoint instead (`PATCH .../proxy/permissions/state` or `PATCH .../proxy/settings`).

### Authentication Type Fields

**JWT:**
```json
{
  "type": "jwt",
  "secret": "string (required)",
  "algorithm": "HS256" | "RS256" | "ES256" (required)",
  "sources": ["string"] (required, e.g., ["header:Authorization"]),
  "claims": {} (optional, required JWT claims)
}
```

**Password:**
```json
{
  "type": "password",
  "username": "string (required)",
  "password": "string (required, plain or hashed)",
  "algorithm": "sha256" (optional)",
  "salt": "string (required)"
}
```

**IP:**
```json
{
  "type": "ip",
  "range": "string (required, IPv4 CIDR)"
}
```

**Token:**
```json
{
  "type": "token",
  "value": "string (required, the token to match)",
  "header": "string" | "cookie": "string" | "param": "string"
  // exactly one of header | cookie | param specifying where the token is read from
}
```

---

## Useful Questions

### Can I use multiple authentication methods for the same group?

No. Each group uses exactly one authentication type (JWT, password, IP, or token). However, you can create multiple groups with different auth methods and all will be checked. If any group matches, the user gets access with that group's permissions.

### Do container-level permissions merge with project-level permissions?

No, they **replace** entirely. If you set container-level permissions, the project-level config is completely ignored for that container. If you want to keep some project settings, you must re-include them in the container configuration.

### What happens if no permissions are configured at all?

The container is **open by default** - anyone with the URL can access all services. This is intentional for rapid development and instant collaboration. The cryptographic URL (2^192 combinations) provides security through obscurity.

### Can I restrict access to specific HTTP ports?

Yes! Use instance numbers for the `http` program. For example, `"http": [80, 3000]` allows only ports 80 and 3000. Each port is treated as an instance.

### How do I temporarily disable access to a container?

Use the proxy state endpoint:
```bash
PATCH /api/v1/containers/{id}/proxy/permissions/state
{ "enable_proxy": false }
```
All service URLs will return 503 until you re-enable.

### Can IP authentication work with dynamic IPs?

IP auth requires static IPs or CIDR ranges. For dynamic IPs, use JWT or token authentication instead, or combine IP with a VPN that provides static exit IPs.

### What's the difference between "default": "allow" and no permissions?

- **No permissions configured**: Open by default, no auth required
- **"default": "allow"**: If groups exist but none match, still allow access
- **"default": "deny"**: If groups exist but none match, deny access

Use `"default": "deny"` for security when you have authentication groups.

### Can I see who accessed my containers?

Not through the proxy permissions system directly. For access logging, use:
- Container firewall logs for connection attempts
- Service-level logging (terminal, exec, etc. all support logging)
- MITM via hoody-exec to log all HTTP traffic

### How do I rotate JWT secrets or tokens?

1. Add new group with new secret/tokens
2. Update client applications to use new credentials
3. Verify new group works
4. Delete old group: `DELETE /api/v1/projects/{id}/proxy/permissions/groups/{oldGroupName}`

### Can password authentication use bcrypt or argon2?

Currently only SHA256 is supported for password hashing. For stronger auth, use JWT with a proper authentication service.

---

## What's Next

**Your proxy is now secure:**

1. ✅ **Authentication configured** - Groups define who can access
2. ✅ **Permissions set** - Programs define what they can do
3. ✅ **Default policy chosen** - Deny by default for security

**Explore related security:**
- **[Container Firewall →](/foundation/networking/firewall/)** - Network-level rules (ingress/egress)
- **[Container Network →](/foundation/networking/network/)** - Proxy/VPN routing
- **[IPv4 Management →](/foundation/networking/ipv4/)** - Dedicated IP addresses

---

> **Default: Open by cryptographic URL.**  
> **Project-level: Consistent team access.**  
> **Container-level: Production security.**  
> **Complete control over who accesses what.**

**From zero-config collaboration to enterprise-grade security—all through HTTP.**