Skip to content

Policy Authoring Guide

This guide explains how to write effective policies for Plan-Lint using Rego, the policy language used by the Open Policy Agent (OPA).

Introduction to Rego

Rego is a declarative query language designed for expressing policies. In Plan-Lint, we use Rego to define rules that agent plans must follow before execution is allowed.

Basic Rego Concepts

A Rego policy consists of:

  • Package declaration: Defines the namespace for your policy
  • Rules: Logical expressions that evaluate to true or false
  • Functions: Reusable blocks of logic
  • Data references: Ways to access the input document

Creating Your First Policy

Let's start with a basic policy that checks if a plan contains sensitive operations:

package plan_lint

# Define a rule that flags file deletion operations
violation[{"message": msg}] {
    step := input.steps[_]
    step.tool == "delete_file"

    msg := sprintf("Plan attempts to delete a file: %v", [step.parameters.target_file])
}

# Flag large transactions
violation[{"message": msg}] {
    step := input.steps[_]
    step.tool == "execute_transaction"
    to_number(step.parameters.amount) > 1000

    msg := sprintf("Transaction amount exceeds limit: %v", [step.parameters.amount])
}

Input Structure

Plan-Lint passes the plan structure to your policies as the input document. A typical plan structure looks like:

{
  "steps": [
    {
      "id": "step1",
      "tool": "tool_name",
      "parameters": {
        "param1": "value1",
        "param2": "value2"
      },
      "depends_on": ["step0"]
    }
  ]
}

Common Policy Patterns

Checking Tool Usage

Restrict which tools can be used:

violation[{"message": msg}] {
    step := input.steps[_]
    forbidden_tools := {"execute_sql", "delete_file", "system_command"}
    step.tool == forbidden_tools[_]

    msg := sprintf("Forbidden tool usage: %v", [step.tool])
}

Parameter Validation

Check parameter values for potential issues:

violation[{"message": msg}] {
    step := input.steps[_]
    step.tool == "execute_sql"
    contains(step.parameters.query, "DROP TABLE")

    msg := "SQL query contains potentially dangerous DROP TABLE statement"
}

Dependency Validation

Ensure proper step dependencies:

violation[{"message": msg}] {
    step := input.steps[_]
    step.tool == "commit_transaction"

    # Check that there's a step that begins the transaction
    not has_begin_transaction

    msg := "Transaction committed without being started"
}

has_begin_transaction {
    some i
    step := input.steps[i]
    step.tool == "begin_transaction"
}

Testing Your Policies

You can test your policies in isolation using the OPA CLI:

# Save your policy to a file named policy.rego
# Create a test plan file plan.json
# Run OPA evaluation
opa eval -i plan.json -d policy.rego "data.plan_lint.violation"

Alternatively, use Plan-Lint's built-in policy testing:

from plan_lint import validate_plan

result = validate_plan(
    plan_data,
    policies=["path/to/your/policy.rego"]
)

print(f"Valid: {result.valid}")
for violation in result.violations:
    print(f"Violation: {violation.message}")

Advanced Techniques

Using Context Information

Policies can use external context provided to the validator:

violation[{"message": msg}] {
    # Access context variables
    role := input.context.user_role
    role != "admin"

    # Check for privileged operations
    step := input.steps[_]
    privileged_tools := {"system_command", "execute_sql"}
    step.tool == privileged_tools[_]

    msg := sprintf("User with role '%v' cannot use tool '%v'", [role, step.tool])
}

Pattern Matching with Regex

Use regex for more flexible matching:

violation[{"message": msg}] {
    step := input.steps[_]
    step.tool == "web_request"
    url := step.parameters.url

    # Check if URL is not HTTPS
    not regex.match("^https://", url)

    msg := sprintf("Insecure URL detected: %v", [url])
}

Best Practices

  1. Keep policies simple and focused: Each policy should address a specific concern
  2. Use meaningful violation messages: Explain why something was flagged
  3. Group related rules together: Organize rules by the type of protection they provide
  4. Comment your policy code: Explain the rationale behind complex rules
  5. Test with both valid and invalid plans: Ensure policies are catching what they should

Example Policy Library

Plan-Lint comes with several example policies you can adapt:

  • security.rego: Basic security checks for dangerous operations
  • data_privacy.rego: Checks for exposure of PII and sensitive data
  • authorization.rego: Role-based access controls

You can find these in the examples/policies directory of the Plan-Lint repository.