Skip to content

Security & Token Architecture

ContextRouter enforces multi-layer security for all inter-service communication. Identity is never carried in payloads — ContextToken is the exclusive source of truth.

Token as Single Point of Truth (SPOT)

Identity fields are never in request payloads. ContextToken is the sole source:

FieldSourceUsed By
user_idtoken.user_idTrace metadata, memory personalization
tenant_idtoken.allowed_tenants[0]Tenant isolation, cache keys, Brain queries

ExecuteDispatcherPayload and ExecuteAgentPayload contain only execution context — messages, agent_id, config, platform. No user_id, tenant_id, or permissions.

# Router resolves tenant from token, never from payload
def _resolve_tenant_id(token) -> str:
if token and token.allowed_tenants:
return token.allowed_tenants[0]
return "default"

Three-Layer Registration Security

When a project calls RegisterTools, three checks run sequentially:

LayerCheckFails if…
A: Tenant bindingproject_id ∈ token.allowed_tenantsToken doesn’t include this project
B: Permissionhas_registration_access(perms, project_id)Missing tools:register:{id} permission
C: Ownershipverify_project_owner(project_id, tenant)Project registered by different owner

DeregisterTools also enforces Layers A+B and cleans up stored stream secrets.

SecureTool Enforcement

Every tool is wrapped in SecureTool at registration time:

  • required_permission: auto-generated as tool:{name} (e.g., tool:execute_acme_sql)
  • bound_tenant: tool only executes for its owning project
  • _enforce_permission() runs on every _run() / _arun() call
  • admin:all bypasses tenant isolation (dashboard only)
  • Fail-closed: no token → PermissionError

SecureNode Execution Wrapper

Every LLM and tool node in a registered graph is wrapped via make_secure_node():

  1. Capability Stripping — attenuates ContextToken to minimum required scope
  2. Prompt Integrity Verification — verifies prompt text against HMAC signature. Mismatch raises TamperDetectedErrorgrpc.StatusCode.ABORTED
  3. Provenance Injection — records node:{name}, shield:secrets:read:{path}, and prompt:{llm_name}:{version} into execution provenance

Shield Pre-LLM Guard

Before executing any LangGraph, user input is sent to Shield via gRPC Scan call:

  • Scans for prompt injection, jailbreaks, and PII
  • Uses SPOT authentication: end-user’s token is propagated to Shield (not a generic service token)
  • Permission inheritance: router:execute implicitly inherits shield:check via contextunity.core.permissions.inheritance
  • Open Source: fails-open when CU_SHIELD_GRPC_URL is unset

Stream Authentication

Bidirectional streams (tool executor pattern) authenticate via:

  1. Shield-verified auth tokens (production)
  2. Per-registration one-time stream_secret — consumed on first connect; reconnect requires re-registration
  3. Fail-closed rejection (no valid credentials)

Agent-Aware Token Minting

Router mints ContextToken per request using:

  1. Default permissions from SecurityPoliciesConfig
  2. Agent max permissions from contextunity.view (AgentConfigCache, TTL 5 min)
  3. Effective = intersection of both

Permission Profiles

ProfilePermissions
rag_readonlygraph:rag, brain:read, memory:read
rag_full+ memory:write, trace:write
adminadmin:all (superadmin)

Graph permissions are manifest-driven: each project gets graph:{template} matching its manifest — never graph:*.

Graph Access Control

RAG graphs check has_graph_access(token.permissions, "rag") before invocation. Graph-level denial raises PermissionError before execution.