Skip to main content

Policy Enforcement

AgentVisor™ uses the Manetu PolicyEngine (MPE) to enforce fine-grained access control on all agent operations.

Overview

Every operation goes through policy evaluation:

Unauthenticated Requests and the Anonymous Principal

AgentVisor does not reject unauthenticated requests at the edge. A request with no valid credentials still reaches policy evaluation like any other — it is simply evaluated as an anonymous principal (sub: "anonymous", role mrn:agentvisor:role:anonymous) rather than turned away before MPE ever sees it.

This is a deliberate design choice, not an oversight: "reject callers without credentials" is itself an access-control decision, and access-control decisions belong in policy, evaluated by MPE, not hardcoded ahead of it. Keeping authentication ("who is calling, if anyone") separate from authorization ("what may this caller do") means every rule — including "anonymous callers get nothing" — is expressed the same way: as an auditable, testable PolicyDomain rule, not as fixed logic a code change is required to alter.

What this means for your PolicyDomain: the policies/domain.yml shipped by agentvisor template create langgraph/chatbot-agent (and the other built-in templates) assigns the built-in mrn:agentvisor:role:anonymous role to a policy that grants any caller with a non-empty sub — which includes the anonymous principal itself. This is a deliberately permissive onboarding default for local development, not a production posture. If your deployment should deny unauthenticated callers, write that as an explicit rule, e.g.:

allow if {
input.principal.sub != ""
input.principal.sub != "anonymous"
}

PolicyEngine Modes

AgentVisor supports three authorization modes:

Embedded Mode

PolicyEngine runs as an embedded library inside the host runtime:

export AGENTVISOR_AUTHZ_TYPE=embedded
export AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES=./policies/domain.yml
  • Low latency (no network call)
  • Policies loaded at startup
  • Best for single-tenant deployments

HTTP Mode

Policies evaluated by an external Policy Decision Point (PDP):

export AGENTVISOR_AUTHZ_TYPE=http
export AGENTVISOR_AUTHZ_HTTP_URL=https://pdp.example.com:9000
export AGENTVISOR_AUTHZ_HTTP_TLS_CA_FILE=/etc/agentvisor/pdp-ca.pem
  • Centralized policy management
  • Hot reload without restart
  • Best for enterprise deployments
  • Supports TLS, mTLS, and custom auth headers — see the Connecting to a Remote PDP guide for full configuration options including mutual TLS and bearer token injection

Dangerous Disable Mode

danger

Never use in production! Bypasses all authorization checks.

export AGENTVISOR_AUTHZ_TYPE=allowall
  • Logs loud warnings
  • All requests allowed
  • Development only

Manetu Resource Names (MRNs)

Resources are identified by MRNs:

