CAPTCHA

Overview

The CAPTCHA system supports three providers, selectable globally. Challenge actions are available for WAF rules, Security Rules, and rate limits. When a challenge is triggered, the client is redirected to a challenge page, and upon successful completion, a cookie is issued that bypasses future challenges for a configurable TTL.

Providers

Native (cap)

The built-in proof-of-work provider. coreX Platform integrates cap — an open-source, self-hosted proof-of-work CAPTCHA service that runs as a separate container (tiago2/cap:latest) alongside the coreX stack. The challenge is solved entirely in the client’s browser via a lightweight widget (loaded from a CDN), with the server verifying the proof-of-work solution. No third-party service or external API keys are required — the only key needed is the CAP_ADMIN_KEY, which authenticates coreX Platform to the Cap service’s admin API for key management.

The Cap service uses Valkey (shared with coreX Platform) for proof-of-work state storage. It is reachable internally at http://cap:3000 and is published on port 3001 by default.

reCAPTCHA

Google reCAPTCHA v2 or v3 (with configurable score threshold). Requires a site key and secret from Google.

Turnstile

A CAPTCHA widget from an external provider. Requires a site key and secret.

Third-party providers

reCAPTCHA is a Google service and Turnstile is a service from another company. Both require their own site keys and secrets. The Native provider is self-hosted by coreX Platform and requires no external service.

Challenge Action

The challenge action is available for:

  • WAF rules — challenge requests that exceed the anomaly threshold
  • Security Rules — challenge requests matching a rule expression
  • Rate limits — challenge clients that exceed the rate limit

When triggered, the challenge action:

  1. Checks for a valid _cv cookie
  2. If valid, allows the request through
  3. If invalid, emits a redirect to the challenge page with rule context
  4. The challenge page presents the CAPTCHA
  5. On success, sets the _cv cookie and redirects back to the original URL

Client Binding

The _cv token is bound to the client via a hash of:

  • Client IP
  • User-Agent
  • JA4 TLS fingerprint

This means a leaked cookie cannot be replayed from a different client.

Why JA4 binding?

Binding to the JA4 fingerprint adds another layer of client identity beyond IP and User-Agent. Even if an attacker steals the cookie and spoofs the IP and User-Agent, a different TLS client will have a different JA4 fingerprint and fail the binding check.

Request ID

HAProxy’s unique-id is passed through the challenge pipeline for correlation with access logs and WAF metrics. This lets you trace a challenge event back to the original request.

Global TTL

captcha_valid_seconds (default 3600 = 1 hour) controls how long the _cv cookie is valid. Configurable from the CAPTCHA page UI.

Challenge Event Tracking

The system logs three event types per rule:

  • Issued — a challenge was presented
  • Solved — the challenge was completed successfully
  • Failed — the challenge was attempted but failed

Each event includes the request_id for correlation.

Settings Page

The CAPTCHA settings page has three tabs:

Provider Tab

  • Provider selector (Native / reCAPTCHA / Turnstile)
  • Per-provider configuration fields:
    • Native: no configuration needed
    • reCAPTCHA: site key, secret, version (v2/v3), score threshold (v3)
    • Turnstile: site key, secret

Stats Tab

  • Per-rule solve rate
  • Recent challenge events (issued, solved, failed)

Keys Tab (Native provider only)

The Keys tab is a management interface for the Cap service’s site keys — the API key pairs that the Cap proof-of-work CAPTCHA uses to issue and verify challenges. It is only visible when the active provider is Native (cap). The tab does not appear for reCAPTCHA or Turnstile, since those providers manage keys externally (via Google or Cloudflare dashboards).

What is a Cap site key?

Each Cap site key is a pair:

ComponentVisibilityPurpose
Site keyPublic (sent to the browser)Identifies your Cap instance to the widget. Embedded in the challenge page HTML and the widget script. Safe to expose in client-side code.
Secret keyPrivate (server-side only)Used by the backend to verify proof-of-work solutions submitted by the browser. Never sent to the client.

The active site key (configured in the Settings tab) is the one HAProxy uses when rendering challenge pages and verifying solutions. You can create multiple site keys — for example, one per environment (staging, production) or per application — but only one site key + secret pair is active at a time.

How coreX Platform talks to Cap

