fix(content): detect explicitly-rendered Turnstile managed challenge (TPS /InternalCaptcha)#7
Merged
Merged
Conversation
Capture the /InternalCaptcha gap: an explicitly-rendered Cloudflare Turnstile (turnstile.render(...)) exposes no .cf-turnstile container and about:blank widget iframes, so detectChallenge() misses it and the sidebar shows verdict buttons over a live bot-gate. The reproducing fixture (API script present, no container, no response input -> true) is red; two regression locks ship green alongside it: - solved container-Turnstile WITH the api.js script -> false, proving the coming `!turnstile` guard defers the container case to the existing solved/unsolved logic (real solved Turnstile pages carry the script too). - Clym-consent-only (cf.clym-widget.net, no challenges.cloudflare.com script) -> false, locking the selector to the specific host so a bare "cf"/"cloudflare" substring can't false-positive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
detectChallenge() now returns true when the Turnstile API script (challenges.cloudflare.com/turnstile) is present in the top document, catching the TruePeopleSearch /InternalCaptcha managed challenge that renders Turnstile via turnstile.render(...) — no .cf-turnstile container, about:blank widget iframes — which every prior selector missed. The sidebar now shows the challenge view instead of verdict buttons over a live bot-gate. Match the specific challenges.cloudflare.com host only: the same page hosts a cf.clym-widget.net iframe (Clym consent, not Cloudflare) that a bare "cf"/"cloudflare" substring would false-positive on. The `!turnstile` guard is load-bearing, not stylistic — real solved Turnstile pages also carry this script, so without it every solved container-Turnstile would falsely re-detect; the guard routes the container case to the existing solved/unsolved logic. Verified against live QA: clean /results pages return null for this selector (no false positive), and solving /InternalCaptcha navigates away, so the fresh load reports CHALLENGE_RESOLVED with no in-page solved-state detection needed. Scoped minimal by design — the human-in-the-loop plus Skip is the backstop, so the unsolved-refinement and observer hardening are deferred until a broker whose gate resolves inline actually needs them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
detectChallenge()'s Turnstile-script signal (added in this branch) is generic and manifest-bounded, but its safety rests on the gate navigating away on solve — proven on TruePeopleSearch only (n=1). Record the per-broker onboarding gate in the M9 milestone and a code-facing pointer in the TODO table, so a future broker that resolves its gate inline gets a resolve signal before it can strand the challenge view over real results. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
detectChallenge()now returnstruewhen the Cloudflare Turnstile API script (challenges.cloudflare.com/turnstile) is present in the top document, closing a gap where the TruePeopleSearch/InternalCaptchamanaged challenge slipped past every existing heuristic and the sidebar showed verdict buttons over a live bot-gate.Why it was missed
The gate renders Turnstile via
turnstile.render(...)— so there is no.cf-turnstilecontainer, and the widget iframes areabout:blank(content injected cross-frame). The only reliable top-document signal is the Turnstile API<script>. Every prior selector (.cf-turnstile,iframe[src*="challenges.cloudflare.com"], the interstitial ids) keys off elements this shape doesn't expose.Pre-existing gap, not a PR #6 regression —
src/content/classify.tsis untouched by the tab-registry PR. Found during manual QA of that branch.The fix (one line)
Two non-obvious details, both locked by tests:
cf.clym-widget.netiframe (a Clym consent widget, not Cloudflare — thecf.prefix is coincidental). A barecf/cloudflarematch would false-positive on it.!turnstileguard is load-bearing. Real solved Turnstile pages also carry the api.js script; without the guard, every solved container-Turnstile would falsely re-detect. The guard routes the container case to the existing solved/unsolved logic.QA evidence (Firefox 140+)
/resultspages return null for this selector → no false-positive challenge over a real listing./InternalCaptchanavigates away to the person page → the fresh load reportsCHALLENGE_RESOLVED; no in-page solved-state detection needed./InternalCaptcha→ challenge view (Skip only) → solve → back to results, challenge key cleared, verdict view returns.Scope — deliberately minimal
The human-in-the-loop plus Skip (no-wedge rule) is the backstop, so hardening for shapes no shipping broker exhibits is deferred:
cf-turnstile-responsecheck — inert on the callback-rendered TPS interstitial), the observerattributeFilterhardening (load-time detection already catches TPS), and the option-2 per-brokerchallenge.urlIncludeshint (schema + signed-dataset + CI cost, not yet needed).plan/expurge-progress.md) — detection is generic and manifest-bounded, but resolve-via-navigation is proven n=1 (TPS). A future broker that resolves its gate inline needs a resolve signal before onboarding, or it would strand the challenge view over real results.Tests
test(content): reproducing fixture (managed challenge → true) + two regression locks (solved-container-with-script → false for the guard; Clym-only → false for the host-specific selector).tsc --noEmitclean,npm run buildclean.🤖 Generated with Claude Code