Skip to content

Policy Design Patterns ​

Best practices and proven patterns for writing effective MeshGuard policies that scale.

Why Patterns Matter ​

Writing AI agent policies is a new discipline. Unlike traditional access control (RBAC for users, IAM for cloud resources), agent policies must handle:

  • Autonomous decision-making: Agents act without human approval for each action
  • Dynamic contexts: The same agent might need different permissions at different times
  • Complex delegation: Agent A asks Agent B to do something on A's behalf
  • High velocity: Agents can attempt thousands of actions per minute

Without established patterns, policy authors tend to either:

  1. Over-permit: Grant broad access to "make it work," creating security holes
  2. Over-restrict: Lock everything down, making agents useless

This guide presents battle-tested patterns that balance security with functionality.

The Foundation: Deny-by-Default ​

Every effective policy starts with the same foundation: deny everything unless explicitly permitted.

The Pattern ​

yaml
name: foundation-policy
version: 1

rules:
  # Catch-all: deny everything by default
  - action: "*"
    effect: deny
    reason: "Action not explicitly permitted"
    
  # Then add specific allows
  - action: "read:public_data"
    effect: allow

Why It Works ​

With deny-by-default:

  • New actions are blocked until reviewed
  • Forgotten permissions don't create vulnerabilities
  • You can audit exactly what's allowed

The Anti-Pattern: Allow-by-Default ​

yaml
# ❌ DANGEROUS: Don't do this
rules:
  - action: "*"
    effect: allow
    
  - action: "admin:*"
    effect: deny  # Trying to block specific things

This seems simpler but fails because:

  • You must anticipate every dangerous action
  • New features are automatically permitted
  • One missed deny creates a vulnerability

Real-World Example: Customer Service Agent ​

A customer service agent needs to read customer data and create tickets. With deny-by-default:

yaml
name: customer-service-foundation
version: 1

rules:
  # Start with deny-all
  - action: "*"
    effect: deny
    reason: "Customer service agent has limited permissions"
    
  # Explicitly allow required actions
  - action: "read:customer_profile"
    effect: allow
    
  - action: "read:order_history"
    effect: allow
    
  - action: "write:support_ticket"
    effect: allow
    
  # Note: agent cannot write:customer_profile, delete anything,
  # or access billing — because we didn't allow it

Role-Based Patterns ​

Different agents have different jobs. Role-based patterns group permissions by function.

The Pattern ​

Define policies for roles, then assign agents to roles:

yaml
name: role-customer-service-basic
version: 1
description: "Basic customer service capabilities"

rules:
  - action: "*"
    effect: deny
    
  - action: "read:customer_profile"
    effect: allow
    
  - action: "read:order_history"
    effect: allow
    
  - action: "write:support_ticket"
    effect: allow
    
  - action: "write:internal_note"
    effect: allow
yaml
name: role-customer-service-senior
version: 1
description: "Senior CS with refund authority"
extends: role-customer-service-basic

rules:
  # Inherits all basic permissions, plus:
  - action: "write:refund"
    effect: allow
    conditions:
      - "request.amount <= 100"
      
  - action: "write:account_credit"
    effect: allow
    conditions:
      - "request.amount <= 50"

Why It Works ​

Role-based patterns provide:

  • Consistency: All agents in a role have the same permissions
  • Maintainability: Update the role, update all agents
  • Clarity: "What can this agent do?" → "What role is it?"

Role Hierarchy Example ​

┌─────────────────────────────────────────────────────────┐
│                    admin-agent                          │
│   Can: Everything including policy management           │
└─────────────────────────────────────────────────────────┘
                          │
                          │ extends
                          ▼
┌─────────────────────────────────────────────────────────┐
│                   senior-cs-agent                       │
│   Can: Basic CS + refunds up to $100 + credits          │
└─────────────────────────────────────────────────────────┘
                          │
                          │ extends
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    basic-cs-agent                       │
│   Can: Read customer data, create tickets               │
└─────────────────────────────────────────────────────────┘

