Skip to content

Latest commit

 

History

History
85 lines (68 loc) · 4.19 KB

File metadata and controls

85 lines (68 loc) · 4.19 KB

FCaptcha Helm chart

Open source CAPTCHA with proof of work, behavioural biometrics, and AI-agent detection. Self-hosted, no external dependencies.

helm install fcaptcha oci://ghcr.io/webdecoy/charts/fcaptcha \
  --namespace fcaptcha --create-namespace \
  --set secret=$(openssl rand -hex 32)

Or from a checkout:

helm install fcaptcha ./charts/fcaptcha \
  --namespace fcaptcha --create-namespace \
  --set secret=$(openssl rand -hex 32)

helm test fcaptcha -n fcaptcha

The chart will not install without a signing key

Both the chart and the server require a signing key. Generate one with openssl rand -hex 32; short and repetitive keys are rejected at server startup. The public development key is available only with explicit insecure development mode, whose bundled launcher binds to loopback.

Set secret, or point existingSecret at a Secret you manage with sealed-secrets, External Secrets or SOPS.

Read this before production

config.trustedProxies decides whose X-Forwarded-For is believed, and it matters more in Kubernetes than anywhere else: traffic arrives through an ingress controller, which is a proxy. If it is not trusted, every visitor is attributed to the controller and rate limiting collapses onto one address — silently. The default covers the usual in-cluster pod CIDRs; narrow it to your controller's range.

The default Go image may run multiple replicas when redis.url is configured. PoW, token replay, Siteverify idempotency, rate limits, suspicion, fingerprint cardinality, and site-key rotation guards are shared; one-time claims are atomic. Without Redis, run one replica. Go, Node, and Python images support the same shared-state contract.

The full list of deployment settings with security consequences is in SECURITY.md.

Values

Key Description Default
secret Token signing key. Required unless existingSecret is set ""
existingSecret Name of a Secret you manage, instead of one the chart creates ""
existingSecretKey Key within that Secret FCAPTCHA_SECRET
verifySecret Credential a backend presents when verifying a token. Empty means "same as the signing key". Splitting them means a leaked verify credential cannot also mint tokens ""
config.trustedProxies Peers allowed to set X-Forwarded-For / X-Real-IP / TLS-fingerprint headers in-cluster private ranges
config.allowedHostnames Comma-separated hostnames permitted to mint tokens. Empty accepts any origin ""
config.siteKeys Comma-separated allowlist of accepted site keys ""
config.logVerdicts One privacy-safe JSON line per verification false
config.logVerdictsIncludeRaw Also log free-text detection reasons. Can contain visitor-derived data false
image.repository / image.tag Container image; tag defaults to the chart's appVersion ghcr.io/webdecoy/fcaptcha
replicaCount See the note above before raising this 1
redis.url / redis.existingSecret Shared security state; required for multiple replicas ""
service.type / service.port ClusterIP / 80
ingress.enabled false
resources 100m CPU / 128Mi requested
autoscaling.enabled Off by default — replicas do not share state false
podDisruptionBudget.enabled false
extraEnv Anything the chart does not model []

Security defaults are on and not parameterised down: non-root (uid 65532), read-only root filesystem, all capabilities dropped, RuntimeDefault seccomp, no service-account token mounted. The image is a single static binary that writes nothing but /tmp.

What helm test checks

More than liveness. /health only proves the process is up, and the published image once returned 404 for /fcaptcha.js for three months while health stayed green. The test checks that the widget is served, that it declares UTF-8 (the translations are decoded using the document's encoding otherwise), that the Turnstile-compatible siteverify endpoint answers, and that a request carrying no proof of work is refused a token.