Contextual Security Policies (CSP)
Contextual Security Policies (CSP) are Zihin's policy engine for AI agents — the layer that provides runtime governance over what an agent is allowed to do. A CSP is a named, versioned policy resource, scoped and inherited across tenant → team → agent → user, that the runtime applies on every execution.
CSPs act through two distinct mechanisms:
- Runtime enforcement — behavior guardrails such as a tool blocklist (
must_not_tools), iteration and token caps, and human approval gates are enforced in the agent loop itself, not merely suggested to the model. - Contextual injection — schedule, data, and origin rules are injected into the prompt-building process so the agent operates within them.
Policy Types
| Type | Description |
|---|---|
schedule | Time-based restrictions (allowed hours, days) |
behavior | Agent behavior rules (tone, must_do, must_not) |
data | Data access restrictions (sensitive fields, entities) |
origin | Request origin restrictions (IPs, domains) |
custom | Custom rules (flexible JSON) |
Scopes
Policies are inherited from broader to narrower scope. More specific scopes override broader ones.
tenant → team → agent → user
| Scope | Description |
|---|---|
tenant | Applies to entire tenant |
team | Applies to a specific team |
agent | Applies to a specific agent |
user | Applies to a specific user |
Authentication
| Method | Access |
|---|---|
JWT (Authorization: Bearer <jwt> + x-tenant-id) | RBAC by user role |
API Key (X-Api-Key: YOUR_API_KEY) | RBAC by API key role |
Both JWT and API Key are subject to the same role-based permission check. The role determines which operations are allowed (e.g., editor can read and create but cannot delete CSPs). See API Keys - Role System for the full permission matrix.
GET /api/csp
List all CSPs for the tenant.
Query Parameters:
| Param | Type | Description |
|---|---|---|
scope | string | Filter by scope |
policy_type | string | Filter by type |
is_active | boolean | Filter active only |
scope_target_id | UUID | Filter by target ID |
Response:
{
"data": [
{
"id": "uuid",
"name": "Business Hours Only",
"policy_type": "schedule",
"scope": "tenant",
"rules": {
"allowed_hours": { "start": "08:00", "end": "18:00" }
},
"is_active": true,
"priority": 10
}
],
"total": 5
}
GET /api/csp/effective
Get effective CSPs for a context, resolving the inheritance hierarchy.
Query Parameters:
| Param | Type | Description |
|---|---|---|
team_id | UUID | Team context |
agent_id | UUID | Agent context |
user_id | UUID | User context |
Response:
{
"data": [
{ "id": "uuid", "name": "Tenant Rules", "policy_type": "behavior", "scope": "tenant" },
{ "id": "uuid", "name": "Agent Rules", "policy_type": "data", "scope": "agent" }
],
"resolution_chain": ["tenant", "agent"]
}
GET /api/csp/:id
Get CSP by ID.
POST /api/csp
Create a new CSP.
Request Body:
{
"name": "Business Hours Only",
"policy_type": "schedule",
"scope": "tenant",
"rules": {
"allowed_hours": { "start": "08:00", "end": "18:00" },
"allowed_days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"timezone": "America/Sao_Paulo"
},
"is_active": true,
"priority": 10
}
PUT /api/csp/:id
Update a CSP (partial update).
PATCH /api/csp/:id/toggle
Toggle CSP active status.
DELETE /api/csp/:id
Delete a CSP.
Policy Rule Examples
Schedule Policy
{
"policy_type": "schedule",
"rules": {
"allowed_hours": { "start": "08:00", "end": "18:00" },
"allowed_days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"timezone": "America/Sao_Paulo"
}
}
Behavior Policy
{
"policy_type": "behavior",
"rules": {
"tone": "professional",
"language": "pt-BR",
"must_do": ["greet_user", "confirm_before_delete", "provide_sources"],
"must_not": ["share_competitor_data", "execute_without_confirmation"],
"max_iterations": 10,
"must_not_tools": ["dangerous_tool"],
"max_tokens_per_request": 4096
}
}
Behavior Guardrails (runtime enforcement)
The following behavior rules are runtime-enforced — applied in the agent loop itself, not just injected into the prompt. must_not_tools acts as a tool blocklist, preventing named tools from ever being called even if the model plans to use them (mitigating tool injection via a compromised plan):
| Field | Effect |
|---|---|
max_iterations | Limits the number of agent loop iterations |
must_not_tools | Tool blocklist — blocks specific tools from being called by the agent |
max_tokens_per_request | Limits max tokens per LLM request |
These guardrails are extracted from the behavior policy in the policy-gate and propagated to the agent loop configuration.
Data Policy
{
"policy_type": "data",
"rules": {
"sensitive_fields": ["cpf", "cnpj", "password"],
"mask_pattern": "[REDACTED]",
"never_expose": ["api_key", "secret_token"],
"restricted_entities": ["Competitor Inc"],
"restriction_message": "You don't have access to this data."
}
}
Origin Policy
{
"policy_type": "origin",
"rules": {
"allowed_origins": ["https://app.zihin.ai", "https://*.zihin.ai"],
"blocked_origins": ["http://*"],
"allowed_ips": ["192.168.1.0/24"],
"reason": "Only HTTPS origins allowed"
}
}
Action Approvals
Action approvals elevate human-in-the-loop from a static per-tool flag to a governance rule plus approval by another user, mediated by the agent inside the conversation. When a sensitive action is triggered, the agent pauses and asks a designated approver to sign off before the action runs.
Action approvals apply only in the native chat (chat.zihin.ai). For the operator-facing walkthrough of the approval flow, see Agents → Action Approvals.
Configuration has two parts: a trigger in the behavior policy that declares which tools need approval, and an approval policy that declares who approves.
Trigger: require_approval_for
Add require_approval_for inside the behavior policy. It is an array whose items are either a plain tool name (string) or an object selecting a group of tools. Any tool that matches requires approval before it runs.
{
"policy_type": "behavior",
"rules": {
"behavior": {
"require_approval_for": [
"create_deal",
{ "tool_name": "send_invoice" },
{ "mcp_server_id": "erp-protheus" },
{ "source": "db" }
],
"approval_policy_id": "b1a2c3d4-..."
}
}
}
| Item form | Matches |
|---|---|
"create_deal" (string) | The tool with that exact name |
{ "tool_name": "..." } | The named tool |
{ "mcp_server_id": "..." } | All tools exposed by that MCP server (precise) |
{ "source": "mcp" } | All MCP tools (precise) |
{ "source": "api" | "db" } | The underlying schema tools for that source |
source: mcp matches precisely. For source: api and source: db, matching resolves to the underlying schema tools and errs toward over-approving — if in doubt, it will ask for approval rather than skip it.
Link one approval policy per scope with approval_policy_id (referencing an approval_policies record described below).
Approval policies
An approval policy is a tenant-owned data resource (approval_policies) that decides who approves. A policy has stages, and each stage lists approvers.
{
"name": "Finance sign-off",
"stages": [
{
"type": "approval",
"approvers": [
{ "type": "user", "id": "e9b94898-..." },
{ "type": "role", "role": "admin" }
]
}
]
}
| Approver form | Resolves to |
|---|---|
{ "type": "user", "id": "<uuid>" } | That specific user |
{ "type": "role", "role": "<role>" } | Every tenant user with that role |
Configure a single stage (its type must be "approval") with any-one-approver semantics — when any listed approver decides, the decision applies. Multi-stage approvals, requiring more than one approval (min_approvals > 1), and timeouts are reserved in the schema for the future and are not active yet.
The member → member flow
- A user chats with an agent and triggers a matching (sensitive) action.
- The agent pauses and delivers an approval card to the approver's own chat session, with a push notification. The approver may be a different user (e.g. a manager).
- The approver approves or declines in their chat.
- On approval, the action runs outside the agent loop with its arguments frozen; the result flows back to the original requester (receipt + push).
Safe by default
- A policy with no valid approver blocks the action with a clear message — never a zombie card that nobody can resolve.
- Sensitive argument keys are redacted in the card.
- The pending-approvals list is scoped by role: a regular
membersees only their own;admin/ownersee all. - Decisions are atomic — the first decision wins.
Endpoints
| Endpoint | Method | Permission | Description |
|---|---|---|---|
GET /api/v1/approvals | GET | sessions:approve-action | Pending-approvals list (kanban). Scoped by role — member sees only their own. Optional status filter: awaiting_user (default), approved, rejected, expired, or all |
GET /api/v1/approval-policies | GET | csp:read | List approval policies |
POST /api/v1/approval-policies | POST | csp:create | Create an approval policy |
PUT /api/v1/approval-policies/:id | PUT | csp:update | Update an approval policy |
All endpoints accept hybrid authentication (JWT or API Key).