Cosec troubleshoot
Skill Ahoo-Wang/skills/plugins/ahoo-cosec-skills/skills/cosec-troubleshoot
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.From its SKILL.md
npx -y skills add Ahoo-Wang/skills --skill cosec-troubleshootAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- reads credentialsReads from 1 credential source: `cosec.jwt.secret`.
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.9 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it
CoSec Troubleshooting Guide
This skill helps you debug authorization issues in CoSec. When a request gets an unexpected result (403, 401, or is allowed when it shouldn't be), follow this systematic approach.
Step 1: Enable Debug Logging
The fastest way to understand authorization decisions is debug logging on SimpleAuthorization:
logging:
level:
me.ahoo.cosec.authorization.SimpleAuthorization: debug
This logs the full evaluation chain: root check → blacklist → global policies → principal policies → role permissions → final result.
For more granular tracing:
logging:
level:
me.ahoo.cosec.policy: debug
me.ahoo.cosec.authentication: debug
me.ahoo.cosec.jwt: debug
Step 2: Understand the Evaluation Order
SimpleAuthorization evaluates in this order, stopping at the first definitive result:
1. Root user check
└─ If principal.id == "cosec" → ALLOW (bypass everything)
2. Blacklist check
└─ If principal is blacklisted → EXPLICIT_DENY
3. Global policies (type: "global")
└─ For each global policy:
a. Check policy-level condition → skip if no match
b. Check DENY statements → EXPLICIT_DENY if any matches
c. Check ALLOW statements → ALLOW if any matches
└─ First definitive result wins
4. Principal-specific policies
└─ Policies attached to the user (via policy IDs on the principal)
└─ Same evaluation as global policies
5. Role-based app permissions
└─ Evaluate role permissions for the request's appId/spaceId
└─ Only applies when request has an appId
6. Default → IMPLICIT_DENY
Each step uses switchIfEmpty to fall through to the next if no match is found.
Step 3: Common Issues and Fixes
All requests return 403
Symptoms: Every endpoint returns 403, even public ones.
Likely causes:
- No policy files loaded — check
cosec.authorization.local-policy.enabled=true - Policy files don't match the location pattern — default is
classpath:cosec-policy/*-policy.json - Policy JSON syntax error — check startup logs for deserialization errors
Fix:
cosec:
authorization:
local-policy:
enabled: true
locations: classpath:cosec-policy/*-policy.json
Specific endpoint returns 403 when it should be public
Symptoms: Most endpoints work, but a new public endpoint returns 403.
Cause: No ALLOW statement matches the endpoint. By default, CoSec uses implicit deny — anything not explicitly allowed is denied.
Fix: Add a statement for the endpoint:
{
"name": "NewPublicEndpoint",
"action": "/api/new-endpoint"
}
Request allowed when it should be denied
Symptoms: A request that should be blocked gets through.
Likely causes:
- DENY statement doesn't match — check action pattern and condition
- Another ALLOW statement matches first (but DENY should take precedence)
- Root user bypass — check if the user ID is "cosec"
Debug: Enable debug logging and check which statement matched.
JWT token rejected
Symptoms: Requests with valid JWT tokens return 401.
Likely causes:
cosec.jwt.secretdoesn't match the token issuer's secretcosec.jwt.algorithmdoesn't match the token's algorithm- Token is expired
- Token format is wrong (not a standard JWT)
Check:
cosec:
jwt:
algorithm: hmac256 # must match the signing algorithm
secret: exact-same-secret-used-by-issuer
Policies not loading from local files
Symptoms: Startup succeeds but policies don't take effect.
Checklist:
- File location:
src/main/resources/cosec-policy/(notresources/main/...) - File naming: must match
*-policy.jsonpattern - Property:
cosec.authorization.local-policy.enabled=true - JSON validity: parse errors are logged at startup
- Policy type: must be
"global"for the policy to apply to all requests
Rate limiter not working
Symptoms: Rate limiting conditions are ignored.
Cause: Rate limiters require a shared state. In a distributed setup, you need Redis-backed caching (cosec-cocache).
Fix: Add the cocache dependency and configure Redis.
Path variables not matching
Symptoms: /user/123 doesn't match /user/{id}.
Check:
- Use
{varName}not:varName(Spring WebFlux style) - Access the variable via
request.path.var.varNamein conditions - Ensure the path pattern is correct (no trailing slash mismatch)
SpEL template not evaluating
Symptoms: #{principal.id} is treated as a literal string.
Cause: SpEL templates use #{} syntax. {} alone is a path variable, not SpEL.
Fix: Use #{principal.id} not {principal.id}.
Condition part path is wrong
Symptoms: Condition always returns false.
Valid part paths:
request.path.var.{name}— path variablerequest.remoteIp— client IP addressrequest.origin— Origin headerrequest.method— HTTP methodrequest.attributes.{key}— request attributesrequest.headers.{name}— request headercontext.principal.id— user IDcontext.principal.attributes.{key}— principal attribute
Common mistakes:
request.ip(wrong) →request.remoteIp(correct)principal.id(wrong) →context.principal.id(correct)request.pathVariable.id(wrong) →request.path.var.id(correct)
Step 4: Testing Policies Locally
Unit test with SimpleAuthorization
@Test
fun `test policy evaluation`() {
val policyLoader = LocalPolicyLoader("classpath:cosec-policy/test-policy.json")
val policies = policyLoader.load()
val evaluator = DefaultPolicyEvaluator(policies)
val request = mockk<Request> {
every { path } returns "/api/users/123"
every { method } returns "GET"
every { remoteIp } returns "192.168.1.1"
}
val principal = mockk<CoSecPrincipal> {
every { id } returns "user-123"
every { authenticated } returns true
every { roles } returns setOf("user")
}
val context = mockk<SecurityContext> {
every { [email protected] } returns principal
}
val result = evaluator.evaluate(request, context)
assertThat(result.authorized).isTrue()
}
Test specific matcher
@Test
fun `test path action matcher`() {
val factory = PathActionMatcherFactory()
val matcher = factory.create(Configuration.of("pattern" to "/api/users/*"))
val request = mockk<Request> {
every { path } returns "/api/users/123"
every { method } returns "GET"
}
assertThat(matcher.match(request, mockk())).isTrue()
}
Step 5: Request Attributes for Debugging
When debugging, inspect the request attributes that CoSec sets:
COSEC_SECURITY_CONTEXT— the parsed security contextrequest.attributes.ipRegion— IP geolocation (ifcosec-ip2regionis enabled)
In a WebFlux handler:
@GetMapping("/debug/whoami")
fun whoami(exchange: ServerWebExchange): Mono<Map<String, Any?>> {
val context = exchange.getAttribute<SecurityContext>(COSEC_SECURITY_CONTEXT)
return Mono.just(mapOf(
"principal" to context?.principal?.id,
"authenticated" to context?.principal?.authenticated,
"roles" to context?.principal?.roles,
"tenant" to context?.tenant?.tenantId
))
}
Quick Reference: Authorization Results
| Result | authorized | Meaning |
|---|---|---|
ALLOW | true | Explicitly allowed by a policy statement |
EXPLICIT_DENY | false | Explicitly denied by a DENY statement |
IMPLICIT_DENY | false | No statement matched (default deny) |
TOKEN_EXPIRED | false | JWT token has expired |
TOO_MANY_REQUESTS | false | Rate limiter exceeded |
What ships with it: 1 file
211 B alongside SKILL.md
agents/
- openai.yaml211 B