The Anti-Pattern: Copy-Paste Policies ​

yaml
# ❌ BAD: Duplicated policies for each agent
name: agent-alice-policy
rules:
  - action: "read:customer_profile"
    effect: allow
  # ... 20 more rules

name: agent-bob-policy  
rules:
  - action: "read:customer_profile"
    effect: allow
  # ... same 20 rules, slightly different

This creates:

  • Drift: Policies diverge over time
  • Maintenance burden: Updates require touching every policy
  • Audit confusion: Why is Bob different from Alice?

Time-Based Patterns ​

Some actions should only be allowed during certain time windows.

The Pattern ​

Use time conditions to restrict when actions are permitted:

yaml
name: business-hours-policy
version: 1

rules:
  - action: "*"
    effect: deny
    
  # Allow customer data access only during business hours
  - action: "read:customer_*"
    effect: allow
    conditions:
      - "time.hour >= 9 AND time.hour < 18"  # 9 AM to 6 PM
      - "time.weekday IN [1,2,3,4,5]"        # Monday-Friday
    
  # After hours: read-only access to knowledge base
  - action: "read:knowledge_base"
    effect: allow

Use Cases ​

1. Maintenance Windows

Allow destructive operations only during scheduled maintenance:

yaml
name: database-maintenance-policy
version: 1

rules:
  # Normal operations always allowed
  - action: "read:database_*"
    effect: allow
    
  - action: "write:database_*"
    effect: allow
    
  # Destructive operations only in maintenance windows
  - action: "delete:database_*"
    effect: allow
    conditions:
      - "time.weekday == 7"           # Sunday only
      - "time.hour >= 2 AND time.hour < 6"  # 2-6 AM
      
  - action: "admin:database_*"
    effect: allow
    conditions:
      - "time.weekday == 7"
      - "time.hour >= 2 AND time.hour < 6"

2. Trading Hours

Financial agents restricted to market hours:

yaml
name: trading-agent-policy
version: 1

rules:
  # Market data always readable
  - action: "read:market_*"
    effect: allow
    
  # Trades only during market hours (9:30 AM - 4 PM ET)
  - action: "write:trade_*"
    effect: allow
    conditions:
      - "time.hour >= 9 AND (time.hour < 16 OR (time.hour == 9 AND time.minute >= 30))"
      - "time.weekday IN [1,2,3,4,5]"
      - "NOT calendar.is_market_holiday"

3. Escalation Windows

Allow autonomous action during off-hours when humans aren't available:

yaml
name: incident-response-policy
version: 1

rules:
  # During business hours: alert humans
  - action: "notify:oncall_team"
    effect: allow
    conditions:
      - "time.hour >= 9 AND time.hour < 18"
      
  # After hours: agent can take autonomous action
  - action: "execute:incident_remediation"
    effect: allow
    conditions:
      - "time.hour < 9 OR time.hour >= 18"  # Off hours
      - "incident.severity IN ['critical', 'high']"

The Anti-Pattern: Hardcoded Time Checks ​

yaml
# ❌ BAD: Hardcoding times without timezone awareness
conditions:
  - "time.hour >= 9"  # Which timezone? Server? User? Agent?

Always specify timezone explicitly:

yaml
# ✅ GOOD: Explicit timezone
conditions:
  - "time.hour >= 9 AND time.hour < 18"
  - "time.timezone == 'America/New_York'"

Resource-Based Patterns ​

Different resources require different protection levels.

The Pattern ​

Classify resources, then apply policies based on classification:

yaml
name: resource-classification-policy
version: 1

