Guardrails for Aperture by Tailscale
Synapse ships with a built-in system route extension for integrating with Aperture by Tailscale to secure AI workloads. When configured as a guardrail, it can enforce Cerbos policies on LLM requests before they leave your perimeter.
The requirements for enabling the Aperture integration are:
Configure the Aperture route extension
Add the following configuration block to Synapse configuration file.
extensions:
routeExtensions:
aperture:
extension:
extensionURL: "system://aperture"
routes:
"/aperture": ["POST"]
You can optionally configure the route extension with the Cerbos resource policy, version and scope it should use for making access decisions.
extensions:
routeExtensions:
aperture:
extension:
extensionURL: "system://aperture"
configuration:
resourceKind: "aperture_hook" (1)
resourcePolicyVersion: "production" (2)
resourceScope: "acme.ai" (3)
routes:
"/aperture": ["POST"]
| 1 | Resource policy name. Defaults to aperture_hook. |
| 2 | Resource policy version. Defaults to default. |
| 3 | Resource policy scope. Defaults to empty. |
| If the resource policy does not exist, LLM requests will be allowed to go through. This is different from the standard PDP behaviour of denying access if no policy is found. |
Configure an Aperture hook
Once Synapse is configured with the Aperture route extension as described above, the final step is to enforce policies by configuring your Aperture gateway to send requests to the endpoint. Refer to https://tailscale.com/docs/aperture/configuration#hooks for full details. The minimal configuration required is as follows:
{
"hooks": {
"cerbos-synapse": {
"url": "https://cerbos-synapse.tail9999999.ts.net/ext/aperture", (1)
},
},
"grants": [{
"src": ["*"],
"app": {"tailscale.com/cap/aperture": [{"models": "**", "send_hooks": [{
"name": "cerbos-synapse",
"events": ["pre_request"],
"send": [
"tools",
"user_message",
"estimated_cost",
"grants",
"quotas",
],
}]}]},
}]
}
| 1 | Replace with the full service URL. See Run as a Tailscale service. |
Define a policy
Synapse converts Aperture hook payloads into Cerbos CheckResources requests of the following shape:
| CheckResources field | Value | Description |
|---|---|---|
|
|
Taken from hook |
|
|
Taken from hook |
|
|
Hardcoded to "user" |
|
|
Taken from hook |
|
|
Taken from hook |
|
|
Taken from hook |
|
|
Hardcoded to "aperture_hook" |
|
|
Can be changed by setting the extension’s |
|
|
Can be changed by setting the extension’s |
|
|
Can be changed by setting the extension’s |
|
|
The entire |
|
|
The other optional fields such as |
If the specified policy (aperture_hook by default) does not exist, the LLM request is allowed to proceed. If the policy exists, the decision returned by the policy execution is converted to an allow or block response expected by Aperture. Policy authors can optionally override the hook response using policy outputs. Note that if a policy output is produced, it’s merged with the default hook response and overwrites any existing fields. Therefore, be aware of copy-paste errors where EFFECT_ALLOW rules still produce outputs with {"action": "block"} and vice versa.
---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
resource: aperture_hook
version: production
rules:
- actions: ["hook_event"]
effect: EFFECT_DENY
roles: ["*"]
condition:
match:
expr: |-
R.attr.tool_calls.exists(tc, tc.params.command.contains("/top/secret"))
output:
when:
ruleActivated: |-
{"message": "Cannot access top secret data"}
- actions: ["hook_event"]
effect: EFFECT_ALLOW
roles: ["*"]
output:
when:
ruleActivated: |-
{"message": "Allowed by Cerbos policy"}
Advanced uses
The built-in Aperture route extension provided by Synapse is designed for general-purpose use. If you require more advanced handling that cannot be easily defined using the primitives provided by Cerbos policies (classifying prompts using another LLM to obtain a risk score, for example), a custom extension would be more suited for those purposes. Refer to custom extension development documentation for more information.