1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! JavaScript integrity trap canary probes.
//!
//! ## What is a "JavaScript integrity trap"?
//!
//! Modern anti-bot vendors (`Cloudflare`, `DataDome`, `PerimeterX`,
//! Akamai Bot Manager, `Kasada`) ship detection scripts that look for
//! artefacts left by **patched** browser surfaces — places where a
//! stealth framework rewrote a native prototype, getter, or accessor
//! and the patch is detectable from the JavaScript side. Classic
//! examples:
//!
//! - `Object.getOwnPropertyDescriptor(Navigator.prototype, "webdriver")`
//! returning a **data property** instead of a native **accessor**.
//! - `Function.prototype.toString` showing patch source code (e.g.
//! `"function webdriver() { [native code] }"`) when applied to a
//! patched prototype method.
//! - `Performance.now` returning suspiciously round / quantized
//! values that betray deterministic jitter injection.
//!
//! The traps come in two shapes:
//!
//! - **Suspected** — the surface shape is unusual but the signal is
//! ambiguous (e.g. a non-`native code` `toString` on a polyfill
//! that the user's browser already shipped).
//! - **Confirmed** — the surface shape is only achievable via a
//! stealth framework patch on a real browser (e.g. `webdriver`
//! is a **data** property on `Navigator.prototype`).
//!
//! ## What this module provides
//!
//! 1. A stable [`IntegrityProbe`] catalogue with weighted risk
//! contributions and per-probe mitigation hints (see
//! [`probes::all_probes`]).
//! 2. A pure-Rust scoring pipeline ([`report::IntegrityRiskScore`])
//! that turns a set of [`probes::ProbeFinding`] records into an
//! aggregate score and a documented **Suspected** vs **Confirmed**
//! classification.
//! 3. A trend-detection seam ([`trend::CanaryTrendObservation`])
//! that future canary infrastructure (T84) can subscribe to
//! without modifying the probe set.
//!
//! ## Probe catalogue
//!
//! The default probe set (see [`probes::all_probes`]) covers eight
//! surfaces:
//!
//! | Probe | Default weight | What it checks |
//! |---|---|---|
//! | [`probes::IntegrityProbeId::WebDriverDescriptorNative`] | 0.20 | `Navigator.prototype.webdriver` accessor shape |
//! | [`probes::IntegrityProbeId::FunctionToStringNative`] | 0.18 | `Function.prototype.toString` reports `[native code]` for patched natives |
//! | [`probes::IntegrityProbeId::ErrorToStringNative`] | 0.08 | `(function(){}).toString()` reports `[native code]` |
//! | [`probes::IntegrityProbeId::IntlDateTimeFormatNative`] | 0.10 | `Intl.DateTimeFormat.prototype.format` is native |
//! | [`probes::IntegrityProbeId::RegExpTestNative`] | 0.08 | `RegExp.prototype.test` is native |
//! | [`probes::IntegrityProbeId::CanvasGetImageDataNative`] | 0.10 | `CanvasRenderingContext2D.prototype.getImageData` is native |
//! | [`probes::IntegrityProbeId::PerformanceNowResolution`] | 0.14 | `performance.now()` resolution is plausible (not quantized) |
//! | [`probes::IntegrityProbeId::ProxyTrapObservable`] | 0.12 | `Proxy` traps on patched natives do not leak surface state |
//!
//! ## Feature flag
//!
//! This module is **default-on** and is always compiled as part of
//! the `stygian-browser` crate. The probe set and scoring pipeline
//! are pure Rust with **no I/O** so they are safely callable in
//! deterministic tests without booting Chrome.
//!
//! ## Integration with the existing diagnostic payload
//!
//! The canary report attaches additively to
//! [`crate::diagnostic::DiagnosticReport`] via
//! [`crate::diagnostic::DiagnosticReport::with_integrity_canary`]
//! (added in this task) so downstream automation can consume the
//! finding set without breaking the legacy schema.
//!
//! ## Reuse of the canary trend pipeline (T84)
//!
//! T84 will add a stealth canary hard-gate. This module exposes
//! [`trend::CanaryTrendObservation`] as the **stable seam** that
//! future canary infrastructure can consume without changing probe
//! definitions: each observation carries the normalized risk score
//! and a deterministic `signature` string so two reports with the
//! same findings produce byte-identical trend entries.
//!
//! # Example
//!
//! ```
//! use stygian_browser::integrity_canary::{
//! IntegrityCanaryReport, IntegrityProbe, IntegrityRiskClassification,
//! };
//!
//! // Simulate a probe set where two probes fired with confirmed traps.
//! let finding_a = IntegrityProbe::confirmed_finding(
//! "webdriver_descriptor_native",
//! 0.20,
//! "Navigator.prototype.webdriver is a data property (should be an accessor)",
//! );
//! let finding_b = IntegrityProbe::confirmed_finding(
//! "performance_now_resolution",
//! 0.14,
//! "performance.now() values are quantized to 0.1 ms (timing-noise injection)",
//! );
//!
//! let report = IntegrityCanaryReport::from_findings(vec![finding_a, finding_b]);
//! assert!(report.score.value() > 0.0);
//! assert!(matches!(
//! report.score.classification(),
//! IntegrityRiskClassification::Confirmed | IntegrityRiskClassification::Suspected
//! ));
//! assert_eq!(report.findings.len(), 2);
//! ```
pub use ;
pub use ;
pub use ;