{
"$schema": "https://json-schema.org/draft-07/schema#",
"$id": "https://captchaforge.santh.dev/schemas/community-rules.schema.json",
"title": "captchaforge community rule pack",
"description": "Schema for captchaforge community.toml rule packs. Each pack contains zero or more `[[provider]]` entries describing a captcha vendor + the DOM/JS/network triggers that identify it.",
"type": "object",
"additionalProperties": false,
"properties": {
"provider": {
"type": "array",
"items": { "$ref": "#/definitions/provider" }
}
},
"definitions": {
"provider": {
"type": "object",
"additionalProperties": false,
"required": ["name", "priority"],
"properties": {
"name": {
"type": "string",
"description": "Stable rule identifier. Lowercase ASCII + underscores. MUST be unique across the pack.",
"pattern": "^[a-z][a-z0-9_]{1,63}$"
},
"priority": {
"type": "integer",
"description": "Detection priority. Higher = earlier evaluation. Reserved ranges: 1-50 first-party (Cloudflare/Google/hCaptcha), 51-99 major WAFs, 100-199 niche vendors, 200+ generic fallback.",
"minimum": 1,
"maximum": 999
},
"solver_names": {
"type": "array",
"description": "Concrete CaptchaSolver impl names the chain should attempt for this vendor, in order. If omitted, the chain's `supports()` filter picks. See src/solver/ for the canonical list.",
"items": {
"type": "string",
"enum": [
"WaitForTokenSolver",
"CloudflareInterstitialSolver",
"AkamaiInterstitialSolver",
"DataDomeSolver",
"PerimeterXSolver",
"GeeTestSolver",
"AwsWafSolver",
"ArkoseSolver",
"ImpervaSolver",
"KasadaSolver",
"MathCaptchaSolver",
"PowCaptchaSolver",
"SliderCaptchaSolver",
"TurnstileInteractiveSolver",
"BehavioralCaptchaSolver",
"YoloGridSolver",
"CrnnTextSolver",
"VlmCaptchaSolver",
"RecaptchaAudioSolver",
"AudioCaptchaSolver",
"ThirdPartyCaptchaSolver",
"MultiStepCaptchaSolver",
"OcrCaptchaSolver"
]
},
"uniqueItems": true
},
"recommended_solver_methods": {
"type": "array",
"description": "Method-keyed solver hint (legacy / fragile when multiple solvers share a method). Prefer `solver_names`.",
"items": {
"type": "string"
}
},
"triggers": {
"$ref": "#/definitions/triggers"
}
}
},
"triggers": {
"type": "object",
"additionalProperties": false,
"description": "At least one of selectors / script_src_contains / window_globals / title_contains / cookie_names MUST be non-empty; otherwise the rule never matches.",
"properties": {
"selectors": {
"type": "array",
"description": "CSS selectors. ANY match fires the rule.",
"items": { "type": "string", "minLength": 1 }
},
"window_globals": {
"type": "array",
"description": "JS global names. The detector probes `typeof window.<name>` and fires on any non-undefined.",
"items": { "type": "string", "pattern": "^[A-Za-z_$][A-Za-z0-9_$]*$" }
},
"script_src_contains": {
"type": "array",
"description": "Substring match against <script src=...>. Use the vendor's stable CDN host.",
"items": { "type": "string", "minLength": 1 }
},
"title_contains": {
"type": "array",
"description": "Case-insensitive substring match against <title> text. Use for vendors whose interstitial title is stable (Akamai 'Access Denied', shopify 'checkpoint', etc.).",
"items": { "type": "string", "minLength": 1 }
},
"cookie_names": {
"type": "array",
"description": "Reserved for the network-layer detector to match Set-Cookie names on the response. Not consulted by the DOM detector.",
"items": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" }
}
}
}
}
}