rules:
  - action: "*"
    effect: deny
    
  # Public resources: broad access
  - action: "read:*"
    effect: allow
    conditions:
      - "resource.classification == 'public'"
      
  # Internal resources: verified agents
  - action: "read:*"
    effect: allow
    conditions:
      - "resource.classification == 'internal'"
      - "agent.trust_tier IN ['verified', 'trusted', 'privileged']"
      
  # Confidential resources: trusted agents only
  - action: "read:*"
    effect: allow
    conditions:
      - "resource.classification == 'confidential'"
      - "agent.trust_tier IN ['trusted', 'privileged']"
      
  # Restricted resources: privileged agents only
  - action: "read:*"
    effect: allow
    conditions:
      - "resource.classification == 'restricted'"
      - "agent.trust_tier == 'privileged'"

Resource Hierarchies ​

Protect parent-child relationships:

yaml
name: database-resource-policy
version: 1

rules:
  # All agents can query analytics database
  - action: "read:database.analytics.*"
    effect: allow
    
  # Only trusted agents can query production
  - action: "read:database.production.*"
    effect: allow
    conditions:
      - "agent.trust_tier IN ['trusted', 'privileged']"
      
  # Only privileged agents can write to production
  - action: "write:database.production.*"
    effect: allow
    conditions:
      - "agent.trust_tier == 'privileged'"
      
  # Nobody can drop production tables without explicit approval
  - action: "admin:database.production.drop_table"
    effect: deny
    reason: "Table drops require manual approval process"

Data-Type Policies ​

Apply rules based on data types:

yaml
name: pii-protection-policy
version: 1

rules:
  # PII can be read but not exported
  - action: "read:*.pii_*"
    effect: allow
    conditions:
      - "agent.trust_tier IN ['trusted', 'privileged']"
      
  - action: "export:*.pii_*"
    effect: deny
    reason: "PII cannot be exported by agents"
    
  # Anonymized data can be exported
  - action: "export:*.anonymized_*"
    effect: allow
    
  # Financial data requires additional logging
  - action: "*:*.financial_*"
    effect: allow
    conditions:
      - "agent.trust_tier IN ['trusted', 'privileged']"
    audit:
      level: enhanced
      notify: ["compliance@company.com"]

The Anti-Pattern: Flat Resource Permissions ​

yaml
# ❌ BAD: No resource distinction
rules:
  - action: "read:database"
    effect: allow
    
  - action: "write:database"
    effect: allow

This allows reading/writing anything in any database. Classify and protect.

Conditional Patterns ​

Real-world policies often need multiple conditions working together.

The Pattern: AND Conditions ​

All conditions must be true:

yaml
rules:
  - action: "write:refund"
    effect: allow
    conditions:
      - "agent.trust_tier == 'trusted'"      # Must be trusted
      - "request.amount <= 100"               # Must be small
      - "customer.account_age_days >= 30"     # Must be established customer

The Pattern: OR Conditions ​

Any condition being true is sufficient:

yaml
rules:
  - action: "read:confidential_docs"
    effect: allow
    conditions:
      any:
        - "agent.department == 'legal'"
        - "agent.department == 'executive'"
        - "agent.has_role == 'auditor'"

Complex Conditionals ​

Combine AND and OR:

yaml
rules:
  - action: "write:large_transaction"
    effect: allow
    conditions:
      all:
        - "request.amount > 10000"
        any:
          - "agent.trust_tier == 'privileged'"
          - all:
              - "agent.trust_tier == 'trusted'"
              - "request.has_dual_approval == true"

This allows large transactions if:

  • Agent is privileged, OR
  • Agent is trusted AND has dual approval

Real-World Example: Data Processing Agent ​

A data processing agent that handles ETL pipelines:

yaml
name: data-processor-policy
version: 1
description: "ETL pipeline agent with environment-aware permissions"

rules:
  - action: "*"
    effect: deny
    
  # Read from any source
  - action: "read:source.*"
    effect: allow
    
  # Write to staging in any environment
  - action: "write:staging.*"
    effect: allow
    
  # Write to production only if:
  # - Pipeline has been tested (staging success)
  # - It's during approved deployment window
  # - Data validation passed
  - action: "write:production.*"
    effect: allow
    conditions:
      all:
        - "pipeline.staging_status == 'success'"
        - "pipeline.validation_status == 'passed'"
        - any:
            - "deploy_window.is_active == true"
            - "request.emergency_override == true AND agent.trust_tier == 'privileged'"
    
  # Delete operations only in non-production
  - action: "delete:*"
    effect: allow
    conditions:
      - "resource.environment != 'production'"

