Skip to content

Wire-Protocol-Aware Policy Evaluation

AGT policy rules can now reference wire-level protocol semantics — not just HTTP metadata — through protocol facets. Facets are structured fields extracted from raw protocol context (SQL queries, Kubernetes API paths, etc.) and merged into the policy evaluation context before rules are evaluated.

How it works

When PolicyEngine.evaluate() is called, it runs extract_protocol_facets(context) before evaluating rules. If the context contains a sql or k8s sub-dict, the relevant parser populates structured fields that YAML rules can reference with dot-notation conditions.

SQL facets

Populate context["sql"]["query"] with a SQL statement. The following fields are extracted and available in policy conditions:

Field Example value Description
sql.verb SELECT, DROP, DELETE Uppercase SQL verb
sql.target users Primary table/object being operated on
sql.tables orders,users Comma-joined list of all tables referenced
sql.functions COUNT,NOW Comma-joined list of SQL functions used

Requires sqlglot (pip install sqlglot). Without it, sql.verb is set to UNKNOWN (fail-closed).

Example rules

Each rule matches a single extracted field via {field, operator, value}. Compound checks (e.g. verb + target) should be expressed as separate rules with appropriate priorities.

rules:
  - name: deny-destructive-sql
    condition: {field: "sql.verb", operator: in, value: ["DROP", "TRUNCATE", "DELETE"]}
    action: deny
    priority: 100

  - name: deny-schema-changes
    condition: {field: "sql.verb", operator: in, value: ["ALTER", "GRANT", "REVOKE"]}
    action: deny
    priority: 100

  - name: allow-read-only-sql
    condition: {field: "sql.verb", operator: eq, value: "SELECT"}
    action: allow
    priority: 5

Example evaluation context

engine.evaluate(
    agent_did="did:example:agent1",
    context={"sql": {"query": "DROP TABLE production"}},
)

Kubernetes facets

Populate context["k8s"]["method"] (HTTP method) and context["k8s"]["path"] (API server path). The following fields are extracted:

Field Example value Description
k8s.verb get, list, delete, create Kubernetes API verb
k8s.resource pods, deployments Resource type
k8s.namespace production Namespace (empty for cluster-scoped)
k8s.name mypod Resource name (empty for collection requests)
k8s.subresource exec, log Subresource (empty if none)

HTTP methods are mapped to Kubernetes verbs:

HTTP Named resource Collection
GET get list
DELETE delete deletecollection
POST create create
PUT update update
PATCH patch patch

Example rules

rules:
  - name: deny-k8s-production-namespace
    condition: {field: "k8s.namespace", operator: eq, value: "production"}
    action: deny
    priority: 110

  - name: deny-k8s-exec
    condition: {field: "k8s.subresource", operator: eq, value: "exec"}
    action: deny
    priority: 100

  - name: deny-k8s-deletecollection
    condition: {field: "k8s.verb", operator: eq, value: "deletecollection"}
    action: deny
    priority: 100

  - name: allow-k8s-readonly
    condition: {field: "k8s.verb", operator: in, value: ["get", "list", "watch"]}
    action: allow
    priority: 5

Example evaluation context

engine.evaluate(
    agent_did="did:example:agent1",
    context={
        "k8s": {
            "method": "DELETE",
            "path": "/api/v1/namespaces/production/pods/mypod",
        }
    },
)

Transparent proxy integration

The MCP proxy (agentmesh proxy) automatically populates wire-protocol context from tool call arguments:

  • Tool arguments named query or sqlcontext["sql"]["query"]
  • Tool arguments named method/http_method + path/api_path starting with /api/ or /apis/context["k8s"]

No application changes are required. Define SQL or K8s rules in your policy file and they apply automatically to proxied tool calls.

Adding custom protocol parsers

Register new protocol extractors via the module-level default_registry:

from agentmesh.governance.protocol_facets import default_registry

def extract_redis_facets(redis_ctx: dict) -> dict:
    cmd = (redis_ctx.get("command") or "").upper()
    return {"verb": cmd, "key": redis_ctx.get("key", "")}

default_registry.register("redis", extract_redis_facets)

Then pass {"redis": {"command": "FLUSHALL"}} in the evaluation context and write rules like:

- name: deny-redis-flush
  condition: {field: "redis.verb", operator: in, value: ["FLUSHALL", "FLUSHDB"]}
  action: deny

See examples/policy-templates/wire-protocol-rules.yaml for a full set of example rules.

Language parity

