SAM Policy & Authorization Reference
SAM uses a decentralized authorization model powered by Biscuit.
The sam-control-plane authenticates users via OIDC and injects Facts into their token based on policies.yaml. The sam-node operates offline, evaluating the token against baseline rules and optional local attenuation policies.
[!IMPORTANT] Default-Deny Security Posture SAM enforces a strict default-deny model. By default, when a node joins the mesh, all of its services (MCP tools, LLM inference endpoints) are completely locked down and inaccessible to other peers. Access is only permitted if the caller presents a Biscuit token containing capability facts (e.g.,
granted_service_exact(...)) explicitly issued by the control plane based on matched user roles. There are no built-in exceptions; even the catalog service (system://sam.catalog) must be explicitly permitted (e.g., via a role mapping) to facilitate peer-to-peer discovery.
1. OIDC to Biscuit Translation
The control plane automatically translates OIDC claims into undeniable cryptographic facts:
| OIDC Claim / Data | Biscuit Fact | Description |
|---|---|---|
sub | user("<sub-id>") | The unique subject ID from the identity provider. |
email | email("<email>") | The user’s email address (if present). |
groups | group("<group-name>") | One fact is injected for each group the user possesses. |
roles / Resolved Roles | role("<role-name>") | One fact is injected for each role mapped or direct role. |
| Peer ID | node("<peer-id>"), client_peer_id("<peer-id>") | Binds the token to the specific agent’s libp2p cryptographic identity. |
| Expiration | expiration(<date>) | The token expiration date based on the OIDC session. |
1.1 Translating Identity to Capability: OIDC to Biscuit
The core innovation of the SAM Network’s security model is translating standard web identity (OIDC JSON Web Tokens defined in OpenID Connect Core 1.0) into decentralized capability tokens (Biscuits). This translation happens securely at the sam-control-plane during the authentication phase.
The Datalog authority facts generated by the control plane are constructed using the Biscuit Symbol Table specification, ensuring compact serialization and cryptographically undeniable credentials.
Here is exactly how an OIDC JWT is transformed into a policy-ready Biscuit:
1. Claim Extraction
When an agent or user submits a valid OIDC JWT, the sam-control-plane verifies the token’s signature against the Identity Provider. Once validated, the control plane extracts the payload claims—typically attributes like sub (subject), email, and custom arrays like groups or roles.
2. Datalog Fact Generation
Biscuit policies are written in Datalog, a declarative logic language. The sam-control-plane acts as a translator, mapping the JSON claims from the OIDC token into immutable Datalog facts.
For example, an incoming OIDC payload like this:
{
"sub": "user-12345",
"email": "agent@google.com",
"groups": ["beta-testers", "engineering"]
}
Is translated by the control plane into the following Datalog facts:
user("user-12345");
email("agent@google.com");
group("beta-testers");
group("engineering");
3. Minting the Authority Block
The sam-control-plane takes these generated Datalog facts and embeds them into the Authority Block of a brand new Biscuit token. The control plane signs this block with its private cryptographic key.
Because the facts are sealed in the Authority Block by the control plane, any sam-node in the mesh can implicitly trust that the user holding the Biscuit possesses those specific emails and groups.
4. Policy Evaluation at the Node
When the agent presents the Biscuit to a sam-node to execute a tool, the node evaluates its local policies against the facts embedded in the token.
Because the OIDC claims were translated into Datalog, a node administrator can write elegant, logic-based rules in policy.go or their YAML configs:
// The node will only execute the tool if the control plane certified the agent is in the engineering group
allow if group("engineering");
2. Control plane policy configuration (REST API)
Admins manage central permissions dynamically via the Control Plane REST API. The policy database defines a set of Roles and Bindings.
- Roles: Define specific capabilities (allowed destinations and services).
allowed_targets: Defines which logical groups or specific peers a user can route messages to, analogous to Active Directory security groups. Target definitions must be formatted as resolved facts (e.g.,group:<name>,user:<sub-id>,email:<email>,role:<role-name>, ornode:<peer-id>). Note: These are evaluated dynamically at the destination node using its own identity (see Section 3.1).allowed_services: Defines the application-level tools or endpoints a user can access. Services use a stricttype://nameconvention (e.g.,mcp://db-agentorinference://openrouter).- Strict Namespaces: There are no implicit fallbacks.
system://...is used for internal services,mcp://...for node services,inference://...for AI models, etc. Service names must be valid domain labels (e.g.value1.value2.value3). - Wildcards: SAM natively supports domain-level wildcards to build complex policies. The control plane generates specific facts like
granted_service_exact($type, $name),granted_service_prefix($type, $prefix),granted_service_suffix($type, $suffix),granted_service_all_in_type($type), orgranted_service_all_types(). You can grant access to an entire type viamcp://*, allow prefix-based wildcard matching likemcp://*.service.local(which matches services ending in.service.local), suffix-based wildcard matching likemcp://service.*(which matches services starting withservice.), or global access to everything via*. - MCP Namespace Convention: The
mcp://prefix (api.MCPServicePrefix) is the explicit convention for all Model Context Protocol targets.
- Strict Namespaces: There are no implicit fallbacks.
- Bindings: Map OIDC identities (sub/user, email, group) to specific Roles.
2.1 Updating Mesh Policy
Admins POST policy JSON updates to the Control Plane /policies endpoint using the admin authorization token:
curl -X POST \
-H "Authorization: Bearer <your-admin-token>" \
-H "Content-Type: application/json" \
-d '{
"roles": [
{
"name": "data-scientist-role",
"allowed_targets": ["group:backend-nodes", "node:12D3KooW..."],
"allowed_services": ["mcp://db-agent", "inference://openrouter", "mcp://*"]
}
],
"bindings": [
{
"role": "data-scientist-role",
"members": ["group:data-science-team"]
}
]
}' \
http://<control-plane-ip>:8080/policies
3. Node Local Policy Schema (sam-node-config.yaml)
Local developers can configure custom validation rules for their specific node under attenuation:. These rules are loaded directly into the main authorizer and evaluated entirely at the destination node.
version: "v1alpha1"
attenuation:
checks:
- 'check if time($time), $time < 2026-12-31T00:00:00Z;'
policies:
- 'deny if user("untrusted_sub_id");'
- 'deny if service("mcp", "restricted-tool"), group("externals");'
3.1 Understanding Authorization Limits (Local vs. control plane)
The SAM network operates on a Zero Trust architecture where authorization is typically managed centrally by the control plane. However, local nodes have full sovereignty over their resources and can define their own authorization policies using the local sam-node configuration.
Local policies (defined under attenuation.policies) configure the Verifier on the local node.
- Restrictive Policies (Deny): Local nodes can narrow down or restrict permissions granted by the control plane. For example, blocking users during off-hours, restricting specific contractors, or adding custom time-bound checks using
deny ifpolicies. - Permissive Policies (Allow): Local nodes can explicitly grant access to peers, overriding an implicit deny (e.g., when the control plane omits a
granted_servicefact). However, local allow policies cannot bypass explicit constraints (such as target restrictions or expirations) enforced via control-plane-injectedcheck ifstatements. In Biscuit, allcheck ifconditions must evaluate to true; if the control plane seals a token with a target restriction that the destination node does not satisfy, the request is unconditionally rejected regardless of any localallow ifpolicies.
3.2 Evaluating allowed_targets at the Destination
The SAM network operates on a Zero Trust architecture. The origin node does not police its own traffic. When the control plane injects allowed_targets permissions (like - "group:backend-nodes") into the token, it creates granted_target_group("backend-nodes") capability facts and seals the token with a target_restricted() fact. If no targets are specified, the control plane mints a target_unrestricted() fact.
It is up to the destination node to mathematically prove that it is the intended target. To do this, the destination node automatically injects its own identity into the local authorization context as target facts (e.g., target_fact("group", "backend-nodes")).
The destination node enforces this via a baseline check:
check if allow_network_target($fact, $val) or target_unrestricted();
Because the target logic is baked directly into the node’s middleware, you don’t need to write manual Datalog rules for it. The node dynamically deduces allow_network_target($fact, $val) if any of its injected target_facts match the granted_target_* facts presented in the incoming token.
If the token is target_restricted() and the destination node does not possess an identity matching the granted targets, the connection is instantly rejected.
[!NOTE] Policy Evaluation Precedence Local policies defined in
sam-node-config.yamlare evaluated before baseline rules. This means local administrators can write rules that explicitlydenyaccess based on custom logic, overriding access granted by the control plane. While they can also use localallowpolicies to bypass control plane service capability constraints, all hardcoded baseline checks (OIDC signatures, replay defense, and target group restrictions) remain strictly enforced.
4. Node Baseline Security Rules
Every sam-node enforces a set of baseline security rules (defined in Go code) to secure the transport layer. These rules run automatically before evaluating custom OIDC or local policies:
4.1 Replay & Impersonation Prevention
Every request must prove that the libp2p cryptographic peer ID of the connection matches the client peer ID embedded in the authorization token:
check if client_peer_id($id), connection_peer_id($id);
4.2 The Catalog Service (sam.catalog)
To allow remote peers to discover tools and query connectivity, each node hosts a built-in catalog service at the special target sam.catalog. This service exposes local metadata tools (e.g. list_local_services, get_mesh_info).
Access to the catalog service is not granted by default and must be explicitly permitted (e.g. via a control plane role mapping allowing system://sam.catalog to verified nodes) to facilitate peer-to-peer discovery.
Example policy allowing catalog discovery:
allow if service("system", "sam.catalog");
5. Ingress Authorization Pipeline (Execution Flow)
When a node receives an incoming connection request (via P2P HTTP Ingress or wrapped protocol streams like MCP/Inference), it performs a multi-stage verification pipeline.
5.1 Pipeline Stages
graph TD
A[Incoming Connection] --> B(Stage 1: Connection Gating)
B -->|Banned/Revoked| C[Drop Connection]
B -->|Allowed| D(Stage 2: Biscuit Token Verification)
D --> E[Run 1: Authorize Node's Own Identity Token]
E --> F[Query & Inject Destination Target Facts]
F --> G[Run 2: Authorize Caller's Request Token]
G -->|Success| H[Proxy to Downstream Service]
G -->|Denied| I[Return 403 Forbidden / Reject Auth Frame]Stage 1: Connection Gating (Layer 2)
- Performed immediately at the connection manager level (
gate.go). - Checks the remote peer ID against local blacklist (
Store.IsBanned) and revocation caches (revokedPeers). - Does not parse Biscuit tokens. Connection is dropped early if matched.
- Performed immediately at the connection manager level (
Stage 2: Biscuit Token Verification (Layer 3/4)
- Handled inside node middleware (
middleware.go). - Performs exactly two Biscuit authorizer execution runs to process authorization:
- Handled inside node middleware (
Run 1: Destination Identity Fact Evaluation
- Target: The node’s own identity token (issued by the control plane during enrollment).
- Purpose: We must mathematically evaluate the node’s own OIDC group/user claims and node ID to produce
target_fact(...)datalog assertions (e.g.target_fact("group", "backend-nodes")). These facts represent who we are. - Mechanism:
- We create an authorizer for our own token signed by the control plane’s public key.
- We add a baseline
allow if truepolicy. This policy is required by Biscuit so that the authorizer can execute (since the token itself holds only identity facts and has no operation-level authorization policies). - We execute
Authorize()to verify our token signature and validate internal checks (such as expiration). - We query
api.TargetFactRulesagainst this authorizer to extract target facts. - We inject these extracted
target_factassertions into the main request authorizer.
Run 2: Caller Request Authorization
- Target: The caller’s request token (the Biscuit token presented in the request’s
X-Sam-Biscuitheader orAuthFrame). - Purpose: Verifies that the caller has been granted access by the control plane to the requested target service, checks for replay protection, and evaluates local attenuation constraints.
- Mechanism:
- We create an authorizer for the caller’s token using the control plane’s public key.
- We inject the target matching facts derived from Run 1.
- We add baseline rules (e.g. peer ID matching connection ID check, system catalog access policies) and local attenuation configurations (
sam-node-config.yaml). - We execute
Authorize()to check all signatures, checks, and policies. If successful, the request is forwarded to the underlying service.
6. Comprehensive Mesh Policy Example
Here is a representative configuration for an engineering mesh deployment. It shows how to use roles, target groups, service namespaces, wildcards, and local attenuation checks to build a zero-trust development network.
6.1 Central control plane policy API update
Send this JSON payload via POST /policies to define global roles and user/service-account mappings.
{
"roles": [
{
"name": "admin",
"allowed_targets": ["*"],
"allowed_services": ["*"]
},
{
"name": "developer",
"allowed_targets": ["group:dev-nodes"],
"allowed_services": ["mcp://code-reviewer", "mcp://git-helper", "mcp://build-runner.*"]
},
{
"name": "data-scientist",
"allowed_targets": ["group:data-nodes", "node:12D3KooWSpecialNode"],
"allowed_services": ["mcp://db-reader", "inference://*"]
},
{
"name": "auditor",
"allowed_targets": ["*"],
"allowed_services": ["system://sam.catalog"]
}
],
"bindings": [
{
"role": "admin",
"members": ["user:system:serviceaccount:sam-mesh:admin-sa", "group:infrastructure-leads"]
},
{
"role": "developer",
"members": ["group:software-engineering-team"]
},
{
"role": "data-scientist",
"members": ["group:data-science-team"]
},
{
"role": "auditor",
"members": ["email:audit-contractor@external.com"]
}
]
}
6.2 Node-Level Configuration (sam-node-config.yaml)
Deploy this file on a specific database node in the data-nodes group to register services and enforce local restrictions.
version: "v1alpha1"
# Static services hosted by this local node
services:
- type: "mcp"
name: "db-reader"
description: "Read-only SQL execution tool"
target_url: "http://127.0.0.1:5001/mcp"
- type: "mcp"
name: "db-writer"
description: "Database modification tool"
target_url: "http://127.0.0.1:5002/mcp"
# Local policies further restrict or override permissions on this specific node
attenuation:
# local rules generate new facts based on existing ones
rules:
# e.g., 'is_off_hours() <- current_hour($h), $h >= 21;'
# local checks must be satisfied for any connection to succeed
checks:
# 1. Enforce strict TLS certificate expiry limit
- 'check if time($time), $time < 2026-12-31T23:59:59Z;'
# local policies can contain deny rules (to restrict control plane grants) or allow rules (to override implicit denies)
policies:
# 2. Block db-writer calls during off-hours (9 PM to 6 AM)
- 'deny if service("mcp", "db-writer"), current_hour($hour), $hour >= 21;'
- 'deny if service("mcp", "db-writer"), current_hour($hour), $hour < 6;'
# 3. Restrict contractors from accessing db-writer even if the control plane granted it
- 'deny if service("mcp", "db-writer"), role("contractor");'
# 4. Explicitly allow local admin bypass
- 'allow if user("local-admin");'