Policy Formats¶
This page explains the different formats supported by Plan-Lint for defining policies.
Overview¶
Plan-Lint supports two main policy formats:
- YAML Policies: A simpler, declarative format for defining basic rules
- Rego Policies: A powerful policy language for expressing complex rules
Each format has its own advantages, and you can choose the one that best fits your needs.
YAML Policies¶
YAML policies provide a simple, declarative way to define validation rules. They are easy to write and understand, making them a good choice for basic validation scenarios.
Basic Structure¶
YAML policies are structured as key-value pairs defining rules:
# List of allowed tools
allow_tools:
- db.query_ro
- notify.email
- payments.transfer
# Parameter boundaries
bounds:
payments.transfer.amount: [0.01, 10000] # Numeric boundaries
notify.email.to_list: [1, 100] # List size boundaries
# Patterns to detect
deny_tokens_regex:
- "password"
- "secret"
- "apikey"
- "\\bsql\\b"
# Risk weights for violations
risk_weights:
excessive_amount: 0.4
sensitive_data: 0.5
sql_injection: 0.6
# Threshold for failing validation
fail_risk_threshold: 0.8
# Maximum number of steps allowed in a plan
max_steps: 20
Available Rules¶
| Rule | Description | Example |
|---|---|---|
allow_tools |
List of permitted tools | - db.query_ro |
bounds |
Parameter boundaries | payments.amount: [0.01, 10000] |
deny_tokens_regex |
Prohibited patterns | - "password" |
allow_tokens_regex |
Permitted patterns | - "safe_pattern" |
risk_weights |
Violation risk weights | sql_injection: 0.6 |
fail_risk_threshold |
Risk threshold | 0.8 |
max_steps |
Maximum plan steps | 20 |
Example YAML Policy¶
Here's a complete example of a YAML policy:
# policy.yaml
allow_tools:
- db.query_ro
- db.query
- payments.transfer
- analytics.summarize
- notify.customer
bounds:
payments.transfer.amount: [0.01, 10000.00]
db.query.limit: [1, 1000]
deny_tokens_regex:
- "1=1"
- "OR 1=1"
- "DROP TABLE"
- "--"
- "password"
- "secret"
- "apikey"
- "creditCard"
- "ssn"
risk_weights:
tool_deny: 0.9
excessive_amount: 0.4
insufficient_amount: 0.1
sensitive_data: 0.5
sql_injection: 0.6
fail_risk_threshold: 0.8
max_steps: 10
Loading a YAML Policy¶
from plan_lint import validate_plan
from plan_lint.loader import load_policy
# Load the YAML policy
policy = load_policy("path/to/policy.yaml")
# Validate a plan against the policy
result = validate_plan(plan, policy=policy)
Rego Policies¶
For more advanced policy definitions, Plan-Lint supports Rego, the policy language of Open Policy Agent (OPA). Rego provides a powerful and flexible way to define complex validation rules.
Basic Structure¶
A Rego policy for Plan-Lint typically includes:
package planlint
import future.keywords.in
# Default deny policy - all plans are denied unless explicitly allowed
default allow = false
# Default empty violations
default violations = []
# Default risk score is 0
default risk_score = 0.0
# Define allowed tools and other policy components
allowed_tools = {
"db.query_ro": {},
"notify.email": {},
"payments.transfer": {"min_amount": 0.01, "max_amount": 10000}
}
# Allow rule - defines when a plan should be allowed
allow if {
# Add conditions for allowing a plan
all_tools_allowed
risk_score < 0.8
}
# Helper rule - check if all tools in the plan are allowed
all_tools_allowed if {
# Logic to check if all tools are allowed
tools_in_plan := [step.tool | step := input.plan.steps[_]]
not_allowed := [tool | tool := tools_in_plan[_]; not tool in object.keys(allowed_tools)]
count(not_allowed) == 0
}
# Define violations
violations[result] {
# Logic to detect violations
step := input.plan.steps[_]
step.tool == "payments.transfer"
amount := to_number(step.parameters.amount)
# Check if the amount exceeds the maximum allowed
max_amount := allowed_tools["payments.transfer"].max_amount
amount > max_amount
# Return a violation result
result := {
"rule": "excessive_amount",
"message": sprintf("Amount %f exceeds maximum allowed %f", [amount, max_amount]),
"severity": "high",
"category": "security",
"step_id": step.id
}
}
# Calculate risk score based on violations
risk_score = total {
violation_scores := [violation.risk_score | violation := violations[_]]
total := sum(violation_scores)
}
Input Structure¶
In your Rego policies, you have access to the plan via the input document:
input.plan.steps # Array of plan steps
input.plan.goal # Plan goal (if provided)
input.plan.context # Plan context (if provided)
input.context # Additional context provided during validation
Example Rego Policy¶
Here's a more complete example of a Rego policy for Plan-Lint:
package planlint
import future.keywords.in
# Default deny policy
default allow = false
default violations = []
default risk_score = 0.0
# Set of allowed tools with constraints
allowed_tools = {
"db.query_ro": {},
"db.query": {"max_joins": 3},
"payments.transfer": {"min_amount": 0.01, "max_amount": 10000},
"notify.email": {"max_recipients": 100},
"analytics.summarize": {}
}
# Allow rule - defines when a plan should be allowed
allow if {
all_tools_allowed
risk_score < 0.8
}
# Check if all tools in the plan are allowed
all_tools_allowed if {
tools_in_plan := [step.tool | step := input.plan.steps[_]]
not_allowed := [tool | tool := tools_in_plan[_]; not tool in object.keys(allowed_tools)]
count(not_allowed) == 0
}
# Collect all violations
violations = all_violations {
all_violations := array.concat(
sql_injection_violations,
excessive_amount_violations,
sensitive_data_violations
)
}
# Detect SQL injection vulnerabilities
sql_injection_violations[result] {
step := input.plan.steps[_]
step.tool in ["db.query", "db.query_ro"]
# SQL injection patterns
patterns := ["'--", "1=1", "'; DROP", "OR 1=1"]
# Check if query contains any dangerous patterns
query := lower(step.parameters.query)
some pattern in patterns
contains(query, pattern)
result := {
"rule": "sql_injection",
"message": sprintf("Potential SQL injection detected in query: %s", [pattern]),
"severity": "high",
"category": "security",
"step_id": step.id,
"risk_score": 0.8
}
}
# Detect excessive transaction amounts
excessive_amount_violations[result] {
step := input.plan.steps[_]
step.tool == "payments.transfer"
amount := to_number(step.parameters.amount)
max_amount := allowed_tools["payments.transfer"].max_amount
amount > max_amount
result := {
"rule": "excessive_amount",
"message": sprintf("Transaction amount %f exceeds maximum allowed %f", [amount, max_amount]),
"severity": "high",
"category": "security",
"step_id": step.id,
"risk_score": 0.6
}
}
# Detect sensitive data exposure
sensitive_data_violations[result] {
step := input.plan.steps[_]
# Look for sensitive parameter names
sensitive_patterns := ["password", "secret", "token", "key", "credential"]
some param_name, param_value in step.parameters
some pattern in sensitive_patterns
contains(lower(param_name), pattern)
result := {
"rule": "sensitive_data_exposure",
"message": sprintf("Step contains sensitive parameter '%s'", [param_name]),
"severity": "medium",
"category": "privacy",
"step_id": step.id,
"risk_score": 0.5
}
}
# Calculate risk score based on violations
risk_score = total {
violation_scores := [v.risk_score | v := violations[_]]
total := sum(violation_scores)
}
Loading a Rego Policy¶
from plan_lint import validate_plan
from plan_lint.loader import load_policy
# Load the Rego policy
policy_path = "path/to/policy.rego"
_, rego_policy = load_policy(policy_path)
# Validate a plan against the Rego policy
result = validate_plan(plan, rego_policy=rego_policy, use_opa=True)
Choosing a Policy Format¶
Here's a comparison to help you choose the right policy format:
| Feature | YAML Policies | Rego Policies |
|---|---|---|
| Ease of Use | Simple to write and understand | More complex but powerful |
| Flexibility | Limited to predefined rules | Unlimited flexibility |
| Logic Complexity | Basic constraints | Complex logical expressions |
| Custom Rules | Limited to existing rule types | Custom rule definition |
| Use Case | Simple security rules | Complex security policies |
Best Practices¶
- Start Simple: Begin with YAML policies for basic validation needs.
- Modular Policies: Split complex Rego policies into logical modules.
- Test Thoroughly: Test policies against both valid and invalid plans.
- Clear Messages: Write clear violation messages to help users understand issues.
- Progressive Enhancement: Gradually add more sophisticated rules as needed.
- Severity Levels: Use consistent severity levels across policies.
Converting Between Formats¶
Plan-Lint provides utilities to convert between YAML and Rego formats:
from plan_lint.converter import yaml_to_rego
# Convert a YAML policy to Rego
yaml_policy_path = "path/to/policy.yaml"
rego_policy = yaml_to_rego(yaml_policy_path)
# Save the Rego policy
with open("policy.rego", "w") as f:
f.write(rego_policy)
This allows you to start with a simpler YAML policy and convert it to Rego when you need more complex validation logic.