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.
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.
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
Log in to the Capsule Security portal.
Navigate to Integrations and locate Aperture.
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.
The token is embedded in the hooks snippet, is valid for 365 days, and is shown once, so copy the snippet before moving on.
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, includinghooksandgrants.Paste the hooks snippet from the Capsule wizard right after the opening
{of thehooksobject. New gateways ship with ahookssection; if yours has none, add"hooks": {},first. The snippet holds only thecapsuleandcapsule-guardentries, 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.
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.
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.
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 for the Aperture side of this flow.
Send a request through Aperture as a user covered by the grant from Step 3.
In the Capsule Security portal, navigate to Inventory > Agents and confirm an agent named
<login_name>'s Aperture agentappears.Open the agent and confirm the prompt, response, and any tool calls appear in its activity.
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.
| 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 |
- 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.
For help with this integration:
- Email: support@capsule.security
- Include: Your organization ID, integration status, and any error messages from Aperture