Testing Policies
Test and debug your authorization policies
Testing is essential for building reliable authorization policies. A policy that allows too much creates security vulnerabilities. A policy that denies too much breaks your application. This guide covers how to thoroughly test your policies using the Denied Platform Playground.
Policy Playground
The Policy Playground lets you test policies against your configured decision node without writing any application code. It's the fastest way to validate that your policies work as expected.
Accessing the Playground
- Go to Governance → Playground
- Ensure you have the correct project selected
- Verify your decision node is configured and active
The Playground requires a running decision node. If you see connection errors, check that your decision node URL is configured in project settings and the service is accessible.
Playground Interface
The Playground has three main sections:
| Section | Purpose |
|---|---|
| Subject | Define who is making the request (user, agent, service) |
| Resource | Define what they want to access |
| Action | Specify the operation being attempted |
Plus additional properties you can add to any section.
Constructing Test Requests
Subject Configuration
Enter details about the requesting entity:
Required fields:
- Type: The subject's type (e.g.
user,agent,service) - ID: The subject's unique identifier scoped to the type (e.g.
john.doe,hr-assistant)
Adding properties: Click "Add Property" to include additional fields your policies use:
| Key | Value |
|---|---|
role | admin |
department | engineering |
verified | true |
Resource Configuration
Define the resource being accessed:
Optional fields:
- Type: The resource's type (e.g.
document,api,database) - ID: The resource's unique identifier scoped to the type (e.g.
doc123)
Adding properties: Include fields your policies check:
| Key | Value |
|---|---|
owner | user://john.doe |
type | document |
visibility | public |
classification | internal |
Action
Specify what operation is being attempted:
read
write
delete
approve
exportProperty Value Types
The Playground automatically parses property values:
| Input | Parsed As |
|---|---|
hello | String: "hello" |
123 | Number: 123 |
true / false | Boolean |
{"key": "value"} | JSON object |
["a", "b"] | JSON array |
Running Tests
Basic Test Flow
- Fill in the subject, resource, and action
- Add any necessary properties
- Click Evaluate
- Review the result
Understanding Results
Allowed Result:
Result: ALLOWED
Matched Rules: allow-own-resources, admin-accessThis means:
- The request was allowed
- Two policies contributed to the allow decision
- Either rule matching would have allowed the request
Denied Result:
Result: DENIED
Matched Rules: (none)This means:
- No allow policies matched the request
- Access was denied by default (secure by default)
Request History
The Playground maintains a history of your test requests. Use this to:
- Compare results across different inputs
- Quickly re-run previous tests
- Track what you've already tested
Test Case Design
Positive Tests
Test cases where access should be allowed:
Test: Admin can delete any resource
Subject: { role: "admin" }
Action: delete
Resource: { owner: "someone-else" }
Expected: ALLOWEDTest: Owner can read their resource
Subject: { id: "user-123" }
Action: read
Resource: { owner: "user-123" }
Expected: ALLOWEDNegative Tests
Test cases where access should be denied:
Test: Viewer cannot delete
Subject: { role: "viewer" }
Action: delete
Resource: { type: "document" }
Expected: DENIEDTest: Non-owner cannot modify
Subject: { id: "user-456" }
Action: update
Resource: { owner: "user-123" }
Expected: DENIEDEdge Cases
Test boundary conditions and unusual inputs:
Test: Missing role field
Subject: { id: "user-123" } // no role
Action: read
Resource: { visibility: "public" }
Expected: Check policy handles missing roleTest: Empty roles array
Subject: { id: "user-123", roles: [] }
Action: read
Resource: { type: "document" }
Expected: DENIED (no roles = no permissions)Test: Null values
Subject: { id: "user-123", role: null }
Action: read
Resource: { owner: null }
Expected: Policy should handle gracefullySecurity Tests
Attempt to bypass your policies:
Test: Cross-tenant access attempt
Subject: { id: "user-1", org_id: "org-A" }
Action: read
Resource: { id: "secret-doc", org_id: "org-B" }
Expected: DENIED (different organizations)Test: Privilege escalation
Subject: { id: "user-1", role: "viewer", self_reported_role: "admin" }
Action: delete
Resource: { type: "user" }
Expected: DENIED (self-reported role shouldn't grant access)Test Templates
Using Test Case Templates
If your policies have associated test templates:
- Click on the Test Templates section
- Select a template from your active policies
- The form auto-fills with the template values
- Click Evaluate
Templates help ensure consistent testing across your team.
Creating Test Templates
When creating policies, document expected test cases:
Policy: allow-team-resources
Test Cases:
1. Team member accessing team resource → Allow
Subject: { team_id: "team-1" }
Resource: { team_id: "team-1" }
2. Non-member accessing team resource → Deny
Subject: { team_id: "team-2" }
Resource: { team_id: "team-1" }
3. User with no team → Deny
Subject: { id: "user-1" } // no team_id
Resource: { team_id: "team-1" }Debugging Strategies
Policy Not Matching?
If your policy isn't allowing when expected:
-
Check field names — Typos are common
# Wrong input.subjct.properties.role # Right input.subject.properties.role -
Verify data types — String vs number matters
# If input.subject.properties.age is "25" (string), this won't match input.subject.properties.age >= 18 # Need to ensure it's a number to_number(input.subject.properties.age) >= 18 -
Check for missing fields — Add logging
print("subject:", input.subject) print("has role:", input.subject.properties.role) input.subject.properties.role == "admin" -
Simplify and isolate — Start with a condition that should always match
# Temporary: Always match to test connectivity true
Unexpected Allows?
If access is granted when it shouldn't be:
-
Check for overly broad conditions
# Too broad - matches if subject has any user_id! input.subject.properties.user_id -
Review all allow policies — Any one can grant access
-
Look for OR logic bugs — Multiple policies create OR logic
# Policy 1: Allows ALL reads input.action.name == "read" # Policy 2: Admin access input.subject.properties.role == "admin" # Problem: Policy 1 allows ALL reads, regardless of role # Solution: Add more conditions to Policy 1 -
Check deny policies — Make sure deny policies are enabled and have correct conditions
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
= vs == | Assignment vs comparison | Use == for comparisons |
| Wrong property path | Field not found | Use input.subject.properties.role not input.subject.role |
| AND vs OR confusion | Logic error | Conditions in same policy = AND, multiple policies = OR |
| Type mismatch | Silent failure | Ensure consistent types |
Missing .properties | Field not found | Always access properties via .properties |
Systematic Testing Approach
1. Identify Policy Rules
List all the rules in your policy:
- Rule 1: Admins can do anything
- Rule 2: Owners can access their resources
- Rule 3: Public resources are readable
2. Create Test Matrix
For each rule, test:
| Rule | Should Allow | Should Deny |
|---|---|---|
| Admin | admin + any action | non-admin + admin-only action |
| Owner | owner + their resource | non-owner + private resource |
| Public | anyone + read + public | anyone + write + public |
3. Test Interactions
When rules combine:
- Admin who is also owner
- Owner of public resource
- Non-admin, non-owner, public resource
4. Test Failure Modes
What happens when:
- Decision node is unreachable
- Policy has syntax errors
- Input is malformed
Monitoring Production
After deploying policies, monitor real decisions:
Logs
Go to Monitoring → Logs to:
- View individual decisions
- Filter by effect (allow/deny)
- Search for specific subjects or resources
- Investigate denied requests
If you see many denials for a legitimate use case, you may need to add a new allow rule. If you see unexpected allows, investigate for overly permissive policies.
Best Practices
Test Early and Often
- Test while writing policies, not after
- Run tests after every change
- Keep a suite of regression tests
Test the Default
Always verify that your default deny works:
Test: Completely unknown request
Subject: { random: "data" }
Action: unknown
Resource: { something: "else" }
Expected: DENIED (default deny)Related
- Rego Basics — Language fundamentals
- Common Patterns — Authorization patterns
- Policies Guide — Manage policies in Denied
- Decision Nodes — Configure your PDP