The same facet model is available across the language SDKs. Each SDK exposes a FacetRegistry, a default registry, and an extract_protocol_facets-equivalent helper, ships sql.* and k8s.* extractors with the same field names, and runs the extractors automatically inside policy evaluation.

Language Module / package Status
Python agentmesh.governance.protocol_facets Shipped (#2553)
Rust agentmesh::protocol_facets Shipped (#2588)
TypeScript @microsoft/agent-governance-sdkprotocol-facets Tracked in #2587
.NET agent-governance-dotnetAgentGovernance.Policy.ProtocolFacets Tracked in #2589
Go agent-governance-golang Tracked in #2590

Rust usage

The Rust SDK exposes the same facet model under the agentmesh::protocol_facets module: FacetRegistry, default_registry(), extract_protocol_facets, extract_sql_facets, extract_k8s_facets. The same sql.* and k8s.* fields are surfaced, and PolicyEngine::evaluate invokes the registry on a defensive copy of the caller's context before matching rules.

use agentmesh::{PolicyEngine, default_registry};
use serde_yaml::Value;
use std::collections::HashMap;

let engine = PolicyEngine::new();
engine.load_from_yaml(r#"
version: "1"
agent: "did:example:agent1"
policies:
  - name: deny-destructive-sql
    type: capability
    denied_actions: ["*"]
    conditions:
      sql.verb: [DROP, TRUNCATE, DELETE]
"#).unwrap();

let mut sub = serde_yaml::Mapping::new();
sub.insert(Value::String("query".into()), Value::String("DROP TABLE production".into()));
let mut ctx = HashMap::new();
ctx.insert("sql".to_string(), Value::Mapping(sub));

let decision = engine.evaluate("db.exec", Some(&ctx));
// decision == PolicyDecision::Deny(...)

// Register a custom protocol extractor:
default_registry().register("redis", |sub| {
    let mut m = std::collections::HashMap::new();
    if let Some(cmd) = sub.get(Value::String("command".into())).and_then(|v| v.as_str()) {
        m.insert("verb".to_string(), Value::String(cmd.to_uppercase()));
    }
    m
});

Rule conditions in the Rust SDK use the existing YAML mapping shape (key: value or key: [v1, v2] for in-style membership). Field names and decision outcomes are identical to the Python implementation.

SQL parser note. The Rust extractor uses a built-in regex tokenizer that handles the common verb / target / function cases used by policy rules. For complex dialect-specific SQL, register a custom extractor via default_registry().register("sql", ...).

.NET usage

The .NET SDK exposes the same facet model under the AgentGovernance.Policy namespace: FacetRegistry, ProtocolFacets.DefaultRegistry, ProtocolFacets.ExtractProtocolFacets, ProtocolFacets.ExtractSqlFacets, ProtocolFacets.ExtractK8sFacets. PolicyEngine.Evaluate invokes the registry on its internal copy of the context before rules run, so callers only need to populate raw sql/k8s sub-dictionaries — the caller's own dictionary is never mutated.

using AgentGovernance.Policy;

var engine = new PolicyEngine();
engine.LoadYaml(@"
apiVersion: governance.toolkit/v1
name: sql-guard
scope: global
default_action: allow
rules:
  - name: deny-destructive-sql
    condition: ""sql.verb == 'DROP'""
    action: deny
    priority: 100
");

var decision = engine.Evaluate("did:mesh:agent1", new Dictionary<string, object>
{
    ["sql"] = new Dictionary<string, object> { ["query"] = "DROP TABLE production" },
});
// decision.Allowed == false, decision.MatchedRule == "deny-destructive-sql"

// Register a custom protocol extractor:
ProtocolFacets.DefaultRegistry.Register("redis", sub =>
{
    var cmd = sub.TryGetValue("command", out var v) ? v?.ToString() ?? "" : "";
    return new Dictionary<string, object> { ["verb"] = cmd.ToUpperInvariant() };
});

Rule conditions in the .NET SDK use the existing expression-string format ("sql.verb == 'DROP'", dot-pathed field references, and/or compounds). Field names and high-level decision outcomes are consistent with the Python implementation; minor rule-syntax differences exist (notably, the .NET in operator references list-valued context fields rather than YAML literal lists, so split per-verb rules into individual == checks — see the example file for the pattern).

SQL parser note. The .NET extractor uses a built-in regex tokenizer that handles the common verb / target / function cases used by policy rules. It is not a full SQL parser and will not catch every dialect- specific construct; for high-assurance environments, register a custom extractor via ProtocolFacets.DefaultRegistry.Register("sql", ...) backed by a real SQL parser.