Skip to content

Captcha: client gates honor admin tolerance + touch loosening; default lenient - #19

Merged
botnick merged 2 commits into
mainfrom
feat/captcha-client-tolerance
Jun 17, 2026
Merged

Captcha: client gates honor admin tolerance + touch loosening; default lenient#19
botnick merged 2 commits into
mainfrom
feat/captcha-client-tolerance

Conversation

@botnick

@botnick botnick commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Why

Follow-up to #18. A code review (codex) of the captcha's elderly/touch friendliness found the real bottleneck: the admin Touch tolerance setting only scaled the server's acceptance. The client games kept fixed, stricter "feels aligned" gates, so even with lenient set, a close-enough gesture got a "miss" shake on the client and was never forwarded to the server. There was also no coarse-pointer (finger) loosening.

What

  • Thread the tolerance multiplier to the client via CaptchaChallengeDTO.tolerance (= the admin profile, cfg.toleranceMult). Not a secret — the server still holds the authoritative tolerance and re-validates every answer.
  • Apply it to every client alignment gate: Slide ALIGN, Rotate ALIGN_DEG, DragTarget ring radius, Connect target radius, PathTrace hit radius. Tap-match/sort have no gate and ignore it.
  • Coarse-pointer loosening: on (pointer: coarse) the client widens its gate a further 1.25× so an imprecise finger isn't pre-rejected before the server sees the answer. This is client-UX only; on the rare over-shoot the server simply asks for a retry (its existing flow).
  • Default tolerance → LENIENT (was standard). Audience includes older/less-dexterous users, and captcha security never rested on tight geometry (PoW economics + single-use + interaction risk + bindings — see docs/human-check-v3.md). Admins can still pick standard/strict.

Security note

Loosening only widens the geometric "close enough" band (max client factor ≈ lenient 1.3 × coarse 1.25 = 1.625). The interaction-risk engine, PoW, single-use tokens, and bindings are unchanged, so the moat is intact. The client never over-accepts beyond what the server will: the server is the authority and re-checks with cfg.toleranceMult.

Verification

  • npm run typecheck — clean
  • npm run test:captcha — 119 passed, 0 failed
  • npm run build — clean (HumanCheck stays a lazy ~12.7 kB gzip chunk)
  • Touch gesture handling is UI; not covered by the unit suite.

botnick added 2 commits June 17, 2026 12:08
…fault lenient

The admin "touch tolerance" only scaled the SERVER's acceptance — the client
games kept fixed, stricter "feels aligned" gates (Slide ALIGN, Rotate
ALIGN_DEG, the drop/connect/trace hit radii), so a lenient setting never felt
lenient: a close-enough gesture got a "miss" shake and was never forwarded.

Thread the tolerance multiplier to the client via the challenge DTO and apply
it to every alignment gate, widened a further 1.25x on coarse (touch) pointers
so an imprecise finger isn't pre-rejected before the server — which still holds
the authoritative tolerance — ever sees the answer. Tap-match/sort have no gate
and ignore it.

Default the tolerance profile to LENIENT (was standard). The audience includes
older/less-dexterous users and captcha security never rested on tight geometry
(PoW + single-use + interaction risk + bindings). Admins can still pick strict.
…bump

Review caught a client/server contract gap: a client-only coarse (touch)
1.25x bump, plus a guessed client base (Slide ALIGN=7, Rotate ALIGN_DEG=12)
that doesn't match the server's per-difficulty secret tolerance, let the
client accept gestures the server then rejected (e.g. Slide normal/hard,
or anything on touch) — a confusing "looked right, try again".

Carry each game's base acceptance (tol) in the public payload so the client
gate is exactly server_base * adminTolerance — never over- or under-shoots.
tol is not a secret: a bot submits the exact target regardless; tol only
sets how forgiving imprecise human input is. Drop the client-only coarse
multiplier (it had no server counterpart, and missed hybrid devices anyway);
touch forgiveness now comes from the lenient default applied consistently on
both sides. DragTarget/Connect/PathTrace already stayed within the server's
padded gate and are unchanged.
@botnick
botnick merged commit c129231 into main Jun 17, 2026
1 check passed
@botnick
botnick deleted the feat/captcha-client-tolerance branch June 17, 2026 05:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant