Skip to main content

Module token_lifecycle

Module token_lifecycle 

Source
Expand description

Challenge-token lifecycle contracts (T91). Strict per-vendor TTL / nonce / single-use / session-binding invariants enforced before submission. Challenge-token lifecycle contracts (T91).

§What this module does

Defines strict lifecycle contracts for challenge tokens — short-lived, vendor-issued artefacts (e.g. cf-chl-bypass, _px3, _abck, datadome=…) that the scraper must present alongside its request to convince the vendor the challenge has been solved.

Each TokenContract captures four invariants:

  1. Time-to-live: tokens older than their TTL are rejected as stale. The TTL is vendor-aware (see the vendor policy table below).
  2. Nonce binding: the contract carries a per-issuance nonce. The validator enforces that any subsequent submission carries the same nonce — a mismatched nonce is always rejected.
  3. Single-use: contracts with single_use = true may only be submitted once. Subsequent submissions trip the replay-defense path with InvalidationReason::NonceReplayed.
  4. Vendor family + challenge class: every contract is stamped with a VendorId family (e.g. Cloudflare, PerimeterX, Akamai, DataDome) and a ChallengeClass (interstitial, captcha, proof-of-work, integrity check, cookie refresh, none). The two together drive diagnostic routing — operators can wire InvalidationReason events into the per-family audit log without re-running the classifier.

§Why a contract?

A naïve scraper that caches a token for hours and replays it across sessions trains the vendor to escalate its posture (rotating nonces more aggressively, shortening TTLs, eventually locking the scraper out entirely). The contract pins when a token may be used, how often, and which vendor issued it so the policy planner can refresh the token before the vendor invalidates it server-side.

§Nonce bookkeeping (T83 integration)

Per-issuance nonces are tracked by a NonceBook — a capacity-bounded LRU+TTL store that reuses the same LruTtlStore primitive the ChallengeMemory uses (T83). That keeps eviction + expiry semantics consistent across both short-horizon stores and satisfies the “no new cache store” constraint.

A TokenValidator consumes a TokenContract and a present-time clock, looks up the nonce in the NonceBook, evaluates the four invariants, and returns a ValidationOutcome the runner can act on. The validator also integrates with the T83 feedback loop by emitting a structured InvalidationReason (with vendor family + challenge class) so the diagnostic payload can route invalidations to the correct per-family audit log.

§Vendor policy table

The defaults are tuned for the Tier 1 vendor catalogue shipped with T89 and the Tier 1 / Tier 2 playbooks shipped with T85:

Vendor familyDefault TTLMax TTLNonce requiredSingle-useSession binding
VendorId::Cloudflare30 minutes45 minutesyesyesoptional
VendorId::Akamai15 minutes30 minutesyesyesrequired
VendorId::DataDome10 minutes20 minutesyesyesrequired
VendorId::PerimeterX15 minutes30 minutesyesyesrequired
VendorId::Hcaptcha5 minutes10 minutesyesyesoptional
VendorId::Recaptcha5 minutes10 minutesyesyesoptional
VendorId::Kasada5 minutes10 minutesyesyesrequired
VendorId::FingerprintCom1 hour2 hoursyesnooptional
VendorId::ShapeSecurity10 minutes20 minutesyesyesrequired
VendorId::Imperva15 minutes30 minutesyesyesrequired
VendorId::Unknown5 minutes10 minutesyesyesoptional

Operators can override per-family defaults via TokenPolicyTable::with_policy; the validator consults the table before applying the contract’s own ttl field, so an over-long contract is clamped to policy.max_ttl at validation time.

§Feature flag

The module is default-on (gated behind the caching feature, which is part of the stygian-charon default feature set, so the module is always compiled). It is purely additive — no existing public type gains a new field, no existing behaviour changes, and no new feature gate is introduced. Operators who want the strict lifecycle validation call TokenValidator::validate on every submission; callers that ignore it see no behaviour change.

§Example

use std::time::Duration;
use stygian_charon::token_lifecycle::{
    ChallengeClass, TokenContract, TokenPolicyTable, TokenValidator,
    ValidationOutcome,
};
use stygian_charon::vendor_classifier::VendorId;

// Build a policy table seeded with the per-vendor defaults
// and a 256-entry nonce book with a 10-minute TTL.
let policy = TokenPolicyTable::with_builtin_defaults();
let validator = TokenValidator::with_defaults(policy);

// A Cloudflare interstitial token issued 1 minute ago.
let contract = TokenContract {
    token_id: "cf-chl-bypass-abc".to_string(),
    issued_at_unix_secs: 1_700_000_000,
    ttl: Duration::from_mins(30),
    nonce: "nonce-xyz".to_string(),
    vendor_family: VendorId::Cloudflare,
    challenge_class: ChallengeClass::Interstitial,
    single_use: true,
    bound_session: None,
    description: "Cloudflare turnstile bypass token".to_string(),
};

// First submission passes: the nonce has not been seen.
let outcome = validator.validate(&contract, Some("session-1"), 1_700_000_060);
assert!(matches!(outcome, ValidationOutcome::Ok { .. }));

Structs§

NonceBook
Capacity-bounded LRU+TTL store of NonceObservations.
NonceObservation
One observation row in the NonceBook.
TokenContract
Lifecycle contract for a single challenge token.
TokenLifecycleError
Errors returned by TokenValidator when it rejects a token submission.
TokenPolicy
Per-vendor defaults for the TokenValidator.
TokenPolicyTable
Per-vendor policy lookup table.
TokenValidator
Token validator.

Enums§

ChallengeClass
Stable label for the kind of challenge a token is bound to.
InvalidationKind
Coarse-grained kind tag for InvalidationReason.
InvalidationReason
Structured reason a TokenValidator rejected a token submission.
ValidationOutcome
Outcome of a TokenValidator::validate call.

Constants§

DEFAULT_NONCE_BOOK_CAPACITY
Default capacity (in nonce entries) for the NonceBook. Conservative default — most workflows observe a few hundred nonces per session.
DEFAULT_NONCE_TTL
Default TTL for nonce observations: 10 minutes.

Functions§

builtin_token_policies
Snapshot of the built-in per-vendor policy table.
nonce_book_key
Build a stable, lower-cased cache key for a (vendor_family, nonce) tuple.