Anti-Patterns to Avoid ​

Learn from common mistakes.

Anti-Pattern 1: The "God Agent" ​

yaml
# ❌ TERRIBLE: One agent that can do everything
name: super-agent-policy
rules:
  - action: "*"
    effect: allow

Why it's bad:

  • No audit granularity
  • Compromise means total access
  • Impossible to debug behavior

Fix: Create specific roles even if one team manages the agent.

Anti-Pattern 2: Permission Creep ​

yaml
# Policy version 1
rules:
  - action: "read:orders"
    effect: allow

# Version 2: "Just add this one thing"
rules:
  - action: "read:orders"
    effect: allow
  - action: "write:orders"    # Added
    effect: allow

# Version 47: "I don't know what this agent can do anymore"
rules:
  - action: "read:*"
    effect: allow
  - action: "write:*"
    effect: allow
  - action: "delete:*"        # How did this get here?
    effect: allow

Fix: Regular policy reviews. Document why each permission exists.

Anti-Pattern 3: Overly Broad Wildcards ​

yaml
# ❌ BAD: Wildcards without constraints
rules:
  - action: "read:customer_*"
    effect: allow  # Matches customer_pii, customer_credit_card, etc.

Fix: Be explicit or add conditions:

yaml
# ✅ GOOD: Explicit actions
rules:
  - action: "read:customer_profile"
    effect: allow
  - action: "read:customer_orders"
    effect: allow
  # Explicitly not including customer_pii

# ✅ ALSO GOOD: Wildcard with exclusions
rules:
  - action: "read:customer_*"
    effect: allow
    conditions:
      - "NOT action MATCHES 'read:customer_pii'"
      - "NOT action MATCHES 'read:customer_payment_*'"

Anti-Pattern 4: Deny Without Reason ​

yaml
# ❌ BAD: No context for denials
rules:
  - action: "admin:*"
    effect: deny

Fix: Always explain why:

yaml
# ✅ GOOD: Reason helps debugging and auditing
rules:
  - action: "admin:*"
    effect: deny
    reason: "Admin operations require manual approval. Contact platform-team@company.com"

Anti-Pattern 5: Testing in Production ​

Writing policies directly against production resources without testing.

Fix: Use a staged approach (see next section).

Testing and Iterating on Policies ​

Policies should be developed like software: write, test, deploy, monitor.

Stage 1: Dry-Run Mode ​

Test policies without enforcement:

yaml
name: new-policy-v1
version: 1
enforcement: dry_run  # Log decisions but don't enforce

rules:
  - action: "write:*"
    effect: deny

