"Why is my rule failing? It worked on the playground!" β Every Rego author at least once. Over the past few months we've written dozens of Wiz Cloud Configuration Rules. Instead of letting you suffer through the same debugging sessions, here's our guide to the most common Rego pitfalls in Wiz and exactly how to fix them.
Fixing Rego Undefined Decision Errors in Wiz Rules
This is the classic error. You write a rule that looks perfect, but it returns nothing.
package wiz
default versioning_enabled = false
# This will fail with "undefined decision" if versioning doesn't exist
versioning_enabled {
input.Provider == "AWS"
input.Type == "STORAGE_BUCKET"
input.StorageBucket.versioning.status == "Enabled" # π₯ Boom!
} How to Fix Undefined Decision Errors: Defensive Checks in Rego
Rego stops evaluation if it tries to access a field that doesn't exist. Always check that parent objects exist before accessing their children.
versioning_enabled {
input.Provider == "AWS"
input.Type == "STORAGE_BUCKET"
input.StorageBucket != null
input.StorageBucket.versioning != null
input.StorageBucket.versioning.status == "Enabled"
} Resolving OPA Rego Unsafe Variable Errors: Grounding Techniques
Rego's safety checker needs to guarantee a variable is grounded. This error appears when it can't be sure a variable will have a value.
# This throws unsafe var error
violations[msg] {
resource := input.resources[_]
missing := required_tags[_]
not resource.tags[missing] # π₯ 'tags' might not exist!
msg := sprintf("Resource %s missing tag: %s", [resource.id, missing])
} The Fix: Ground Your Variables
Ensure that any variable used in a `not` clause or complex expression has been safely introduced, often by checking its parent for existence.
violations[msg] {
resource := input.resources[_]
resource.tags != null # β
Now Rego knows tags exists
missing := required_tags[_]
not resource.tags[missing]
msg := sprintf("Resource %s missing tag: %s", [resource.id, missing])
} Rego Regex Best Practices: Avoiding Common Pattern Matching Errors
A common mistake is assuming `regex.match` checks for substrings. It doesn't; it matches against the entire string.
# Trying to check if a bucket name contains "prod"
prod_bucket {
input.Type == "STORAGE_BUCKET"
regex.match(".*prod.*", input.Name) # Works but...
}
# This looks cleaner but FAILS
prod_bucket {
input.Type == "STORAGE_BUCKET"
regex.match("prod", input.Name) # π₯ This checks EXACT match!
} The Fix: Use Wildcards or `contains()`
For partial matches, wrap your pattern in `.*`. For simple substring checks, `contains()` is often cleaner.
# Option 1: Regex with wildcards
prod_bucket {
regex.match(".*prod.*", input.Name)
}
# Option 2: Use contains (cleaner for simple cases)
prod_bucket {
contains(input.Name, "prod")
} Rego Assignment vs Comparison Operators: When to Use = vs ==
This one gets developers coming from other languages. A single `=` is for assignment or unification, not for checking equality.
# This ALWAYS returns true! π€¦
check_encryption {
input.encryption = true # This is assignment, not comparison!
}
# What you meant:
check_encryption {
input.encryption == true # Comparison
} The Fix: Always Use `==` for Comparison
Double-check your operators. For boolean checks, you can often omit `== true` entirely.
check_encryption {
input.encryption == true
}
# Or even cleaner for booleans:
check_encryption {
input.encryption # Implicitly checks if true
} 5. Array Iteration Gone Wrong
Iterating over a list that might be null or empty will result in an undefined error.
# Trying to check if ANY security group allows 0.0.0.0/0
public_sg {
input.Type == "SECURITY_GROUP"
input.rules[_].cidr == "0.0.0.0/0" # π₯ What if rules is empty or null?
} The Fix: Check Existence or Provide a Default
Confirm the array exists and is not empty before iterating, or use `object.get` to provide an empty array `[]` as a safe default.
public_sg {
input.Type == "SECURITY_GROUP"
input.rules != null
count(input.rules) > 0
input.rules[_].cidr == "0.0.0.0/0"
}
# Or use a default:
public_sg {
input.Type == "SECURITY_GROUP"
rules := object.get(input, "rules", []) # Default to empty array
rules[_].cidr == "0.0.0.0/0"
} 6. Multi-Cloud Madness: Different Fields, Same Concept
A policy written for an AWS S3 bucket won't work on an Azure Storage Account because the field names for concepts like "encryption" are different.
# This only works for AWS
encryption_enabled {
input.Provider == "AWS"
input.encryption.enabled == true
}
# Fails for Azure because field name is different! The Fix: Provider-Specific Rules
Write separate helper rules for each cloud provider and unify them in a main rule. This keeps your logic clean and maintainable.
# AWS check
encryption_enabled {
input.Provider == "AWS"
input.Type == "STORAGE_BUCKET"
input.encryption.enabled == true
}
# Azure check
encryption_enabled {
input.Provider == "AZURE"
input.Type == "STORAGE_ACCOUNT"
input.properties.encryption.services.blob.enabled == true
}
# GCP check
encryption_enabled {
input.Provider == "GCP"
input.Type == "STORAGE_BUCKET"
input.encryption.defaultKmsKeyName != null
} 7. The Silent Type Mismatch
JSON doesn't enforce types, so a port number could be an integer `1024` or a string `"1024"`. Mathematical comparisons will fail on the string.
# This might not work as expected
high_port {
input.port > 1024 # What if port is a string "8080"?
} The Fix: Explicit Type Conversion
Use `to_number()` to safely convert a variable before performing mathematical operations.
high_port {
port := to_number(input.port)
port > 1024
}
# Or handle both cases:
high_port {
# If it's already a number
is_number(input.port)
input.port > 1024
}
high_port {
# If it's a string
is_string(input.port)
port := to_number(input.port)
port > 1024
} π Key Takeaways
- Be Defensive: Always check for null/undefined values before accessing nested properties.
- Be Explicit: Use `==` for comparison and `to_number()` for type safety.
- Be Precise: Remember `regex.match` requires a full match. Use `contains()` for simpler cases.
- Be Modular: Break complex logic into smaller, reusable helper rules, especially for multi-cloud policies.
- Be Thorough: Test with various input scenarios, including empty and missing data.
Conclusion
Rego in Wiz can be tricky, but most issues come down to these common pitfalls. Bookmark this guide, and the next time you see "undefined decision" or "unsafe var", you'll know exactly what to do. Remember: defensive programming in Rego isn't paranoidβit's necessary.