Skip to content

Action Policies

Action policies give Wanaku a deterministic authorization layer for MCP actions. They complement the evaluator. Use action policies for rules that depend on known names, labels, URIs, and structured request values. Use evaluators for contextual or semantic decisions.

The Governance Posture model defines the behavior for unmatched actions, policy failures, audit-only evaluation, and disabled scopes. The action-policy filter resolves the global posture and namespace override before it evaluates a request.

Configure a policy

Add one action_policy object to wanaku.yaml:

yaml
action_policy:
  rules:
    - id: deny-production-delete
      description: Prevent destructive production calls
      effect: deny
      selectors:
        namespace: production
        operation: tools/call
        target_type: tool
        target_name:
          matcher: glob
          value: "delete-*"
        labels:
          risk: high
      predicates:
        - operator: equals
          pointer: /arguments/force
          value: true
      reason_code: destructive_action_denied
      message: This action is not permitted.
      metadata:
        owner: platform-security

    - id: allow-public-reports
      effect: allow
      selectors:
        operation: resources/read
        target_type: resource
        uri:
          matcher: prefix
          value: "https://reports.example/public/"

Wanaku validates and compiles the complete policy before activation. Wanaku rejects the revision if a rule is invalid. An invalid revision does not replace the last active revision.

Each rule uses these fields:

FieldRequiredValue
idYesA stable identifier. Start with a letter or digit. Then use letters, digits, _, -, ., /, or :.
descriptionNoText for operators.
effectYesallow or deny.
selectorsYesOne or more action selectors.
predicatesNoA list of typed JSON Pointer predicates.
reason_codeNoA stable identifier for a denied action.
messageNoA safe message that Wanaku can return to the caller.
metadataNoOperator metadata. Metadata keys use the identifier syntax. Values are JSON values.

Unknown fields cause validation to fail. Rule IDs must be unique in one policy.

Select actions

A rule can use these selectors:

SelectorValue
namespaceThe Wanaku namespace.
operationtools/call, resources/read, or prompts/get.
target_typetool, resource, or prompt.
target_nameAn exact or glob match expression.
labelsRequired registry label key-value pairs.
uriAn exact or prefix match expression for a resource URI.

All selectors and all predicates in one rule use AND semantics. The rule matches only when every configured condition matches.

For labels, the configured labels must be a subset of the registry entry labels. Extra labels on the registry entry do not prevent a match. Prompt entries do not currently supply registry labels to the policy adapter.

Match target names

Use exact to compare the complete target name:

yaml
target_name: { matcher: exact, value: delete-account }

Use glob for target names. * matches zero or more characters. ? matches one character. A backslash escapes the next character. For example, report-\* matches the literal name report-*. A trailing backslash is invalid.

URI selectors do not support glob matching. They support exact and literal prefix matching only.

Match resource URIs

Wanaku compares the exact URI string from resources/read. Wanaku does not decode, resolve, or normalize the URI. Case, escaping, separators, and trailing slashes remain significant.

yaml
uri:
  matcher: prefix
  value: "s3://company-private/"

A URI selector applies only to a resource read. Tool registry transport URIs are not policy resource URIs.

Match structured request values

Each predicate reads the MCP params object with an RFC 6901 JSON Pointer. JSON types remain distinct. The string "1" does not equal the number 1.

OperatorOperandMatch condition
existsvalue: true or falseThe pointer is present or absent.
equalsvalueThe present value equals the operand.
not_equalsvalueThe present value does not equal the operand.
one_ofNon-empty valuesThe present value equals one list value.
not_one_ofNon-empty valuesThe present value equals no list value.

Example:

yaml
predicates:
  - operator: exists
    pointer: /arguments/approved
    value: true
  - operator: one_of
    pointer: /arguments/environment
    values: [production, staging]

Missing is different from JSON null:

Input at the pointerexists: trueexists: falseequals: nullnot_equals: nullone_of: [null]not_one_of: [null]
MissingNoYesNoNoNoNo
nullYesNoYesNoYesNo
falseYesNoNoYesNoYes
0YesNoNoYesNoYes
""YesNoNoYesNoYes

