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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +10 -9
  4. data/UPSTREAM.md +6 -6
  5. data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  6. data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  7. data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
  8. data/lib/cfsolve/version.rb +2 -2
  9. data/vendor/cfsolve/README.md +0 -35
  10. data/vendor/cfsolve/examples/build_profile.rs +17 -15
  11. data/vendor/cfsolve/examples/capture_session.rs +37 -0
  12. data/vendor/cfsolve/examples/check_answers.rs +66 -0
  13. data/vendor/cfsolve/examples/compare_session.rs +190 -0
  14. data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
  15. data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  16. data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  17. data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
  18. data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
  19. data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
  20. data/vendor/cfsolve/src/capture/profile.rs +7 -18
  21. data/vendor/cfsolve/src/capture/recorded.rs +42 -7
  22. data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
  23. data/vendor/cfsolve/src/challenge/installed.rs +122 -271
  24. data/vendor/cfsolve/src/challenge/payload.rs +107 -5
  25. data/vendor/cfsolve/src/challenge/state.rs +98 -16
  26. data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
  27. data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
  28. data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
  29. data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
  30. data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
  31. data/vendor/cfsolve/src/http/client.rs +11 -0
  32. data/vendor/cfsolve/src/js/ast.rs +12 -3
  33. data/vendor/cfsolve/src/js/objects.rs +9 -2
  34. data/vendor/cfsolve/src/listing/fold.rs +0 -83
  35. data/vendor/cfsolve/src/listing/query.rs +25 -244
  36. data/vendor/cfsolve/src/listing.rs +0 -1
  37. data/vendor/cfsolve/src/session/initial.rs +14 -5
  38. data/vendor/cfsolve/src/session/probes.rs +160 -125
  39. data/vendor/cfsolve/src/session/timing.rs +58 -53
  40. data/vendor/cfsolve/src/session/widget.rs +5 -6
  41. data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
  42. data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
  43. data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
  44. data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
  45. data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
  46. data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
  47. data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
  48. data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
  49. data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
  50. data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
  51. data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
  52. data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
  53. data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
  54. data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
  55. data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
  56. data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
  57. data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
  58. data/vendor/cfsolve/src/stage2/probes.rs +0 -1
  59. data/vendor/cfsolve/src/stage2/records.rs +4 -13
  60. data/vendor/cfsolve/src/stage2/reference.json +1 -1
  61. data/vendor/cfsolve/src/stage2/reference.rs +98 -100
  62. data/vendor/cfsolve/src/stage2/registry.rs +41 -34
  63. data/vendor/cfsolve/src/stage2/replay.rs +182 -254
  64. data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
  65. data/vendor/cfsolve/src/stage2/trace.rs +18 -0
  66. data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
  67. data/vendor/cfsolve/src/stage2.rs +2 -1
  68. data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
  69. data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
  70. data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
  71. data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
  72. data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
  73. data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
  74. data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
  75. data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
  76. data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
  77. data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
  78. data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
  79. data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
  80. data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
  81. data/vendor/cfsolve/src/vm/interpret.rs +7 -2
  82. data/vendor/cfsolve/src/vm/machine.rs +6 -2
  83. data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
  84. data/vendor/cfsolve/src/vm/session.rs +36 -14
  85. data/vendor/cfsolve/src/vm/value.rs +13 -0
  86. data/vendor/cfsolve/src/vm.rs +1 -0
  87. data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
  88. metadata +13 -6
  89. data/vendor/cfsolve/src/listing/normalize.rs +0 -264
  90. data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
  91. data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
  92. data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
  93. 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> {