Policy Governance

Comprehensive guide to writing and using CEL policies.


What is CEL?

Definition: Common Expression Language. A Google-developed language for defining security rules.

Why CEL?:

  • Safe: No loops, no I/O, guaranteed termination.
  • Expressive: List operations, string matching, logical operators.
  • Regex-safe: Uses RE2, immune to ReDoS.

Resource Consumption (Security)

To prevent Denial of Service (DoS) attacks via complex policy evaluation, mcptrust actively limits CEL execution:

  • Cost Limit: Execution is capped at 1,000,000 cost units (strictly enforced).
  • Fail-Closed: Exceeding cost or time limits results in a policy failure.

Policy File Structure

name: "My Security Policy"
rules:
  - name: "rule_name"
    expr: "CEL expression returning boolean"
    failure_msg: "Message shown when rule fails"
    severity: warn  # or error (default: error)

The input Object

Available Fields:

FieldTypeDescription
input.toolslistAll discovered tools
input.tools[].namestringTool name
input.tools[].descriptionstringTool description
input.tools[].risk_levelstring"LOW", "MEDIUM", "HIGH"
input.tools[].inputSchemamapJSON Schema
input.artifactobject(With --lockfile) Package metadata
input.provenanceobject(With --lockfile and provenance) SLSA attestation

Common Policy Patterns

Pattern 1: No High-Risk Tools

- name: "no_high_risk"
  expr: '!input.tools.exists(t, t.risk_level == "HIGH")'
  failure_msg: "High-risk tools are not allowed"

Pattern 2: Tool Denylist

- name: "no_dangerous_names"
  expr: '!input.tools.exists(t, t.name in ["exec", "shell", "eval"])'
  failure_msg: "Dangerous tool name detected"

Pattern 3: Require Namespace Prefix

- name: "namespaced_tools"
  expr: 'input.tools.all(t, t.name.startsWith("myorg_"))'
  failure_msg: "All tools must be prefixed with 'myorg_'"

Pattern 4: Limit Arguments

- name: "max_args"
  expr: 'input.tools.all(t, size(t.inputSchema.properties) <= 5)'
  failure_msg: "Tools cannot have more than 5 arguments"

Pattern 5: Require Provenance (Supply Chain)

- name: "require_slsa"
  expr: 'has(input.provenance) && input.provenance.method == "cosign_slsa"'
  failure_msg: "Artifact must have verified SLSA provenance"

Pattern 6: Trusted Source Only

- name: "trusted_org"
  expr: |
    has(input.provenance) &&
    input.provenance.source_repo.matches("^https://github.com/(myorg|trustedvendor)/.*")
  failure_msg: "Artifact must come from trusted GitHub org"

Running Policies

# With external policy file
mcptrust policy check --policy ./policy.yaml -- "npx -y @scope/server"
 
# With preset
mcptrust policy check --preset strict -- "npx -y @scope/server"
 
# With lockfile (enables supply chain fields)
mcptrust policy check --lockfile mcp-lock.json --policy ./policy.yaml -- "npx -y @scope/server"

Presets

PresetDescriptionSeverityKey Rules
baselinePermissive, warnings onlywarnTools exist, no obvious bads
strictFail on any issueerrorPinning required, no HIGH risk