A missing value never matches a comparison operator. Use exists: false to match an omitted value.

Decision behavior

Wanaku evaluates all rules. The declaration order does not change the result.

  1. If any matching rule has effect: deny, Wanaku denies the action.
  2. If no deny matches and an allow matches, Wanaku records an explicit allow and continues the pipeline.
  3. If no rule matches, Wanaku continues the pipeline. The baseline behavior belongs to #1872.

A static allow does not skip the evaluator or another downstream filter. A static deny returns a JSON-RPC error before the evaluator and upstream action handler run.

The caller receives one deterministic safe denial reason. Wanaku selects the matching deny rule with the lexicographically lowest rule ID. A configured reason_code and message take precedence. Otherwise, Wanaku uses action_policy_denied and The requested action is not allowed. The decision model retains all matching rule IDs and configured reason codes for authorized internal consumers.

An unconfigured policy continues governed requests. If no active last-known-good policy exists, an invalid configured policy rejects governed requests with action_policy_invalid. If an active policy exists, an invalid update or startup policy does not replace it. Wanaku continues to enforce the active last-known-good policy. These are temporary failure semantics. #1872 owns the final baseline and failure modes.

Wanaku governs tools/call, resources/read, and prompts/get. Wanaku does not filter tools/list, resources/list, or prompts/list. Discovery does not grant authorization.

Pipeline position

Place wanaku_action_policy after MCP metadata, namespace resolution, and wanaku_mcp_init. Place it before wanaku_evaluator:

yaml
- filter: mcp
- filter: wanaku_namespace
- filter: wanaku_mcp_init
- filter: wanaku_action_policy
- filter: wanaku_evaluator

The default pipeline already uses this order.

Manage policy revisions

The management API uses a dedicated policy resource:

MethodPathResult
GET/api/v1/action-policiesGet the effective policy and active revision metadata.
PUT/api/v1/action-policiesValidate and activate a policy.
GET/api/v1/action-policies/revisionsList revision metadata, newest first.
GET/api/v1/action-policies/revisions/activeGet the active revision and policy.
GET/api/v1/action-policies/revisions/{id}Get one revision and policy.
POST/api/v1/action-policies/revisions/{id}/activateValidate the selected policy and create a new active revision.

Activate a policy:

bash
curl -X PUT http://localhost:8080/api/v1/action-policies \
  -H 'Content-Type: application/json' \
  -d '{
    "policy": {
      "rules": [{
        "id": "deny-delete",
        "effect": "deny",
        "selectors": {"operation": "tools/call"}
      }]
    },
    "expected_revision": 4
  }'

expected_revision is optional. If it does not equal the active revision, Wanaku returns HTTP 409 Conflict. A validation failure returns HTTP 422 Unprocessable Entity. Wanaku records the invalid revision as rejected. It does not replace the active revision.

Rollback creates a new revision. It does not change historical data:

bash
curl -X POST http://localhost:8080/api/v1/action-policies/revisions/2/activate \
  -H 'Content-Type: application/json' \
  -d '{"expected_revision": 4}'

Wanaku stores policy revisions in action-policy-revisions.json under WANAKU_PERSIST_PATH when file persistence is enabled. It writes the file after each revision change. The history contains at most 50 revisions. The policy revision stream is independent from evaluator-revisions.json.

At startup, Wanaku immediately validates and installs the persisted active policy. If wanaku.yaml contains a different valid policy, Wanaku activates a new startup revision. If the policy is unchanged, Wanaku does not create a duplicate revision. An invalid startup policy does not replace a persisted last-known-good policy.

Deferred work

Governance audit events and external decision evidence belong to #1869. Action-policy decisions already retain rule IDs and reason codes for that integration. Wanaku does not copy structured request values into management responses.

Agent-to-agent action adapters belong to #383. The engine and action context are transport-neutral so that work can reuse the current rule semantics.