In dry-run mode:

  • All actions are allowed (as if policy didn't exist)
  • Decisions are logged with "would have been denied" markers
  • You can analyze impact before enforcing

Stage 2: Shadow Testing ​

Run the new policy alongside the old one:

python
# Both policies evaluate, only old one enforces
shadow_config = {
    "active_policy": "existing-policy-v5",
    "shadow_policy": "new-policy-v1",
}

Compare decisions:

  • Where do they agree?
  • Where do they disagree?
  • Are the disagreements intentional?

Stage 3: Gradual Rollout ​

Enforce for a subset of agents first:

yaml
name: new-policy-v1
version: 1
enforcement: enabled
rollout:
  percentage: 10  # Only 10% of matching agents
  # OR
  agents: ["test-agent-1", "test-agent-2"]  # Specific agents only

Stage 4: Full Deployment ​

After validation, remove rollout restrictions:

yaml
name: new-policy-v1
version: 1
enforcement: enabled
# rollout removed = applies to all matching agents

Monitoring Policy Performance ​

Track key metrics:

yaml
# In your policy config
monitoring:
  alert_on:
    - denial_rate > 20%       # Too many denials might indicate policy is too strict
    - denial_rate < 0.1%      # Too few denials might indicate policy is too permissive
    - unknown_actions > 10/hr # Actions hitting catch-all deny (need explicit rules)

Policy Versioning ​

Always version your policies:

yaml
name: customer-service-policy
version: 12  # Increment on every change
changelog:
  - version: 12
    date: 2026-01-25
    author: platform-team
    changes: "Added refund limits for fraud prevention"
  - version: 11
    date: 2026-01-10
    changes: "Expanded read access to order history"

Real-World Scenario: Admin Agent ​

An admin agent needs powerful capabilities but must be tightly controlled.

yaml
name: admin-agent-policy
version: 1
description: "System administration agent with enhanced logging"

# Require privileged trust tier
required_trust_tier: privileged

rules:
  - action: "*"
    effect: deny
    reason: "Admin actions must be explicitly permitted"
    
  # System health: always allowed
  - action: "read:system.*"
    effect: allow
    
  # Configuration changes: allowed with logging
  - action: "write:config.*"
    effect: allow
    audit:
      level: enhanced
      include_diff: true  # Log what changed
      
  # Service restarts: allowed during maintenance windows
  - action: "admin:service.restart"
    effect: allow
    conditions:
      - "time.is_maintenance_window == true"
    audit:
      level: enhanced
      notify: ["oncall@company.com"]
      
  # User management: requires dual approval
  - action: "admin:user.*"
    effect: allow
    conditions:
      - "request.approval_count >= 2"
    audit:
      level: enhanced
      notify: ["security@company.com"]
      
  # Destructive operations: never automated
  - action: "admin:*.delete"
    effect: deny
    reason: "Destructive admin operations require manual execution"
    
  - action: "admin:*.drop"
    effect: deny
    reason: "Destructive admin operations require manual execution"

Putting It All Together ​

Here's how patterns combine in practice:

yaml
name: comprehensive-agent-policy
version: 3
description: "Production policy for customer-facing agents"

# Foundation: Deny by default
default_effect: deny

# Role-based: Inherit from base role
extends: role-customer-service-basic

rules:
  # Resource-based: Different data, different rules
  - action: "read:customer.public_*"
    effect: allow
    
  - action: "read:customer.private_*"
    effect: allow
    conditions:
      - "agent.trust_tier IN ['trusted', 'privileged']"
      
  # Time-based: Refunds only during business hours
  - action: "write:refund"
    effect: allow
    conditions:
      - "agent.trust_tier == 'trusted'"
      - "request.amount <= 100"
      - "time.hour >= 9 AND time.hour < 18"
      - "time.timezone == 'America/New_York'"
      
  # Conditional: Large refunds need approval
  - action: "write:refund"
    effect: allow
    conditions:
      - "request.amount > 100 AND request.amount <= 500"
      - "request.manager_approval == true"

# Monitoring
monitoring:
  alert_on:
    - denial_rate > 15%
    - "action:write:refund denial_count > 50/day"

Key Takeaways ​

  1. Start with deny-by-default — it's the only safe foundation
  2. Use roles — group permissions by function, not by agent
  3. Add time constraints — not everything should be allowed 24/7
  4. Classify resources — different data needs different protection
  5. Test before deploying — use dry-run and shadow modes
  6. Monitor and iterate — policies are never "done"
  7. Document everything — reasons, changelogs, and decisions

Good policy design is the difference between AI agents that enhance your business and AI agents that become liabilities.


Next Steps ​

Ready to dive deeper? Explore these related concepts:

Design Better Policies Today

MeshGuard provides the tools to implement these patterns at scale — policy versioning, dry-run testing, real-time monitoring, and comprehensive audit logs.

Start building with MeshGuard →

Built for the agentic era.