cfsolve 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +10 -9
- data/UPSTREAM.md +6 -6
- data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
- data/lib/cfsolve/version.rb +2 -2
- data/vendor/cfsolve/README.md +0 -35
- data/vendor/cfsolve/examples/build_profile.rs +17 -15
- data/vendor/cfsolve/examples/capture_session.rs +37 -0
- data/vendor/cfsolve/examples/check_answers.rs +66 -0
- data/vendor/cfsolve/examples/compare_session.rs +190 -0
- data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
- data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
- data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
- data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
- data/vendor/cfsolve/src/capture/profile.rs +7 -18
- data/vendor/cfsolve/src/capture/recorded.rs +42 -7
- data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
- data/vendor/cfsolve/src/challenge/installed.rs +122 -271
- data/vendor/cfsolve/src/challenge/payload.rs +107 -5
- data/vendor/cfsolve/src/challenge/state.rs +98 -16
- data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
- data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
- data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
- data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
- data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
- data/vendor/cfsolve/src/http/client.rs +11 -0
- data/vendor/cfsolve/src/js/ast.rs +12 -3
- data/vendor/cfsolve/src/js/objects.rs +9 -2
- data/vendor/cfsolve/src/listing/fold.rs +0 -83
- data/vendor/cfsolve/src/listing/query.rs +25 -244
- data/vendor/cfsolve/src/listing.rs +0 -1
- data/vendor/cfsolve/src/session/initial.rs +14 -5
- data/vendor/cfsolve/src/session/probes.rs +160 -125
- data/vendor/cfsolve/src/session/timing.rs +58 -53
- data/vendor/cfsolve/src/session/widget.rs +5 -6
- data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
- data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
- data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
- data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
- data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
- data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
- data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
- data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
- data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
- data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
- data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
- data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
- data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
- data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
- data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
- data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
- data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
- data/vendor/cfsolve/src/stage2/probes.rs +0 -1
- data/vendor/cfsolve/src/stage2/records.rs +4 -13
- data/vendor/cfsolve/src/stage2/reference.json +1 -1
- data/vendor/cfsolve/src/stage2/reference.rs +98 -100
- data/vendor/cfsolve/src/stage2/registry.rs +41 -34
- data/vendor/cfsolve/src/stage2/replay.rs +182 -254
- data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
- data/vendor/cfsolve/src/stage2/trace.rs +18 -0
- data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
- data/vendor/cfsolve/src/stage2.rs +2 -1
- data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
- data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
- data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
- data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
- data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
- data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
- data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
- data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
- data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
- data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
- data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
- data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
- data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
- data/vendor/cfsolve/src/vm/interpret.rs +7 -2
- data/vendor/cfsolve/src/vm/machine.rs +6 -2
- data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
- data/vendor/cfsolve/src/vm/session.rs +36 -14
- data/vendor/cfsolve/src/vm/value.rs +13 -0
- data/vendor/cfsolve/src/vm.rs +1 -0
- data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
- metadata +13 -6
- data/vendor/cfsolve/src/listing/normalize.rs +0 -264
- data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
- data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
- data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
- data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
//! Browser-measured CSSOM spelling, reused only when the source differs by session names.
|
|
2
|
+
//!
|
|
3
|
+
//! A whole-sheet comparison guards against reusing a serialization after its CSS changes.
|
|
4
|
+
|
|
5
|
+
use std::collections::BTreeMap;
|
|
6
|
+
use std::sync::LazyLock;
|
|
7
|
+
|
|
8
|
+
use anyhow::{Context, Result, ensure};
|
|
9
|
+
use regex::{Captures, Regex};
|
|
10
|
+
use scraper::{Html, Selector};
|
|
11
|
+
use serde::{Deserialize, Serialize};
|
|
12
|
+
use sha2::{Digest, Sha256};
|
|
13
|
+
|
|
14
|
+
use super::records::Probe;
|
|
15
|
+
use super::reference::{STYLESHEETS_HASH, STYLESHEETS_TEXT};
|
|
16
|
+
use super::registry::Kind;
|
|
17
|
+
use super::vocabulary;
|
|
18
|
+
use crate::js::{JsObject, json};
|
|
19
|
+
|
|
20
|
+
static NAME: LazyLock<Regex> =
|
|
21
|
+
LazyLock::new(|| Regex::new(r"_[0-9a-f]{24}").expect("the session name pattern is valid"));
|
|
22
|
+
|
|
23
|
+
/// Source CSS and the browser's serialization of each of its rules.
|
|
24
|
+
#[derive(Debug, Deserialize, Serialize)]
|
|
25
|
+
pub struct Stylesheet {
|
|
26
|
+
pub source: String,
|
|
27
|
+
pub rules: Vec<String>,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Numbers session names by first appearance; returns the text and the names in order.
|
|
31
|
+
fn normalized(source: &str) -> (String, Vec<String>) {
|
|
32
|
+
let mut names = Vec::new();
|
|
33
|
+
let text = NAME.replace_all(source, |found: &Captures<'_>| {
|
|
34
|
+
let name = &found[0];
|
|
35
|
+
let index = names.iter().position(|old| old == name).unwrap_or_else(|| {
|
|
36
|
+
names.push(name.to_owned());
|
|
37
|
+
names.len() - 1
|
|
38
|
+
});
|
|
39
|
+
format!("__name_{index}__")
|
|
40
|
+
});
|
|
41
|
+
(text.into_owned(), names)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl Stylesheet {
|
|
45
|
+
/// The rules under `current`'s session names, if `current` is this sheet's CSS.
|
|
46
|
+
fn translate(&self, current: &str) -> Option<Vec<String>> {
|
|
47
|
+
let (old, recorded_names) = normalized(&self.source);
|
|
48
|
+
let (new, current_names) = normalized(current);
|
|
49
|
+
if old != new {
|
|
50
|
+
return None;
|
|
51
|
+
}
|
|
52
|
+
let mapping: BTreeMap<String, String> =
|
|
53
|
+
recorded_names.into_iter().zip(current_names).collect();
|
|
54
|
+
self.rules
|
|
55
|
+
.iter()
|
|
56
|
+
.map(|rule| {
|
|
57
|
+
let known = NAME
|
|
58
|
+
.find_iter(rule)
|
|
59
|
+
.all(|found| mapping.contains_key(found.as_str()));
|
|
60
|
+
known.then(|| {
|
|
61
|
+
NAME.replace_all(rule, |found: &Captures<'_>| mapping[&found[0]].clone())
|
|
62
|
+
.into_owned()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
.collect()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// The hex-named class selector the stylesheet probe excludes from its hash, or `None` if
|
|
70
|
+
/// `probe` is not the stylesheet probe.
|
|
71
|
+
pub(crate) fn excluded_selector(probe: &Probe) -> Option<Result<&str>> {
|
|
72
|
+
let operation = probe.operation.as_ref()?;
|
|
73
|
+
(operation.family() == Kind::Stylesheets).then(|| {
|
|
74
|
+
probe
|
|
75
|
+
.features
|
|
76
|
+
.iter()
|
|
77
|
+
.find(|text| text.strip_prefix('.').is_some_and(vocabulary::is_hex_name))
|
|
78
|
+
.map(String::as_str)
|
|
79
|
+
.context("the stylesheet probe has no excluded selector")
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Recomputes the CSS text and its digest from this iframe's styles, rejecting unknown CSS.
|
|
84
|
+
pub(crate) fn patch(
|
|
85
|
+
html: &str,
|
|
86
|
+
templates: &[Stylesheet],
|
|
87
|
+
excluded: &str,
|
|
88
|
+
fields: &mut JsObject,
|
|
89
|
+
) -> Result<()> {
|
|
90
|
+
let document = Html::parse_document(html);
|
|
91
|
+
let selector = Selector::parse("style").expect("`style` is a valid selector");
|
|
92
|
+
let mut included = Vec::new();
|
|
93
|
+
let mut matched = None;
|
|
94
|
+
for (index, style) in document.select(&selector).enumerate() {
|
|
95
|
+
let source = style.inner_html();
|
|
96
|
+
let rules = templates
|
|
97
|
+
.iter()
|
|
98
|
+
.find_map(|template| template.translate(&source))
|
|
99
|
+
.with_context(|| format!("no CSSOM measurement matches iframe stylesheet {index}"))?;
|
|
100
|
+
let text = format!("{}{index}", rules.join("|"));
|
|
101
|
+
if text.contains(excluded) {
|
|
102
|
+
matched = Some(text);
|
|
103
|
+
} else {
|
|
104
|
+
included.push(text);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
ensure!(
|
|
108
|
+
!included.is_empty(),
|
|
109
|
+
"the stylesheet hash has no input sheets"
|
|
110
|
+
);
|
|
111
|
+
let text = matched.context("the stylesheet probe's excluded selector is absent")?;
|
|
112
|
+
let hash = format!("{:x}", Sha256::digest(included.join("|").as_bytes()));
|
|
113
|
+
fields.insert(STYLESHEETS_TEXT.into(), json!(text));
|
|
114
|
+
fields.insert(STYLESHEETS_HASH.into(), json!(&hash[..32]));
|
|
115
|
+
Ok(())
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#[cfg(test)]
|
|
119
|
+
mod tests {
|
|
120
|
+
use super::*;
|
|
121
|
+
|
|
122
|
+
#[test]
|
|
123
|
+
fn hashes_current_names_and_excludes_the_reported_sheet() {
|
|
124
|
+
let templates = vec![
|
|
125
|
+
Stylesheet {
|
|
126
|
+
source: "._111111111111111111111111{color:red}".into(),
|
|
127
|
+
rules: vec!["._111111111111111111111111 { color: red; }".into()],
|
|
128
|
+
},
|
|
129
|
+
Stylesheet {
|
|
130
|
+
source: "._222222222222222222222222{width:0}".into(),
|
|
131
|
+
rules: vec!["._222222222222222222222222 { width: 0px; }".into()],
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
let html = "<style>._aaaaaaaaaaaaaaaaaaaaaaaa{color:red}</style><style>._bbbbbbbbbbbbbbbbbbbbbbbb{width:0}</style>";
|
|
135
|
+
let mut fields = JsObject::new();
|
|
136
|
+
patch(html, &templates, "._bbbbbbbbbbbbbbbbbbbbbbbb", &mut fields).unwrap();
|
|
137
|
+
assert_eq!(
|
|
138
|
+
fields[STYLESHEETS_HASH],
|
|
139
|
+
json!("c04d76a7e9af7f370a08eb5d1776c682")
|
|
140
|
+
);
|
|
141
|
+
assert_eq!(
|
|
142
|
+
fields[STYLESHEETS_TEXT],
|
|
143
|
+
json!("._bbbbbbbbbbbbbbbbbbbbbbbb { width: 0px; }1")
|
|
144
|
+
);
|
|
145
|
+
assert!(
|
|
146
|
+
patch(
|
|
147
|
+
&html.replace("color:red", "color:blue"),
|
|
148
|
+
&templates,
|
|
149
|
+
"._bbbbbbbbbbbbbbbbbbbbbbbb",
|
|
150
|
+
&mut fields
|
|
151
|
+
)
|
|
152
|
+
.is_err()
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[test]
|
|
157
|
+
fn translation_requires_identical_css_and_consistent_names() {
|
|
158
|
+
let measured = Stylesheet {
|
|
159
|
+
source: "._111111111111111111111111{color:red}".into(),
|
|
160
|
+
rules: vec!["._111111111111111111111111 { color: red; }".into()],
|
|
161
|
+
};
|
|
162
|
+
assert_eq!(
|
|
163
|
+
measured.translate("._aaaaaaaaaaaaaaaaaaaaaaaa{color:red}"),
|
|
164
|
+
Some(vec!["._aaaaaaaaaaaaaaaaaaaaaaaa { color: red; }".into()])
|
|
165
|
+
);
|
|
166
|
+
assert!(
|
|
167
|
+
measured
|
|
168
|
+
.translate("._aaaaaaaaaaaaaaaaaaaaaaaa{color:blue}")
|
|
169
|
+
.is_none()
|
|
170
|
+
);
|
|
171
|
+
assert_ne!(
|
|
172
|
+
normalized("_111111111111111111111111 _111111111111111111111111").0,
|
|
173
|
+
normalized("_aaaaaaaaaaaaaaaaaaaaaaaa _bbbbbbbbbbbbbbbbbbbbbbbb").0
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
use std::collections::BTreeSet;
|
|
8
8
|
|
|
9
|
+
use anyhow::{Context, Result};
|
|
9
10
|
use swc_core::ecma::ast::{
|
|
10
11
|
AssignExpr, AssignTarget, BinaryOp, BlockStmt, CallExpr, Expr, FnDecl, Function, Lit,
|
|
11
12
|
MemberProp, SimpleAssignTarget, Stmt,
|
|
12
13
|
};
|
|
13
14
|
use swc_core::ecma::visit::{Visit, VisitWith};
|
|
14
15
|
|
|
16
|
+
use super::vocabulary;
|
|
15
17
|
use crate::js::ast::{self, ParsedScript};
|
|
16
18
|
use crate::script::Source;
|
|
17
19
|
use crate::{candidates, vm};
|
|
@@ -31,6 +33,22 @@ pub struct Trace {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
impl Trace {
|
|
36
|
+
/// The frames the environment audit reports, including a worker continuation when present.
|
|
37
|
+
pub fn audit_key(&self, features: &[String], controller: Option<&str>) -> Result<String> {
|
|
38
|
+
let entered = features
|
|
39
|
+
.windows(2)
|
|
40
|
+
.find_map(|pair| match pair {
|
|
41
|
+
[name, next] if *next == self.replace && !vocabulary::is_hex_name(name) => {
|
|
42
|
+
Some(name)
|
|
43
|
+
}
|
|
44
|
+
[previous, name] if *previous == self.replace && name.contains('$') => Some(name),
|
|
45
|
+
_ => None,
|
|
46
|
+
})
|
|
47
|
+
.context("environment audit has no session trace entry")?;
|
|
48
|
+
let second = controller.unwrap_or(&self.returned_root);
|
|
49
|
+
Ok(format!("{},{second},{entered}", self.initial_root))
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
/// Recovers the iframe's trace if its helpers and entry sites are present and unambiguous.
|
|
35
53
|
#[must_use]
|
|
36
54
|
pub fn recover(source: &Source) -> Option<Self> {
|