Policy Evaluation
Policies are the core abstraction for defining authorization rules in CoSec. A policy contains a collection of statements, each with an action matcher and optional condition. The evaluation follows a strict order: condition check first, then deny-first across all matching statements.
Policy Structure
PolicyData
PolicyData is the concrete implementation of the Policy interface:
class PolicyData(
override val id: String,
override val category: String,
override val name: String,
override val description: String,
override val type: PolicyType, // GLOBAL, SYSTEM, or CUSTOM
override val tenantId: String,
override val condition: ConditionMatcher = AllConditionMatcher.INSTANCE,
override val statements: List<Statement> = listOf()
) : PolicyKey properties:
id: Unique policy identifiertype:GLOBAL,SYSTEM, orCUSTOM-- global policies, platform-managed system policies, or user-defined policiestenantId: Tenant scope -- policies are tenant-scopedcondition: A top-level condition that must match before any statement is evaluatedstatements: Ordered list of permission rules
StatementData
StatementData represents a single rule:
data class StatementData(
override val name: String = "",
override val effect: Effect = Effect.ALLOW,
override val action: ActionMatcher,
override val condition: ConditionMatcher = AllConditionMatcher.INSTANCE
) : Statementeffect:ALLOWorDENY-- determines the outcome when this statement matchesaction: Matches the request (e.g., path pattern). See Action Matcherscondition: Additional condition that must be met. Defaults toAllConditionMatcher(always matches)
Policy Verification Algorithm
Policy-Level Verification
When a policy is evaluated against a request, the process follows this sequence:
- Policy condition check:
policy.condition.match(request, securityContext)-- if false, the entire policy is skipped - Deny-first statement scan: All
DENYstatements are checked first; if any matches, the result isEXPLICIT_DENY - Allow statement scan:
ALLOWstatements are checked next; if any matches, the result isALLOW - No match: Returns
null(falls through to the next authorization source)
Statement-Level Verification
Each statement is verified in two steps:
- Action match:
statement.action.match(request, securityContext)-- does the request match the action pattern? - Condition match:
statement.condition.match(request, securityContext)-- do additional conditions hold?
Both must return true for the statement to match.
Load-Time Validation
DefaultPolicyEvaluator
DefaultPolicyEvaluator validates policies at load time using a mock request and context:
object DefaultPolicyEvaluator : PolicyEvaluator {
override fun evaluate(policy: Policy) {
val evaluateRequest = EvaluateRequest()
val mockContext = SimpleSecurityContext(SimpleTenantPrincipal.ANONYMOUS)
// Verify policy condition
safeEvaluate { policy.condition.match(evaluateRequest, mockContext) }
// Verify each statement
policy.statements.forEach { statement ->
safeEvaluate { statement.condition.match(evaluateRequest, mockContext) }
statement.action.match(evaluateRequest, mockContext)
safeEvaluate { statement.verify(evaluateRequest, mockContext) }
}
// Verify full policy
safeEvaluate { policy.verify(evaluateRequest, mockContext) }
}
}The safeEvaluate wrapper catches TooManyRequestsException (from rate limiter conditions) and RegexTimeoutException (from regex conditions exceeding their time budget) and continues, since neither can be meaningfully evaluated with mock data.
EvaluateRequest
EvaluateRequest provides sensible defaults for validation:
data class EvaluateRequest(
override val path: String = "/policies/test",
override val method: String = "POST",
override val remoteIp: String = "127.0.0.1",
override val origin: URI = URI.create("http://mockOrigin"),
override val referer: URI = URI.create("http://mockReferer"),
override val attributes: Map<String, String> = mapOf(),
private val headers: Map<String, String> = mapOf(),
private val queries: Map<String, String> = mapOf(),
private val cookies: Map<String, String> = mapOf(),
) : RequestArchitecture Diagrams
Policy Verification Flow
flowchart TD
A["Policy.verify(request, context)"] --> B{"policy.condition.match?"}
B -->|"false"| C["Skip policy"]
B -->|"true"| D["Phase 1: Scan DENY statements"]
D --> E{"DENY statement.action.match?"}
E -->|"false"| F["Next DENY statement"]
E -->|"true"| G{"statement.condition.match?"}
G -->|"false"| F
G -->|"true"| H["return EXPLICIT_DENY"]
F --> I{"More DENY statements?"}
I -->|"yes"| E
I -->|"no"| J["Phase 2: Scan ALLOW statements"]
J --> K{"ALLOW statement.action.match?"}
K -->|"false"| L["Next ALLOW statement"]
K -->|"true"| M{"statement.condition.match?"}
M -->|"false"| L
M -->|"true"| N["return ALLOW"]
L --> O{"More ALLOW statements?"}
O -->|"yes"| K
O -->|"no"| P["return null (no match)"]
style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style E fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style F fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style G fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style H fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style I fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style J fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style K fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style L fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style M fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style N fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style O fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style P fill:#2d333b,stroke:#6d5dfc,color:#e6edf3Statement Verification Sequence
sequenceDiagram
autonumber
participant Policy as Policy
participant Stmt as Statement
participant Action as ActionMatcher
participant Cond as ConditionMatcher
Policy->>Stmt: verify(request, context)
Stmt->>Action: match(request, context)
alt Action does not match
Action-->>Stmt: false
Stmt-->>Policy: IMPLICIT_DENY
else Action matches
Action-->>Stmt: true
Stmt->>Cond: match(request, context)
alt Condition does not match
Cond-->>Stmt: false
Stmt-->>Policy: IMPLICIT_DENY
else Condition matches
Cond-->>Stmt: true
Stmt-->>Policy: effect (ALLOW or DENY)
end
endLoad-Time Validation Sequence
sequenceDiagram
autonumber
participant Loader as PolicyLoader
participant Evaluator as DefaultPolicyEvaluator
participant Policy as Policy
participant MockCtx as MockContext
Loader->>Evaluator: evaluate(policy)
Evaluator->>MockCtx: create EvaluateRequest + Anonymous context
Evaluator->>Policy: condition.match(mockRequest, mockCtx)
loop each statement
Evaluator->>Policy: statement.condition.match(mockRequest, mockCtx)
Evaluator->>Policy: statement.action.match(mockRequest, mockCtx)
Evaluator->>Policy: statement.verify(mockRequest, mockCtx)
end
Evaluator->>Policy: policy.verify(mockRequest, mockCtx)
Evaluator-->>Loader: validation passed / exception on errorPolicyVerifyContext
When a policy or statement matches during authorization, a PolicyVerifyContext is created and attached to the SecurityContext:
data class PolicyVerifyContext(
val source: PolicyVerifySource,
val policy: Policy,
val statementIndex: Int,
val statement: Statement,
override val result: VerifyResult
) : VerifyContextThis enables downstream code (audit logging, debugging, OpenTelemetry tracing) to know exactly which policy and statement produced the authorization decision. The source property records which policy group produced the match — PolicyVerifySource.GLOBAL for global policies or PolicyVerifySource.PRINCIPAL for principal-assigned policies.
Performance: Sequence-Based Evaluation
The evaluateDenyFirst function takes a Kotlin Sequence<T> and materializes it exactly once via items.partition { ... }, which builds two intermediate lists (deny items and allow items). This means:
- Every upstream condition (e.g. a policy-level rate limiter) is evaluated exactly once per authorization, including conditions positioned after a matching early DENY
- Within each pass, verification short-circuits on the first decisive result: the DENY pass stops at the first
EXPLICIT_DENY, the ALLOW pass at the firstALLOW
References
- DefaultPolicyEvaluator.kt:26 - Load-time policy validation
- PolicyVerifyContext.kt:60 - Verification context data classes
- PolicyData.kt:35 - Policy data implementation
- StatementData.kt:31 - Statement data implementation
- SimpleAuthorization.kt:90 - Policy verification in authorization
Related Pages
- Authorization Flow - Full authorization pipeline using policies
- Action Matchers - How action patterns are matched
- Condition Matchers - How conditions are evaluated
- Permissions and Roles - Role-based permission structure