Skip to content

Connect 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:

HookAperture eventTypeDescription
capsuleentire_requestObservationThe prompt, model response, tool calls, and estimated cost, sent after the request completes
capsule-guardpre_requestBlockingThe 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:

{
  "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 and the configuration reference 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:

{
  "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:

{ "action": "allow" }
{ "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_policyBehaviorTradeoff
fail_open (recommended)Aperture lets the request throughAvailability first - a Capsule outage never blocks LLM traffic
fail_closedAperture blocks the request with a 503Enforcement 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 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 for client-specific instructions.


Troubleshooting

SymptomCauseResolution
Aperture logs 401 from CapsuleWrong or expired tokenReconnect Aperture in Capsule to mint a new token, then update apikey on both hooks
Nothing appears in CapsuleThe grant has no send_hooks, or the user is not covered by that grant's srcConfirm the grant lists both hooks and its src includes the user
Prompts are never blockedNo prevention policies configured, or capsule-guard is not attached with pre_requestConfigure prevention policies in Capsule and check the grant from Step 3
Requests fail with 503fail_policy is fail_closed and Capsule was unreachable or exceeded timeoutCheck 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 entryNest 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