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

requestID

metadata.request_id

Taken from hook metadata object

principal.id

metadata.login_name

Taken from hook metadata object

principal.roles

"user"

Hardcoded to "user"

principal.attr.stable_node_id

metadata.stable_node_id

Taken from hook metadata object

principal.attr.tailnet_name

metadata.tailnet_name

Taken from hook metadata object

principal.attr.user_agent

metadata.user_agent

Taken from hook metadata object

resource.id

"aperture_hook"

Hardcoded to "aperture_hook"

resource.kind

"aperture_hook"

Can be changed by setting the extension’s resourceKind configuration value

resource.policyVersion

"default"

Can be changed by setting the extension’s resourcePolicyVersion configuration value

resource.scope

""

Can be changed by setting the extension’s resourceScope configuration value

resource.attr.metadata

metadata

The entire metadata object from the hook request

resource.attr.*

*

The other optional fields such as tool_calls and user_message included in the hook. See https://tailscale.com/docs/aperture/configuration#hook-send-types

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.

Example policy restricting access to top secret files
---
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.