원클릭으로
cosec-policy-author
// 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.
// 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.
Use when extending CoSec policy matching with custom ActionMatcher or ConditionMatcher implementations, condition types, matcher factories, ServiceLoader SPI registration, or Spring bean matcher registration.
Use when adding CoSec to a Spring Boot application, choosing WebFlux/WebMVC/Gateway modules, configuring JWT authentication, authorization filters, local policies, Redis policy caching, social login, IP enrichment, OpenAPI, or OpenTelemetry integration.
Use when diagnosing CoSec authentication or authorization failures such as unexpected 401/403 responses, denied requests that should be allowed, policies not loading, JWT token rejection, matcher mismatches, or unclear access decisions.
| 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. |
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.
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": [ ... ]
}
| 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 |
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) |
This means you should write DENY rules before ALLOW rules in the statements array for clarity, though the framework handles ordering internally.
The action field defines which requests a statement applies to. There are several formats:
"action": "/api/users"
Matches the exact path. Supports Spring path patterns with wildcards and variables.
"action": "/user/#{principal.id}/*"
#{principal.id} is evaluated at match time against the current security context principal.
"action": "/user/{id}"
Matches path segments. Access the variable in conditions via request.path.var.id.
"action": ["/auth/register", "/auth/login", "/auth/logout"]
Matches if ANY path in the array matches.
"action": "*"
Matches all requests. Use with conditions to restrict scope.
"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.
"action": {
"all": {
"method": "GET"
}
}
Matches all GET requests regardless of path.
"action": {
"composite": [
"/api/public/*",
{
"path": {
"method": "POST",
"pattern": "/api/webhook/*"
}
}
]
}
The condition field adds additional constraints beyond path matching. All condition types:
"condition": { "authenticated": {} }
"condition": { "inRole": { "value": "admin" } }
"condition": { "inTenant": { "value": "tenant-abc" } }
"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 variablerequest.remoteIp — client IPrequest.origin — request originrequest.method — HTTP methodrequest.attributes.{key} — request attributes (e.g., request.attributes.ipRegion)context.principal.id — current user IDcontext.principal.attributes.{key} — principal attributesThe value field supports SpEL templates like #{principal.id}.
"condition": {
"contains": {
"part": "request.attributes.ipRegion",
"value": "上海"
}
}
"condition": {
"startsWith": {
"part": "request.attributes.ipRegion",
"value": "中国"
}
}
"condition": {
"in": {
"part": "context.principal.id",
"value": ["adminId", "developerId"]
}
}
"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).
"condition": {
"path": {
"part": "request.remoteIp",
"pattern": "192.168.0.*",
"options": {
"caseSensitive": false,
"separator": ".",
"decodeAndParseSegments": false
}
}
}
"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.
"condition": {
"spel": {
"expression": "#principal.attributes['vip'] == 'true'"
}
}
"condition": {
"rateLimiter": {
"permitsPerSecond": 10
}
}
"condition": {
"groupedRateLimiter": {
"permitsPerSecond": 100,
"groupKey": "context.principal.id"
}
}
{
"id": "public-api",
"name": "Public API",
"type": "global",
"tenantId": "(platform)",
"statements": [
{
"name": "PublicEndpoints",
"action": ["/auth/login", "/auth/register", "/health"]
}
]
}
{
"id": "admin-api",
"name": "Admin API",
"type": "global",
"tenantId": "(platform)",
"statements": [
{
"name": "AdminOnly",
"action": "/admin/**",
"condition": { "inRole": { "value": "admin" } }
}
]
}
{
"name": "OwnResourcesOnly",
"action": "/api/users/{id}/**",
"condition": {
"eq": {
"part": "request.path.var.id",
"value": "#{principal.id}"
}
}
}
{
"name": "BlockExternalIp",
"effect": "deny",
"action": "*",
"condition": {
"regular": {
"negate": true,
"part": "request.remoteIp",
"pattern": "^(10\\.0\\.0\\.|192\\.168\\.)"
}
}
}
{
"name": "RestrictOrigin",
"effect": "deny",
"action": "*",
"condition": {
"regular": {
"negate": true,
"part": "request.origin",
"pattern": "^https://(app\\.example\\.com|admin\\.example\\.com)"
}
}
}
{
"name": "RateLimitedApi",
"action": "/api/**",
"condition": {
"bool": {
"and": [
{ "authenticated": {} },
{ "rateLimiter": { "permitsPerSecond": 10 } }
]
}
}
}
{
"id": "(health-probe)",
"name": "Health Probe",
"type": "global",
"tenantId": "(platform)",
"statements": [
{
"name": "actuator",
"action": [
"/actuator/health",
"/actuator/health/readiness",
"/actuator/health/liveness"
]
}
]
}
When reviewing a policy, check:
#{principal.id} is valid; {principal.id} is NOT (conflicts with path variables){varName} syntax, access via request.path.var.varName in conditions"action": "*" alone allows everything; always pair with a conditionregular matcher has negate field; other matchers don'tand and or are sibling fields, not nestedrequest.*, context.principal.*, request.path.var.*, request.attributes.*global policies apply to all requests; custom policies are attached to specific users/roles