mrn:agentvisor:<type>:<identifier>
Resource TypeMRN FormatExample
HTTP Endpointsmrn:agentvisor:http:<host>/<path>mrn:agentvisor:http:api.openai.com/v1/chat
Agent Toolsmrn:agentvisor:tool:<name>mrn:agentvisor:tool:web-search
Store Itemsmrn:agentvisor:store:<namespace>/<key>mrn:agentvisor:store:memories/*
Threadsmrn:agentvisor:thread:<thread_id>mrn:agentvisor:thread:*
Runsmrn:agentvisor:run:<thread_id>/<run_id>mrn:agentvisor:run:*/*
Thread Statemrn:agentvisor:state:<thread_id>mrn:agentvisor:state:*
Thread KVmrn:agentvisor:thread-kv:<thread_id>mrn:agentvisor:thread-kv:*
Checkpointsmrn:agentvisor:checkpoint:<id>mrn:agentvisor:checkpoint:*
Agentsmrn:agentvisor:agent:<name>mrn:agentvisor:agent:*
MCP Servermrn:agentvisor:mcp:<server_name>mrn:agentvisor:mcp:github
MCP Toolmrn:agentvisor:mcp:<server_name>/<tool_name>mrn:agentvisor:mcp:github/list_repos
MCP Resourcemrn:agentvisor:mcp:<server_name>/resource/<uri>mrn:agentvisor:mcp:github/resource/*
A2A Agentmrn:agentvisor:a2a:<agent_name>mrn:agentvisor:a2a:research-agent
A2A Taskmrn:agentvisor:a2a:<agent_name>/task/<task_id>mrn:agentvisor:a2a:research-agent/task/*
Filesystemmrn:agentvisor:fs:<canonical-host-path>mrn:agentvisor:fs:/home/user/workspace/*
Temporal Namespacemrn:agentvisor:temporal:<namespace>mrn:agentvisor:temporal:default

Operations

Agent Operations (Guest → Host)

OperationDescriptionStatus
agentvisor:http:requestHTTP proxy requestsEnforced
agentvisor:http:upgradeHTTP proxy protocol upgrades (e.g. WebSocket)Enforced
agentvisor:store:readRead from storeEnforced
agentvisor:store:createCreate a store itemEnforced
agentvisor:store:updateUpdate a store itemEnforced
agentvisor:store:deleteDelete a store itemEnforced
agentvisor:store:searchSearch store itemsEnforced
agentvisor:store:listList store namespacesEnforced
agentvisor:thread-kv:readRead thread-scoped KV entryEnforced
agentvisor:thread-kv:writeWrite thread-scoped KV entryEnforced
agentvisor:thread-kv:deleteDelete thread-scoped KV entryEnforced
agentvisor:thread-kv:listList thread-scoped KV entriesEnforced

agentvisor:checkpoint:save/load and agentvisor:tool:call do not appear in this table because they aren't policy-evaluated operations at all — see the note below.

Checkpoints and stream chunks are not policy-evaluated

SaveCheckpoint, LoadCheckpoint, and EmitStreamChunk deliberately skip authorization — no Authorize call, no Op* constant. Checkpoints are internal to a run whose access was already adjudicated at run creation, so re-adjudicating every read/write would be redundant; stream chunks are a high-frequency output path where per-chunk evaluation is prohibitive. See SECURITY-DESIGN.md (Decision 2) for the full rationale. There is also no built-in agentvisor:tool:call or agentvisor:event:emit operation — neither exists in the current Op* constants (internal/host/grpcserver/server.go).

API Operations (External → Host)

OperationDescriptionStatus
agentvisor:thread:createCreate threadEnforced
agentvisor:thread:readRead threadEnforced
agentvisor:thread:updateUpdate threadEnforced
agentvisor:thread:deleteDelete threadEnforced
agentvisor:thread:listList/search threadsEnforced
agentvisor:run:createCreate runEnforced
agentvisor:run:readRead runEnforced
agentvisor:run:cancelCancel runEnforced
agentvisor:run:deleteDelete runEnforced
agentvisor:run:listList runsEnforced
agentvisor:state:readRead stateEnforced
agentvisor:state:updateUpdate stateEnforced
agentvisor:agent:listList agentsEnforced
agentvisor:agent:readRead agent metadataEnforced

MCP Gateway Operations (Agent → External MCP Servers)

MCP Gateway policy uses a two-tier model. The server-scope check gates whether the agent may enumerate anything from a given server at all. Per-item checks then gate individual item visibility in list responses.

Server-scope (coarse gate):

OperationMRN ScopeDescription
agentvisor:mcp:tool:listmrn:agentvisor:mcp:<server>List all tools from a server
agentvisor:mcp:tool:callmrn:agentvisor:mcp:<server>/<tool>Call a specific tool
agentvisor:mcp:resource:listmrn:agentvisor:mcp:<server>List all resources from a server
agentvisor:mcp:resource:readmrn:agentvisor:mcp:<server>/resource/<uri>Read a specific resource

Per-item (fine-grained visibility):

OperationMRN ScopeDescription
agentvisor:mcp:tool:readmrn:agentvisor:mcp:<server>/<tool>Per-tool visibility in McpListTools response
agentvisor:mcp:resource:describemrn:agentvisor:mcp:<server>/resource/<uri>Per-resource visibility in McpListResources response

When a server-scope check (...:list) is denied, the entire list call is short-circuited. When a per-item check (...:tool:read, ...:resource:describe) is denied, that item is silently dropped from the response — the agent sees only tools/resources it is allowed to access.

A2A Gateway Operations (Agent → External A2A Agents)

A2A Gateway policy also uses a two-tier model.

Server-scope (coarse gate):

OperationMRN ScopeDescription
agentvisor:a2a:agent:listmrn:agentvisor:a2aList all configured agents
agentvisor:a2a:agent:describemrn:agentvisor:a2a:<agent>Get agent card
agentvisor:a2a:task:sendmrn:agentvisor:a2a:<agent>Send a task to an agent
agentvisor:a2a:task:getmrn:agentvisor:a2a:<agent>/task/<id>Get task status
agentvisor:a2a:task:cancelmrn:agentvisor:a2a:<agent>/task/<id>Cancel a task
agentvisor:a2a:task:streammrn:agentvisor:a2a:<agent>/task/<id>Stream task updates

Per-item (fine-grained visibility in A2AListAgents):

OperationMRN ScopeDescription
agentvisor:a2a:agent:describemrn:agentvisor:a2a:<agent>Per-agent visibility in A2AListAgents response

agentvisor:a2a:agent:describe gates both individual agent card retrieval (A2AGetAgentCard) and per-agent visibility within A2AListAgents. A single policy rule covering this operation controls both discovery and card access.

Codec Server Operations (Temporal Web UI → Host)

The codec server authorizes /encode and /decode per-namespace only when temporal.codec.server.auth.enabled is set. A request with no valid Bearer token is rejected before either operation is evaluated.

OperationMRN ScopeDescription
agentvisor:temporal:codec-decodemrn:agentvisor:temporal:<namespace>Decrypt payloads for a namespace (/decode)
agentvisor:temporal:codec-encodemrn:agentvisor:temporal:<namespace>Encrypt payloads for a namespace (/encode)

PolicyDomain Structure

Policies are defined in YAML following MPE's v1beta1 schema. A domain needs two sections beyond policies:/resource-groups:/resources: to actually grant or deny anything: operations: (without it, MPE hard-denies every request before any resource policy runs) and roles: (without a role matching the caller's mroles/mgroups claims — including the built-in anonymous principal's own mrn:agentvisor:role:anonymous — MPE denies regardless of what the resource policy decides; see Unauthenticated Requests and the Anonymous Principal above):

apiVersion: iamlite.manetu.io/v1beta1
kind: PolicyDomain
metadata:
name: my-agent-policies
spec:
policies:
# Operation phase - tri-state (-1 deny, 0 continue, >0 grant+skip)
- mrn: &policy-operation "mrn:iam:policy:agentvisor-operation"
name: agentvisor-operation
rego: |
package authz
import rego.v1
default allow := -1
allow := 0 if input.principal.sub != ""

- mrn: &policy-allow "mrn:iam:policy:allow-authenticated"
name: allow-authenticated
rego: |
package authz
import rego.v1
default allow := false
allow if input.principal.sub != ""

- mrn: &policy-deny "mrn:iam:policy:deny-all"
name: deny-all
rego: |
package authz
default allow = false

# Identity phase - the anonymous principal carries this role automatically
roles:
- mrn: "mrn:agentvisor:role:anonymous"
name: anonymous
policy: *policy-allow

resource-groups:
- mrn: "mrn:iam:resource-group:allowed"
name: allowed
policy: *policy-allow

- mrn: "mrn:iam:resource-group:denied"
name: denied
default: true
policy: *policy-deny

resources:
- name: openai-api
selector:
- "mrn:agentvisor:http:api\\.openai\\.com.*"
group: "mrn:iam:resource-group:allowed"

- name: default
selector:
- "mrn:agentvisor:http:.*"
group: "mrn:iam:resource-group:denied"

# Operation phase - a single entry routes every AgentVisor operation
# through the tri-state policy above
operations:
- name: agentvisor-ops
selector: ["agentvisor:.*"]
policy: *policy-operation

Policy Components

Policies

Rego rules that evaluate to allow/deny:

policies:
# Grants any authenticated principal - including the anonymous one, see
# Unauthenticated Requests and the Anonymous Principal above
- mrn: "mrn:iam:policy:allow-authenticated"
name: allow-authenticated
rego: |
package authz
default allow = false
allow {
input.principal.sub != ""
}

Resource Groups

Collections of resources sharing a policy:

resource-groups:
- mrn: "mrn:iam:resource-group:llm-apis"
name: llm-apis
policy: "mrn:iam:policy:allow-authenticated"

Resources

MRN patterns mapped to groups:

resources:
- name: openai
selector:
- "mrn:agentvisor:http:api\\.openai\\.com.*"
group: "mrn:iam:resource-group:llm-apis"

Common Patterns

Allow Specific APIs

resources:
# OpenAI
- name: openai
selector: ["mrn:agentvisor:http:api\\.openai\\.com.*"]
group: "mrn:iam:resource-group:allowed"

# Anthropic
- name: anthropic
selector: ["mrn:agentvisor:http:api\\.anthropic\\.com.*"]
group: "mrn:iam:resource-group:allowed"

# Your internal API
- name: internal-api
selector: ["mrn:agentvisor:http:api\\.mycompany\\.com.*"]
group: "mrn:iam:resource-group:allowed"

Block Internal Networks

resources:
- name: rfc1918
selector:
- "mrn:agentvisor:http:10\\..*"
- "mrn:agentvisor:http:192\\.168\\..*"
- "mrn:agentvisor:http:172\\.(1[6-9]|2[0-9]|3[0-1])\\..*"
group: "mrn:iam:resource-group:denied"

- name: localhost
selector:
- "mrn:agentvisor:http:localhost.*"
- "mrn:agentvisor:http:127\\..*"
group: "mrn:iam:resource-group:denied"

- name: metadata
selector:
- "mrn:agentvisor:http:169\\.254\\..*" # AWS metadata
group: "mrn:iam:resource-group:denied"

Per-Principal Policies

policies:
- mrn: "mrn:iam:policy:admin-only"
name: admin-only
rego: |
package authz
default allow = false
allow {
input.principal.role == "admin"
}

- mrn: "mrn:iam:policy:team-access"
name: team-access
rego: |
package authz
default allow = false
allow {
input.principal.team == input.resource.team
}

Rate Limiting (via Rego)

policies:
- mrn: "mrn:iam:policy:rate-limited"
name: rate-limited
rego: |
package authz
default allow = false

# Allow if under rate limit
allow {
count(input.context.requests_today) < 1000
}

# Always allow specific principals
allow {
input.principal.tier == "unlimited"
}

Evaluation Flow

Debugging Policies

Enable Debug Logging

AGENTVISOR_LOG_LEVEL=debug agentvisor serve ...

Test Policies with MPE CLI

# Validate policy syntax
mpe lint -f ./policies/domain.yml

# Test policy evaluation
mpe test decisions -b ./policies/domain.yml -i ./policies/test-cases.yml

Check Policy Match

Every decision logs at info level (no AGENTVISOR_LOG_LEVEL=debug needed) as a pair of MPE authorization check / MPE authorization result messages, tagged with the principal, operation, and resource MRN:

INFO [host] MPE authorization check {"principal": "anonymous", "operation": "agentvisor:thread:create", "resource": "mrn:agentvisor:thread:*"}
INFO [host] MPE authorization result {"allowed": true, "principal": "anonymous", "operation": "agentvisor:thread:create", "resource": "mrn:agentvisor:thread:*"}

Best Practices

  1. Default deny: Always include a catch-all deny rule
  2. Specific selectors: Use precise patterns, not overly broad
  3. Block internals first: Deny internal networks before allowing external
  4. Test thoroughly: Use MPE's test framework
  5. Audit decisions: Enable audit logging for compliance
  6. Version control: Keep policies in git with your agent code