Immutable. This exact content is served forever at /api/v1/blob/f2e04f142b4c1109.
---
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`.
```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:
```json
{
"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
1. Policy-level condition checked first — if it doesn't match, the entire policy returns IMPLICIT_DENY
2. **DENY statements evaluated first** — any match returns EXPLICIT_DENY immediately
3. **ALLOW statements evaluated next** — any match returns ALLOW
4. 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
```json
"action": "/api/users"
```
Matches the exact path. Supports Spring path patterns with wildcards and variables.
### String with SpEL template
```json
"action": "/user/#{principal.id}/*"
```
`#{principal.id}` is evaluated at match time against the current security context principal.
### String with path variables
```json
"action": "/user/{id}"
```
Matches path segments. Access the variable in conditions via `request.path.var.id`.
### Array — multiple paths (OR logic)
```json
"action": ["/auth/register", "/auth/login", "/auth/logout"]
```
Matches if ANY path in the array matches.
### Wildcard
```json
"action": "*"
```
Matches all requests. Use with conditions to restrict scope.
### Object — path matcher with options
```json
"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
```json
"action": {
"all": {
"method": "GET"
}
}
```
Matches all GET requests regardless of path.
### Object — composite matcher (OR logic across different matcher types)
```json
"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
```json
"condition": { "authenticated": {} }
```
### inRole — user must have the specified role
```json
"condition": { "inRole": { "value": "admin" } }
```
### inTenant — request must be from the specified tenant
```json
"condition": { "inTenant": { "value": "tenant-abc" } }
```
### eq — exact value match
```json
"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
```json
"condition": {
"contains": {
"part": "request.attributes.ipRegion",
"value": "上海"
}
}
```
### startsWith / endsWith — prefix/suffix match
```json
"condition": {
"startsWith": {
"part": "request.attributes.ipRegion",
"value": "中国"
}
}
```
### in — value must be in a list
```json
"condition": {
"in": {
"part": "context.principal.id",
"value": ["adminId", "developerId"]
}
}
```
### regular — regex match
```json
"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
```json
"condition": {
"path": {
"part": "request.remoteIp",
"pattern": "192.168.0.*",
"options": {
"caseSensitive": false,
"separator": ".",
"decodeAndParseSegments": false
}
}
}
```
### bool — boolean logic (AND/OR)
```json
"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
```json
"condition": {
"spel": {
"expression": "#principal.attributes['vip'] == 'true'"
}
}
```
### rateLimiter — rate limiting
```json
"condition": {
"rateLimiter": {
"permitsPerSecond": 10
}
}
```
### groupedRateLimiter — grouped rate limiting
```json
"condition": {
"groupedRateLimiter": {
"permitsPerSecond": 100,
"groupKey": "context.principal.id"
}
}
```
## Common Patterns
### Public endpoints (no auth required)
```json
{
"id": "public-api",
"name": "Public API",
"type": "global",
"tenantId": "(platform)",
"statements": [
{
"name": "PublicEndpoints",
"action": ["/auth/login", "/auth/register", "/health"]
}
]
}
```
### Admin-only endpoints
```json
{
"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
```json
{
"name": "OwnResourcesOnly",
"action": "/api/users/{id}/**",
"condition": {
"eq": {
"part": "request.path.var.id",
"value": "#{principal.id}"
}
}
}
```
### IP whitelist with deny
```json
{
"name": "BlockExternalIp",
"effect": "deny",
"action": "*",
"condition": {
"regular": {
"negate": true,
"part": "request.remoteIp",
"pattern": "^(10\\.0\\.0\\.|192\\.168\\.)"
}
}
}
```
### CORS origin restriction
```json
{
"name": "RestrictOrigin",
"effect": "deny",
"action": "*",
"condition": {
"regular": {
"negate": true,
"part": "request.origin",
"pattern": "^https://(app\\.example\\.com|admin\\.example\\.com)"
}
}
}
```
### Rate-limited authenticated API
```json
{
"name": "RateLimitedApi",
"action": "/api/**",
"condition": {
"bool": {
"and": [
{ "authenticated": {} },
{ "rateLimiter": { "permitsPerSecond": 10 } }
]
}
}
}
```
### Health probe (for Kubernetes)
```json
{
"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:
1. **DENY before ALLOW** — while the framework handles ordering, writing DENY statements first improves readability
2. **SpEL templates** — `#{principal.id}` is valid; `{principal.id}` is NOT (conflicts with path variables)
3. **Path variables** — use `{varName}` syntax, access via `request.path.var.varName` in conditions
4. **Wildcard with conditions** — `"action": "*"` alone allows everything; always pair with a condition
5. **Negate logic** — `regular` matcher has `negate` field; other matchers don't
6. **Bool structure** — `and` and `or` are sibling fields, not nested
7. **Part paths** — must be valid: `request.*`, `context.principal.*`, `request.path.var.*`, `request.attributes.*`
8. **Policy type** — `global` policies apply to all requests; `custom` policies are attached to specific users/roles