Extend CoSec with custom ActionMatcher or ConditionMatcher implementations and register their factories through ServiceLoader or Spring. Do not use for policies that built-in matchers already express.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add Ahoo-Wang/skills --skill cosec-custom-matcher --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Cosec Custom Matcher?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ahoo-wang-cosec-custom-matcher)More formats (shields.io, HTML) on the badges page.
---
name: cosec-custom-matcher
description: Extend CoSec with custom ActionMatcher or ConditionMatcher implementations and register their factories through ServiceLoader or Spring. Do not use for policies that built-in matchers already express.
---
# CoSec Custom Matchers
Add a matcher only after confirming that the built-ins cannot express the rule. Prefer composing `path`, `bool`, part matchers, or the local/Redis rate limiters over new code.
## Extension contract
- `ActionMatcher` and `ConditionMatcher` live in `cosec-api` and extend `RequestMatcher`.
- Their factory interfaces and providers live in `cosec-core` under `me.ahoo.cosec.policy.action` and `me.ahoo.cosec.policy.condition`.
- A factory's `type` is the object key used in policy JSON. It must not collide with another factory; later registration replaces the existing entry.
- Do not change the SPI interfaces to add a matcher. Implement them and register the factory.
- Keep matching synchronous and side-effect free unless the matcher intentionally enforces a stateful control such as rate limiting.
Before editing, inspect the target CoSec version's interfaces and the nearest built-in matcher. In a CoSec checkout, the authoritative files are:
- `cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/RequestMatcher.kt`
- `cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/`
- `cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/`
## Minimal condition matcher
Use `AbstractConditionMatcher` when `negate` support is desirable. Parse and validate configuration once during construction, not on every request.
```kotlin
class PremiumUserConditionMatcher(configuration: Configuration) :
AbstractConditionMatcher(PremiumUserConditionMatcherFactory.TYPE, configuration) {
private val requiredTier = configuration.getRequired("tier").asString()
override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean {
val tier = securityContext.principal.attributes["tier"]?.toString()
return tier == requiredTier
}
}
class PremiumUserConditionMatcherFactory : ConditionMatcherFactory {
companion object {
const val TYPE = "premiumUser"
}
override val type: String = TYPE
override fun create(configuration: Configuration): ConditionMatcher =
PremiumUserConditionMatcher(configuration)
}
```
Register it for non-Spring and Spring environments with:
```text
# src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory
com.example.cosec.PremiumUserConditionMatcherFactory
```
```json
{
"action": "/api/premium/**",
"condition": {
"premiumUser": { "tier": "gold" }
}
}
```
For an action matcher, use the same pattern with `AbstractActionMatcher`, `ActionMatcherFactory`, and:
```text
src/main/resources/META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory
```
## Spring registration
When a factory needs Spring-managed dependencies, expose it as a bean instead of adding a service file:
```kotlin
@Bean
fun premiumUserConditionMatcherFactory(): ConditionMatcherFactory =
PremiumUserConditionMatcherFactory()
```
`MatcherFactoryRegister` registers all action and condition factory beans at startup. Do not register the same type through both mechanisms unless replacement is intentional.
## Useful API surface
Read configuration through `get`/`getRequired` followed by `asString`, `asBoolean`, `asInt`, `asLong`, `asDouble`, `asList`, or `asMap`.
Request matching commonly uses `path`, `method`, `remoteIp`, `origin`, `referer`, `appId`, `spaceId`, `deviceId`, `requestId`, headers, queries, cookies, and request attributes. Context matching commonly uses principal ID/authentication/roles/policies/attributes, tenant ID, and context attributes. Inspect the target interfaces rather than assuming fields from another CoSec version.
## Existing type names
Do not replace these unless the user explicitly wants an override:
- Actions: `all`, `path`, `composite`
- Conditions: `all`, `authenticated`, `inRole`, `inTenant`, `eq`, `contains`, `startsWith`, `endsWith`, `in`, `regular`, `path`, `bool`, `spel`, `ognl`, `rateLimiter`, `groupedRateLimiter`
- With Redis cache support: `redisRateLimiter`, `redisGroupedRateLimiter`
## Verification
Add one focused test that creates the matcher through its factory and checks a match and non-match. If registration changed, also deserialize one policy using the custom type so a missing service entry or Spring bean fails the test. Run the narrow module test and the repository's static analysis command.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!