# Aperture Integration

Connect [Aperture](https://tailscale.com/docs/aperture), Tailscale's AI gateway, to Capsule Security for identity-attributed visibility into every LLM request and inline blocking of prompts that violate policy.

## Overview

Aperture calls external webhooks, called hooks, at defined points in each request. This integration registers two Capsule hooks:

| Hook | Aperture event | Type | Description |
|  --- | --- | --- | --- |
| **capsule** | `entire_request` | Observation | The prompt, model response, tool calls, and estimated cost, sent after the request completes |
| **capsule-guard** | `pre_request` | Blocking | The user prompt, sent before it reaches the model provider - Capsule allows it or blocks it on policy violation |


Aperture authenticates every request with a Tailscale identity, so activity is attributed to the real user:

- Each Aperture user (`login_name`) becomes an agent in the Capsule inventory, named `<login_name>'s Aperture agent`
- The models the user calls and the tools those models invoke become resources linked to that agent
- Each request produces activities for the user prompt, the model response, and each tool call, grouped into sessions by Aperture's `session_id`. Estimated cost is kept with the activity's raw data


Only users whose Aperture grant includes the Capsule hooks are monitored.

## Prerequisites

Before you begin, ensure you have:

- An **Aperture** deployment and permission to edit its configuration
- A **Capsule Security** account with admin access
- Network access from Aperture to your Capsule endpoints


## Step 1: Connect Aperture in Capsule

1. Log in to the **Capsule Security** portal.
2. Navigate to **Integrations** and locate **Aperture**.
3. Click **Connect**. The wizard walks you through the hooks snippet, then a grant snippet, then verification, with your tenant's endpoints and token already filled in.
4. The token is embedded in the hooks snippet, is valid for 365 days, and is shown once, so copy the snippet before moving on.


## Step 2: Add the Capsule Hooks

1. From a device joined to the tailnet, open the gateway's MagicDNS address, `http://<aperture-hostname>.<tailnet>.ts.net` (the hostname is the gateway's machine name in the Tailscale admin console under Machines). In the Aperture dashboard, go to **Administration > Configuration** (`http://<aperture-hostname>/admin/settings/config`). This opens a raw HUJSON editor holding your whole configuration, including `hooks` and `grants`.
2. Paste the hooks snippet from the Capsule wizard right after the opening `{` of the `hooks` object. New gateways ship with a `hooks` section; if yours has none, add `"hooks": {},` first. The snippet holds only the `capsule` and `capsule-guard` entries, each with a trailing comma, so it pastes cleanly. The result looks like this:


```json
{
  "hooks": {
    "capsule": {
      "url": "<events-endpoint>",
      "apikey": "<token>",
      "authorization": "bearer",
      "timeout": "10s"
    },
    "capsule-guard": {
      "url": "<pre-request-endpoint>",
      "apikey": "<token>",
      "authorization": "bearer",
      "timeout": "5s",
      "fail_policy": "fail_open"
    }
  }
}
```

The Capsule wizard fills in `<events-endpoint>`, `<pre-request-endpoint>`, and `<token>` for you - the endpoint hosts depend on your region and environment.

With `authorization` set to `bearer` (the default), Aperture sends `apikey` as `Authorization: Bearer <apikey>`. See [Build a custom webhook](https://tailscale.com/docs/aperture/how-to/build-custom-webhook) and the [configuration reference](https://tailscale.com/docs/aperture/configuration) for the full hook schema.

## Step 3: Attach the Hooks to a Grant

Hooks fire only for requests that match a grant listing them in `send_hooks`. In the same **Administration > Configuration** editor, paste the grant snippet from the Capsule wizard right after the opening `[` of the `grants` array. To monitor only some users, add its `send_hooks` list to the `tailscale.com/cap/aperture` entry of their existing grant instead:

```json
{
  "src": [
    "*"
  ],
  "app": {
    "tailscale.com/cap/aperture": [
      {
        "send_hooks": [
          {
            "name": "capsule",
            "events": [
              "entire_request"
            ],
            "send": [
              "user_message",
              "response_body",
              "tools",
              "estimated_cost"
            ]
          },
          {
            "name": "capsule-guard",
            "events": [
              "pre_request"
            ],
            "send": [
              "user_message"
            ]
          }
        ]
      }
    ]
  }
},
```

This grant has no `models` key, so it's a floating grant: it only attaches the hooks and doesn't widen model access on its own.

This grant attaches the hooks for every user matched by `src: ["*"]`.

If you also attach the `capsule` hook to `tool_call_entire_request`, Capsule de-duplicates events that share a request id.

## How Prevention Works

For each `pre_request` hook, Capsule evaluates the user prompt against your prevention policies and responds with either:

```json
{ "action": "allow" }
```

```json
{ "action": "block", "status_code": 403, "message": "<reason>" }
```

Aperture returns a block to the caller in the model provider's error format.

Prevention applies to the prompt only, because `pre_request` fires before the model call. Model responses and tool calls are observed, not blocked.

### Failure Behavior

`fail_policy` on the `capsule-guard` hook controls what happens when Capsule is unreachable or slower than `timeout`:

| `fail_policy` | Behavior | Tradeoff |
|  --- | --- | --- |
| `fail_open` (recommended) | Aperture lets the request through | Availability first - a Capsule outage never blocks LLM traffic |
| `fail_closed` | Aperture blocks the request with a `503` | Enforcement first - Capsule becomes a hard dependency for every request |


Capsule also fails open internally: if prompt analysis errors, the request is allowed. See [Set up guardrails](https://tailscale.com/docs/aperture/how-to/set-up-guardrails) for the Aperture side of this flow.

## Step 4: Verify the Integration

1. Send a request through Aperture as a user covered by the grant from Step 3.
2. In the **Capsule Security** portal, navigate to **Inventory > Agents** and confirm an agent named `<login_name>'s Aperture agent` appears.
3. Open the agent and confirm the prompt, response, and any tool calls appear in its activity.


## Roll Out to All Devices

Hooks and grants are configured once, on the gateway. Nothing is configured per client.

Each AI client (coding agents, IDEs, SDKs) only needs its API base URL pointed at the gateway. Push that one setting through your MDM or the client's managed-settings mechanism.

Devices must be joined to the tailnet for the hostname to resolve.

See [Aperture: per-client setup](https://tailscale.com/docs/aperture) for client-specific instructions.

## Troubleshooting

| Symptom | Cause | Resolution |
|  --- | --- | --- |
| Aperture logs `401` from Capsule | Wrong or expired token | Reconnect Aperture in Capsule to mint a new token, then update `apikey` on both hooks |
| Nothing appears in Capsule | The grant has no `send_hooks`, or the user is not covered by that grant's `src` | Confirm the grant lists both hooks and its `src` includes the user |
| Prompts are never blocked | No prevention policies configured, or `capsule-guard` is not attached with `pre_request` | Configure prevention policies in Capsule and check the grant from Step 3 |
| Requests fail with `503` | `fail_policy` is `fail_closed` and Capsule was unreachable or exceeded `timeout` | Check network access to Capsule, raise `timeout`, or switch to `fail_open` |
| Config fails validation with `unknown config key "grants[N].send_hooks"` | `send_hooks` placed at the grant's top level instead of inside a capability entry | Nest `send_hooks` inside the `tailscale.com/cap/aperture` entry, as in Step 3 |


## Security Considerations

- **Protect the token.** Anyone holding it can post events to your tenant. Limit who can read the Aperture configuration, and reconnect the integration in Capsule to mint a new token if it is exposed.
- **Use HTTPS endpoints only.** Aperture sends the token on every hook call.


## Support

For help with this integration:

- **Email**: support@capsule.security
- **Include**: Your organization ID, integration status, and any error messages from Aperture


## References

- [Aperture: Build a custom webhook](https://tailscale.com/docs/aperture/how-to/build-custom-webhook)
- [Aperture: Set up guardrails](https://tailscale.com/docs/aperture/how-to/set-up-guardrails)
- [Aperture configuration reference](https://tailscale.com/docs/aperture/configuration)