cosec-custom-matcher

star 43

Use when extending CoSec policy matching with custom ActionMatcher or ConditionMatcher implementations, condition types, matcher factories, ServiceLoader SPI registration, or Spring bean matcher registration.

Ahoo-Wang By Ahoo-Wang schedule Updated 5/27/2026

name: cosec-custom-matcher description: "Use when extending CoSec policy matching with custom ActionMatcher or ConditionMatcher implementations, condition types, matcher factories, ServiceLoader SPI registration, or Spring bean matcher registration."

CoSec Custom Matcher Development

This skill helps you create custom ActionMatcher and ConditionMatcher implementations to extend CoSec's policy evaluation logic. CoSec uses Java SPI (ServiceLoader) to discover matcher factories.

Architecture Overview

Policy matching has two sides:

  • ActionMatcher — determines if a request's action (path + method) matches a policy pattern
  • ConditionMatcher — determines if contextual conditions are met (user attributes, request properties, etc.)

Both extend RequestMatcher and are created by corresponding factory classes registered via SPI.

Policy
├── condition: ConditionMatcher (policy-level gate)
└── statements[]
    ├── Statement (effect: DENY)
    │   ├── action: ActionMatcher
    │   └── condition: ConditionMatcher
    └── Statement (effect: ALLOW)
        ├── action: ActionMatcher
        └── condition: ConditionMatcher

Creating a Custom ConditionMatcher

Step 1: Implement the ConditionMatcher

package com.example.cosec.condition

import me.ahoo.cosec.api.context.SecurityContext
import me.ahoo.cosec.api.context.request.Request
import me.ahoo.cosec.api.policy.ConditionMatcher
import me.ahoo.cosec.api.configuration.Configuration

class PremiumUserConditionMatcher(
    override val configuration: Configuration
) : ConditionMatcher {

    override val type: String = "premiumUser"

    override fun match(request: Request, securityContext: SecurityContext): Boolean {
        val isPremium = securityContext.principal.attributes["premium"]
        return isPremium == "true"
    }
}

Key points:

  • type — unique string identifier used in policy JSON
  • configuration — arbitrary key-value config passed from the policy JSON
  • match() — return true if the condition is satisfied

Step 2: Implement the Factory

package com.example.cosec.condition

import me.ahoo.cosec.api.configuration.Configuration
import me.ahoo.cosec.api.policy.ConditionMatcher
import me.ahoo.cosec.policy.condition.ConditionMatcherFactory

class PremiumUserConditionMatcherFactory : ConditionMatcherFactory {

    override val type: String = "premiumUser"

    override fun create(configuration: Configuration): ConditionMatcher {
        return PremiumUserConditionMatcher(configuration)
    }
}

Step 3: Register via SPI

Create file: src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory

com.example.cosec.condition.PremiumUserConditionMatcherFactory

Step 4: Use in Policy JSON

{
  "name": "PremiumEndpoints",
  "action": "/api/premium/**",
  "condition": {
    "premiumUser": {}
  }
}

With configuration:

{
  "name": "TieredAccess",
  "action": "/api/**",
  "condition": {
    "premiumUser": {
      "minTier": "gold"
    }
  }
}

Access configuration in the matcher:

val minTier = configuration.getRequiredString("minTier")

Creating a Custom ActionMatcher

Step 1: Implement the ActionMatcher

package com.example.cosec.action

import me.ahoo.cosec.api.context.SecurityContext
import me.ahoo.cosec.api.context.request.Request
import me.ahoo.cosec.api.policy.ActionMatcher
import me.ahoo.cosec.api.configuration.Configuration

class HttpMethodActionMatcher(
    override val configuration: Configuration
) : ActionMatcher {

    override val type: String = "httpMethod"

    private val allowedMethods: Set<String> = configuration.getRequiredString("methods")
        .split(",")
        .map { it.trim().uppercase() }
        .toSet()

    override fun match(request: Request, securityContext: SecurityContext): Boolean {
        return request.method.uppercase() in allowedMethods
    }
}

Step 2: Implement the Factory

package com.example.cosec.action

import me.ahoo.cosec.api.configuration.Configuration
import me.ahoo.cosec.api.policy.ActionMatcher
import me.ahoo.cosec.policy.action.ActionMatcherFactory

class HttpMethodActionMatcherFactory : ActionMatcherFactory {

    override val type: String = "httpMethod"

    override fun create(configuration: Configuration): ActionMatcher {
        return HttpMethodActionMatcher(configuration)
    }
}

Step 3: Register via SPI

Create file: src/main/resources/META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory

com.example.cosec.action.HttpMethodActionMatcherFactory

Step 4: Use in Policy JSON

{
  "name": "ReadOnlyAccess",
  "action": {
    "httpMethod": {
      "methods": "GET,HEAD,OPTIONS"
    }
  }
}

Accessing Configuration Values

The Configuration interface provides typed accessors:

// Required (throws if missing)
val value: String = configuration.getRequiredString("key")

// Optional with default
val value: String = configuration.get("key", "default")

// Nested configuration
val nested: Configuration = configuration.getRequiredConfiguration("nested")

Accessing Request and Context Data

Request properties

request.path           // URL path
request.method         // HTTP method
request.remoteIp       // client IP
request.origin         // Origin header
request.referer        // Referer header
request.appId          // application ID
request.spaceId        // space ID
request.deviceId       // device ID
request.requestId      // request ID
request.getHeader("X-Custom")    // any header
request.getQuery("param")        // query parameter
request.getCookieValue("name")   // cookie value

SecurityContext properties

securityContext.principal                    // CoSecPrincipal
securityContext.principal.id                 // user ID
securityContext.principal.authenticated      // boolean
securityContext.principal.anonymous          // boolean
securityContext.principal.roles              // Set<String>
securityContext.principal.policies           // Set<String>
securityContext.principal.attributes         // Map<String, String>
securityContext.tenant                       // Tenant info
securityContext.attributes                   // MutableMap<String, Any>

Built-in ConditionMatcher Types Reference

For reference, here are all built-in types:

Type Description Key Config
authenticated User must be logged in
inRole User must have role value: role name
inTenant Must be from tenant value: tenant ID
eq Exact match part, value
contains Substring match part, value
startsWith Prefix match part, value
endsWith Suffix match part, value
in Value in list part, value: array
regular Regex match part, pattern, negate
path Path pattern match part, pattern, options
bool Boolean logic and: array, or: array
spel Spring Expression expression
ognl OGNL expression expression
rateLimiter Rate limiting permitsPerSecond
groupedRateLimiter Grouped rate limit permitsPerSecond, groupKey

Built-in ActionMatcher Types Reference

Type Description Key Config
path URL path matching pattern, method, options
all Wildcard method (optional)
composite OR combination array of matchers

Spring Registration (Alternative to SPI)

You can also register matcher factories as Spring beans. The MatcherFactoryRegister auto-configuration picks them up from the ApplicationContext:

@Configuration
class CustomMatcherConfig {

    @Bean
    fun premiumUserConditionMatcherFactory(): ConditionMatcherFactory {
        return PremiumUserConditionMatcherFactory()
    }

    @Bean
    fun httpMethodActionMatcherFactory(): ActionMatcherFactory {
        return HttpMethodActionMatcherFactory()
    }
}

This approach is simpler when your matcher needs Spring dependencies (e.g., a database or external service).

Install via CLI
npx skills add https://github.com/Ahoo-Wang/CoSec --skill cosec-custom-matcher
Repository Details
star Stars 43
call_split Forks 4
navigation Branch main
article Path SKILL.md
More from Creator