| name | cosec-policy-author |
| description | Use when writing, validating, explaining, or debugging CoSec policy JSON, policy statements, ALLOW/DENY rules, action matchers, condition matchers, role-based authorization, tenant scoping, or rate limiting policies. |
CoSec Policy Authoring
This skill helps you write, validate, and explain CoSec security policy JSON files. CoSec uses an AWS IAM-like policy model where policies contain statements that define ALLOW or DENY rules matched against requests.
Policy JSON Structure
A policy file is a single JSON object placed in src/main/resources/cosec-policy/. The file naming convention is *-policy.json.
{
"id": "unique-policy-id",
"name": "Human-readable name",
"category": "optional-category",
"description": "What this policy does",
"type": "global",
"tenantId": "(platform)",
"condition": { ... },
"statements": [ ... ]
}
Fields
| Field | Required | Description |
|---|
id | Yes | Unique identifier for the policy |
name | Yes | Human-readable display name |
category | No | Logical grouping label |
description | No | Detailed description |
type | Yes | global (applies to all requests), system (system-level), or custom (user/role-specific) |
tenantId | Yes | Tenant scope. Use (platform) for global/system policies |
condition | No | Policy-level ConditionMatcher — if present and doesn't match, entire policy is skipped |
statements | Yes | Array of Statement objects |
Statement Structure
Each statement defines a single permission rule:
{
"name": "StatementName",
"effect": "allow",
"action": "...",
"condition": { ... }
}
| Field | Required | Default | Description |
|---|
name | No | — | Descriptive name for the statement |
effect | No | "allow" | "allow" or "deny". DENY takes precedence over ALLOW |
action | Yes | — | ActionMatcher definition (see below) |
condition | No | — | ConditionMatcher definition (see below) |
Evaluation Order
- Policy-level condition checked first — if it doesn't match, the entire policy returns IMPLICIT_DENY
- DENY statements evaluated first — any match returns EXPLICIT_DENY immediately
- ALLOW statements evaluated next — any match returns ALLOW
- Default: IMPLICIT_DENY
This means you should write DENY rules before ALLOW rules in the statements array for clarity, though the framework handles ordering internally.
Action Matchers
The action field defines which requests a statement applies to. There are several formats:
Simple string — path pattern
"action": "/api/users"
Matches the exact path. Supports Spring path patterns with wildcards and variables.
String with SpEL template
"action": "/user/#{principal.id}/*"
#{principal.id} is evaluated at match time against the current security context principal.
String with path variables
"action": "/user/{id}"
Matches path segments. Access the variable in conditions via request.path.var.id.
Array — multiple paths (OR logic)
"action": ["/auth/register", "/auth/login", "/auth/logout"]
Matches if ANY path in the array matches.
Wildcard
"action": "*"
Matches all requests. Use with conditions to restrict scope.
Object — path matcher with options
"action": {
"path": {
"method": "GET",
"pattern": "/api/users/*",
"options": {
"caseSensitive": false,
"separator": "/",
"decodeAndParseSegments": false
}
}
}
The method field can be a single string or array: "method": ["GET", "POST"].
The pattern field can be a single string or array of patterns.
Object — all matcher with method filter
"action": {
"all": {
"method": "GET"
}
}
Matches all GET requests regardless of path.
Object — composite matcher (OR logic across different matcher types)
"action": {
"composite": [
"/api/public/*",
{
"path": {
"method": "POST",
"pattern": "/api/webhook/*"
}
}
]
}
Condition Matchers
The condition field adds additional constraints beyond path matching. All condition types:
authenticated — user must be logged in
"condition": { "authenticated": {} }
inRole — user must have the specified role
"condition": { "inRole": { "value": "admin" } }
inTenant — request must be from the specified tenant
"condition": { "inTenant": { "value": "tenant-abc" } }
eq — exact value match
"condition": {
"eq": {
"part": "request.path.var.id",
"value": "#{principal.id}"
}
}
The part field is a path expression that extracts a value from the request or security context:
request.path.var.{name} — path variable
request.remoteIp — client IP
request.origin — request origin
request.method — HTTP method
request.attributes.{key} — request attributes (e.g., request.attributes.ipRegion)
context.principal.id — current user ID
context.principal.attributes.{key} — principal attributes
The value field supports SpEL templates like #{principal.id}.
contains — substring match
"condition": {
"contains": {
"part": "request.attributes.ipRegion",
"value": "上海"
}
}
startsWith / endsWith — prefix/suffix match
"condition": {
"startsWith": {
"part": "request.attributes.ipRegion",
"value": "中国"
}
}
in — value must be in a list
"condition": {
"in": {
"part": "context.principal.id",
"value": ["adminId", "developerId"]
}
}
regular — regex match
"condition": {
"regular": {
"negate": true,
"part": "request.origin",
"pattern": "^(http|https)://github.com"
}
}
Set negate: true to invert the match (matches when regex does NOT match).
path — path pattern match on arbitrary values
"condition": {
"path": {
"part": "request.remoteIp",
"pattern": "192.168.0.*",
"options": {
"caseSensitive": false,
"separator": ".",
"decodeAndParseSegments": false
}
}
}
bool — boolean logic (AND/OR)
"condition": {
"bool": {
"and": [
{ "authenticated": {} }
],
"or": [
{ "in": { "part": "context.principal.id", "value": ["dev1"] } },
{ "path": { "part": "request.remoteIp", "pattern": "10.0.0.*" } }
]
}
}
All items in and must match. At least one item in or must match. Both are optional — you can use just and, just or, or both.
spel / ognl — expression-based
"condition": {
"spel": {
"expression": "#principal.attributes['vip'] == 'true'"
}
}
rateLimiter — rate limiting
"condition": {
"rateLimiter": {
"permitsPerSecond": 10
}
}
groupedRateLimiter — grouped rate limiting
"condition": {
"groupedRateLimiter": {
"permitsPerSecond": 100,
"groupKey": "context.principal.id"
}
}
Common Patterns
Public endpoints (no auth required)
{
"id": "public-api",
"name": "Public API",
"type": "global",
"tenantId": "(platform)",
"statements": [
{
"name": "PublicEndpoints",
"action": ["/auth/login", "/auth/register", "/health"]
}
]
}
Admin-only endpoints
{
"id": "admin-api",
"name": "Admin API",
"type": "global",
"tenantId": "(platform)",
"statements": [
{
"name": "AdminOnly",
"action": "/admin/**",
"condition": { "inRole": { "value": "admin" } }
}
]
}
User can only access their own resources
{
"name": "OwnResourcesOnly",
"action": "/api/users/{id}/**",
"condition": {
"eq": {
"part": "request.path.var.id",
"value": "#{principal.id}"
}
}
}
IP whitelist with deny
{
"name": "BlockExternalIp",
"effect": "deny",
"action": "*",
"condition": {
"regular": {
"negate": true,
"part": "request.remoteIp",
"pattern": "^(10\\.0\\.0\\.|192\\.168\\.)"
}
}
}
CORS origin restriction
{
"name": "RestrictOrigin",
"effect": "deny",
"action": "*",
"condition": {
"regular": {
"negate": true,
"part": "request.origin",
"pattern": "^https://(app\\.example\\.com|admin\\.example\\.com)"
}
}
}
Rate-limited authenticated API
{
"name": "RateLimitedApi",
"action": "/api/**",
"condition": {
"bool": {
"and": [
{ "authenticated": {} },
{ "rateLimiter": { "permitsPerSecond": 10 } }
]
}
}
}
Health probe (for Kubernetes)
{
"id": "(health-probe)",
"name": "Health Probe",
"type": "global",
"tenantId": "(platform)",
"statements": [
{
"name": "actuator",
"action": [
"/actuator/health",
"/actuator/health/readiness",
"/actuator/health/liveness"
]
}
]
}
Validation Checklist
When reviewing a policy, check:
- DENY before ALLOW — while the framework handles ordering, writing DENY statements first improves readability
- SpEL templates —
#{principal.id} is valid; {principal.id} is NOT (conflicts with path variables)
- Path variables — use
{varName} syntax, access via request.path.var.varName in conditions
- Wildcard with conditions —
"action": "*" alone allows everything; always pair with a condition
- Negate logic —
regular matcher has negate field; other matchers don't
- Bool structure —
and and or are sibling fields, not nested
- Part paths — must be valid:
request.*, context.principal.*, request.path.var.*, request.attributes.*
- Policy type —
global policies apply to all requests; custom policies are attached to specific users/roles