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

# Proxy RBAC & Cloud Auth

> Deny-first access control, ES256 JWT tokens, and subagent policy narrowing

# Proxy RBAC & Cloud Auth

The proxy enforces **role-based access control** using policies embedded in JWT tokens from the Quint [auth service](/cloud/auth-overview). Evaluation is deny-first — every action must pass all checks to be allowed.

## Token Types

Quint uses 6 token types, each identified by a prefix:

| Prefix         | Type       | Purpose                                         |
| -------------- | ---------- | ----------------------------------------------- |
| `qt_app_`      | `app`      | Application-level access                        |
| `qt_bearer_`   | `bearer`   | Environment-scoped access (has `env` field)     |
| `qt_agent_`    | `agent`    | Agent identity with RBAC policy                 |
| `qt_subagent_` | `subagent` | Child agent with narrowed permissions           |
| `qt_session_`  | `session`  | Time/event-limited session                      |
| `qt_override_` | `override` | One-time decision override for a specific event |

All tokens are **ES256** (ECDSA P-256) JWTs. The proxy validates signatures against the auth service's public key, which is fetched and cached with a configurable TTL (default: 5 minutes).

## Deny-First Evaluation

Every action goes through 6 sequential checks. The first failure stops evaluation.

<Steps>
  <Step title="Step 1: Denied Actions">
    If the action matches any pattern in `denied_actions` → **DENY**.
  </Step>

  <Step title="Step 2: Allowed Actions">
    If `allowed_actions` is non-empty and the action matches none → **DENY**.
  </Step>

  <Step title="Step 3: Denied Resources">
    If the resource matches any pattern in `denied_resources` → **DENY**.
  </Step>

  <Step title="Step 4: Allowed Resources">
    If `allowed_resources` is non-empty and the resource matches none → **DENY**.
  </Step>

  <Step title="Step 5: Sensitivity Level">
    If the resource's sensitivity level exceeds the policy's limit → **DENY**.
  </Step>

  <Step title="Step 6: Allowed">
    All checks passed → **ALLOW**.
  </Step>
</Steps>

## Glob Pattern Matching

Actions and resources are matched using glob patterns with colon-aware wildcards:

| Pattern        | Matches                       | Does Not Match                  |
| -------------- | ----------------------------- | ------------------------------- |
| `mcp:github:*` | `mcp:github:list_repos.list`  | `mcp:slack:post.send`           |
| `mcp:**`       | `mcp:github:list_repos.list`  | `http:api.openai.com:POST.chat` |
| `mcp:*:*.read` | `mcp:postgres:query.read`     | `mcp:postgres:query.write`      |
| `*:*:*.delete` | `mcp:s3:remove_object.delete` | `mcp:s3:list_objects.list`      |

* `*` matches within a single colon-separated segment
* `**` matches across segments (recursive)

## RBAC Policy Structure

Each agent/subagent token carries an RBAC policy in its JWT claims:

```json theme={null}
{
  "sub": "customer-uuid",
  "typ": "agent",
  "agent_id": "support-bot",
  "rbac": {
    "allowed_actions": ["mcp:slack:*", "mcp:notion:*"],
    "denied_actions": ["mcp:**:*.delete", "mcp:**:*.execute"],
    "allowed_resources": ["*"],
    "denied_resources": ["vault/*", "*/credentials"],
    "sensitivity_level": 2,
    "max_risk_score": 75
  }
}
```

| Field               | Type       | Description                                                                                                                                                      |
| ------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowed_actions`   | `string[]` | Glob patterns for permitted actions (empty = allow all)                                                                                                          |
| `denied_actions`    | `string[]` | Glob patterns for blocked actions (checked first)                                                                                                                |
| `allowed_resources` | `string[]` | Glob patterns for permitted resources                                                                                                                            |
| `denied_resources`  | `string[]` | Glob patterns for blocked resources                                                                                                                              |
| `sensitivity_level` | `int`      | Max data sensitivity the agent can access (0-4)                                                                                                                  |
| `max_risk_score`    | `int`      | Threshold (0-100) that gates the shadow-mode corroborating risk signal — not the primary block decision, which is a scope violation against a sensitive resource |

## Subagent Policy Narrowing

When a parent agent spawns a child, the child's RBAC policy is automatically **narrowed** — it can never exceed the parent's permissions:

| Constraint        | Rule                                                         |
| ----------------- | ------------------------------------------------------------ |
| Allowed actions   | `child ⊆ parent` (intersection)                              |
| Denied actions    | `child ⊇ parent` (union — child inherits all parent denials) |
| Sensitivity level | `child ≤ parent` (minimum)                                   |
| Max risk score    | `child ≤ parent` (minimum)                                   |

```
Parent: allowed_actions = ["mcp:github:*", "mcp:slack:*"]
Child:  allowed_actions = ["mcp:github:*.read"]
Result: valid (child is a subset)

Parent: denied_actions = ["mcp:**:*.delete"]
Child:  denied_actions = ["mcp:**:*.delete", "mcp:**:*.execute"]
Result: valid (child is a superset)

Parent: sensitivity_level = 3
Child:  sensitivity_level = 4
Result: INVALID (child exceeds parent)
```

<Warning>
  Narrowing is validated cryptographically — a subagent token with wider permissions than its parent will be rejected during JWT validation.
</Warning>

## Local Scope Hierarchy

For agents without cloud tokens, the proxy uses a local scope system:

| Scope           | Implies                     |
| --------------- | --------------------------- |
| `tools:admin`   | `tools:write`, `tools:read` |
| `tools:execute` | `tools:read`                |
| `tools:write`   | `tools:read`                |
| `tools:read`    | (base level)                |

Tool names are mapped to required scopes:

| Tool Prefix/Keyword                 | Required Scope              |
| ----------------------------------- | --------------------------- |
| `delete`, `remove`, `drop`          | `tools:admin`               |
| `execute`, `shell`, `bash`, `run`   | `tools:execute`             |
| `write`, `create`, `update`, `edit` | `tools:write`               |
| `read`, `get`, `list`, `search`     | `tools:read`                |
| Unknown                             | `tools:write` (fail-closed) |

## Cloud Key Validation

The proxy fetches the auth service's public key to validate JWT signatures:

```
GET {auth_service_url}/keys/public/{customer_id}
```

| Setting               | Default | Description                |
| --------------------- | ------- | -------------------------- |
| `base_url`            | —       | Auth service URL           |
| `customer_id`         | —       | Customer ID for key lookup |
| `timeout_ms`          | 5000    | Validation timeout         |
| `key_refresh_seconds` | 300     | Public key cache TTL       |

```json theme={null}
{
  "auth_service": {
    "base_url": "https://api.quintai.dev",
    "customer_id": "a1b2c3d4",
    "enabled": true,
    "timeout_ms": 5000,
    "key_refresh_seconds": 300
  }
}
```

Environment variable `QUINT_TOKEN` can provide a cloud JWT token for stdio relay mode.

## Token Resolution Flow

```mermaid theme={null}
flowchart TD
    Token["`Token received`"] --> Check{"`Has qt_ prefix?`"}
    Check -->|Yes| Cloud["`Cloud JWT validation`"]
    Check -->|No| Local["`Local API key lookup`"]
    Cloud --> Verify["`ES256 signature check`"]
    Verify --> Claims["`Extract claims + RBAC`"]
    Claims --> Identity["`Build Identity`"]
    Local --> Identity
    Identity --> RBAC["`6-step RBAC evaluation`"]
```