The Keys tab is a proxy to the Cap service’s admin API. coreX Platform does not store the site keys or secrets itself — it forwards all key operations to the Cap container:

  Browser (Keys tab)
       │  GET/POST/PUT/DELETE /api/v1/captcha/keys

  coreX API backend
       │  authenticates to Cap with CAP_ADMIN_KEY
       │  (session cached for 29 days — Cap sessions last 30)

  Cap service (http://cap:3000)
       │  /server/keys, /server/keys/{site_key},
       │  /server/keys/{site_key}/config,
       │  /server/keys/{site_key}/rotate-secret

  Cap's key store (backed by Valkey)

The CAP_ADMIN_KEY environment variable (set in .env or docker-compose.yml) is the admin password for the Cap service. Without it, the Keys tab returns a 503 error. The backend logs into Cap’s /auth/login endpoint with the admin key, receives a session token, and caches it for 29 days (Cap sessions last 30). If the session expires, the backend re-authenticates transparently.

CAP_ADMIN_KEY is required

The Keys tab only works when CAP_ADMIN_KEY is set in the environment. If you see a 503 “CAP_ADMIN_KEY not configured” error, add it to your .env file:

CAP_ADMIN_KEY=your-strong-random-secret

Then restart the backend. The key is set when the Cap container is first deployed — if you didn’t set one, generate a random value and use the same value for both CAP_ADMIN_KEY (coreX) and ADMIN_KEY (Cap container).

Key operations

OperationPermissionDescription
List keysAny authenticated userShows all Cap site keys with their name and today’s stats (challenges, verified, failed)
Create keyWrite (admin/operator)Creates a new site key + secret pair. The secret is shown once in a banner — copy it immediately
Rotate secretWrite (admin/operator)Generates a new secret for an existing site key. The old secret stops working immediately. The new secret is shown once
Delete keyWrite (admin/operator)Permanently deletes the site key and its secret. Challenges using this key will fail until you switch the active key
Secrets are shown once

When you create a key or rotate a secret, the secret key is displayed in a yellow banner only once. Cap does not store it in a retrievable form. If you lose it, you must rotate the secret to get a new one — the old one will stop working.

Per-key stats

Each key row shows today’s statistics fetched from the Cap service:

ColumnDescription
KeyThe site key (public identifier)
NameOptional label you set when creating the key
ChallengesTotal challenges issued today using this key
VerifiedChallenges successfully solved today
FailedChallenges that failed verification today

These are real-time stats from the Cap service, not from coreX Platform’s ChallengeEvent table (which tracks per-rule events). The Keys tab stats are per-site-key; the Stats tab stats are per-rule.

When to create multiple keys

ScenarioWhy
Staging vs productionUse separate keys so a leaked staging key can’t be used to mint valid challenges against production
Multiple applicationsIf you run multiple sites behind the same coreX instance, give each its own key so you can revoke one without affecting the others
Key rotationCreate a new key, update the Settings tab to use it, then delete the old key — zero-downtime rotation
Rate limit isolationCap enforces per-key rate limits; separate keys prevent one high-traffic app from exhausting another’s quota

Relationship to the Settings tab

The Settings tab’s Native provider fields (cap_site_key and cap_secret) select which site key is active. The Keys tab manages what keys exist. The typical workflow is:

  1. Keys tab → create a site key (copy the secret)
  2. Settings tab → paste the site key and secret into the Native fields
  3. Apply Changes → HAProxy config is regenerated to use the new key
  4. Later, to rotate: Keys tab → rotate secret (copy new secret) → Settings tab → update the secret field → Apply Changes

Step-by-Step: Configure the Native Provider

  1. Ensure CAP_ADMIN_KEY is set in your .env file (this is the admin password for the Cap service — the same value must be set as ADMIN_KEY on the Cap container)
  2. Navigate to Security > CAPTCHA
  3. In the Provider tab, select Native
  4. Set the TTL (default 3600 is fine)
  5. If you don’t have a Cap site key yet, go to the Keys tab and click New Key — copy the secret when it’s shown
  6. Back in the Provider tab, enter the site key and secret from the Keys tab
  7. Click Save
  8. Click Apply Changes

Step-by-Step: Configure reCAPTCHA

  1. Obtain a site key and secret from Google reCAPTCHA
  2. Navigate to Security > CAPTCHA
  3. Select reCAPTCHA as the provider
  4. Enter the site key and secret
  5. Select version (v2 or v3)
  6. If v3, set the score threshold (e.g. 0.5)
  7. Click Save

Step-by-Step: Create a Challenge Rule

  1. Navigate to Security > Security Rules

  2. Click Add Rule

  3. Name: challenge-suspicious

  4. Expression: risk.score > 50

  5. Action: challenge

  6. Save and Apply

  7. Test:

    curl -k https://localhost/

    A request that triggers the rule should redirect to the challenge page.

Verification

  1. Trigger a challenge:

    curl -k -v https://localhost/ 2>&1 | grep -i 'location\|set-cookie'

    Expect a redirect to the challenge page and no _cv cookie on the first request.

  2. Complete the challenge and verify the _cv cookie is set.

  3. Re-request with the cookie:

    curl -k -b "_cv=<token>" https://localhost/

    The request should pass through without a challenge.

  4. Check stats:

    • Navigate to Security > CAPTCHA > Stats
    • Verify issued, solved, and failed events appear

Next Steps

  • Security Rules — Create rules that trigger challenges
  • WAF — Use challenge actions in WAF rules
  • Rate Limiting — Challenge clients that exceed limits