Expand description
Queue and interstitial detection routing.
§What is an “interstitial”?
Anti-bot vendors (Cloudflare, DataDome, PerimeterX,
Akamai Bot Manager, Kasada, Fingerprint.com) often
respond to a high-risk navigation with a non-success page
that is not a hard 4xx/5xx and not the target
document. These intermediate pages come in four shapes:
- Queue / waiting room — the user is told to wait
(“Please wait…”, “You are #5 in line”, “Estimated wait
time 2 minutes”). The page often returns a
2xxor3xxwithqueue/waitbody markers. - Challenge interstitial — a vendor-issued
captcha / turnstile / proof-of-work challenge that the
client must solve before being allowed to the target
document. Common markers:
cf-chl-bypass,g-recaptcha,h-captcha,cf-turnstile,akamai,perimeterx,_abck. - Hard block — a terminal vendor block page
(“Access denied”, “Request blocked”,
Just a moment...that does not auto-resolve, vendor-specific/blocked//forbiddenURLs). - Transient redirect — a
3xxredirect chain that should be followed before classifying the response (often the case for region / cookie-consent redirections that vendors insert before the challenge).
§What this module provides
InterstitialClassifier— pure deterministic classifier that consumes aPageSignatureand returns anInterstitialKind.InterstitialRouter— maps the classification to a dedicatedInterstitialRoute(the dedicated acquisition strategy per kind) with explicit diagnostics in aRouterDecision.- A stable
severityfield onRouterDecisionthat observability tooling can use to distinguishInterstitialKind::Queue(retryable wait) fromInterstitialKind::HardBlock(terminal escalation) without branching on the kind itself.
§Routing behavior table
InterstitialKind | Default route | Default severity | Strategy hint |
|---|---|---|---|
Queue | InterstitialRoute::WaitAndRetry | Retryable | Wait the configured interval, then retry. Honors the optional queue position hint. |
Challenge | InterstitialRoute::ChallengeSolve | RequiresSolve | Escalate to a browser with sticky session + solve budget. Optional vendor hint narrows the strategy. |
HardBlock | InterstitialRoute::HardBlock | Terminal | Rotate session + invalidate sticky context + escalate to the strongest available strategy. |
Transient | InterstitialRoute::Transient | Retryable | Follow redirect chain (bounded hops), then re-classify. |
§Integration with AcquisitionRunner
AcquisitionRequest::interstitial
carries a previously-observed PageSignature plus an
InterstitialPolicy into the runner. The runner
evaluates the signature via InterstitialClassifier
before any stage executes:
- The resulting
RouterDecisionis attached toAcquisitionResult::interstitialso downstream policy mapping (T83 / T85 / T89 / T93) can consume the decision as a strategy hint. - When the decision is non-
TransientandInterstitialPolicy::short_circuit_on_classifiedistrue(the default), the runner short-circuits with a structuredStageFailureKind::InterstitialRoutedfailure tagged with the decision so the calling layer can route via the dedicated strategy without burning through the generic ladder.
Transient redirects do not short-circuit by default — they flow through the ladder so the redirect can be followed normally.
§Feature flag
This module is default-on and is always compiled as
part of the stygian-browser crate. No new feature gate
is introduced; the integration is purely additive on
crate::acquisition::AcquisitionRequest and
crate::acquisition::AcquisitionResult.
§Example
use stygian_browser::interstitial_router::{
InterstitialClassifier, InterstitialKind, InterstitialRouter, PageSignature,
};
// A Cloudflare challenge interstitial observed on a previous attempt.
let signature = PageSignature::new(
"https://example.com/cdn-cgi/challenge-platform/h/b",
Some(403),
)
.with_body_marker("cf-chl-bypass")
.with_header("cf-mitigated");
let classifier = InterstitialClassifier::new();
let kind = classifier.classify(&signature);
assert_eq!(kind, InterstitialKind::Challenge);
let router = InterstitialRouter::with_defaults();
let decision = router.route(&signature, kind);
assert!(decision.is_classified());
assert_eq!(decision.kind(), InterstitialKind::Challenge);Structs§
- Interstitial
Classifier - Deterministic interstitial classifier.
- Interstitial
Policy - Routing tunables for
InterstitialRouter. - Interstitial
Router - Dedicated acquisition router for classified interstitials.
- Page
Signature - Page signature consumed by the
InterstitialClassifier. - Page
Signature Evidence - Evidence the classifier extracted from a
PageSignature. - Router
Decision - Result of routing a
PageSignature. - Router
Decision Log - Wrapper struct that records the router decision
alongside the original
PageSignaturefor audit / replay.
Enums§
- Interstitial
Kind - Classification kind for an interstitial page.
- Interstitial
Route - Dedicated acquisition route per
InterstitialKind. - Interstitial
Severity - Operational severity tier for an interstitial decision.
Constants§
- DEFAULT_
CHALLENGE_ SOLVE_ BUDGET_ MS - Default challenge solve budget.
- DEFAULT_
HARD_ BLOCK_ ESCALATION - Default strategy to escalate to on a hard block.
- DEFAULT_
MAX_ TRANSIENT_ HOPS - Default max redirect hops to follow on a transient page.
- DEFAULT_
QUEUE_ INTERVAL_ MS - Default wait interval between queue retries.
- DEFAULT_
QUEUE_ MAX_ RETRIES - Default maximum retries for a queue page.
- DEFAULT_
TRANSIENT_ FOLLOW_ REDIRECT - Default follow-redirect flag for transient pages.
Functions§
- classify_
and_ route - One-shot helper: classify + route via a default router. Convenience for tests and call sites that don’t need to customise the policy.
- route
- One-shot helper: route a pre-classified signature via a default router.