flareguard 0.1.0
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.
- package/Cargo.lock +2962 -0
- package/Cargo.toml +66 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/bin/flareguard.js +78 -0
- package/package.json +50 -0
- package/scripts/postinstall.mjs +83 -0
- package/src/bindings/ast_scanner.rs +950 -0
- package/src/bindings/cli.rs +49 -0
- package/src/bindings/jsonc.rs +166 -0
- package/src/bindings/mod.rs +7 -0
- package/src/bindings/reporter.rs +328 -0
- package/src/bindings/types.rs +129 -0
- package/src/bindings/validator.rs +227 -0
- package/src/bindings/wrangler.rs +647 -0
- package/src/cli.rs +77 -0
- package/src/lib.rs +7 -0
- package/src/main.rs +451 -0
- package/src/origin/cli.rs +92 -0
- package/src/origin/cloudflare.rs +170 -0
- package/src/origin/confidence.rs +320 -0
- package/src/origin/crtsh.rs +144 -0
- package/src/origin/dns.rs +177 -0
- package/src/origin/enumerator.rs +271 -0
- package/src/origin/error.rs +19 -0
- package/src/origin/mock.rs +320 -0
- package/src/origin/mod.rs +19 -0
- package/src/origin/models.rs +167 -0
- package/src/origin/prober.rs +260 -0
- package/src/origin/remediation.rs +73 -0
- package/src/origin/report.rs +625 -0
- package/src/origin/scanner.rs +217 -0
- package/src/secrets/cli.rs +94 -0
- package/src/secrets/env_parser.rs +464 -0
- package/src/secrets/ignore.rs +139 -0
- package/src/secrets/mod.rs +14 -0
- package/src/secrets/report/json_format.rs +97 -0
- package/src/secrets/report/mod.rs +48 -0
- package/src/secrets/report/sarif.rs +225 -0
- package/src/secrets/report/text.rs +105 -0
- package/src/secrets/rules/builtin.rs +280 -0
- package/src/secrets/rules/entropy.rs +66 -0
- package/src/secrets/rules/mod.rs +7 -0
- package/src/secrets/rules/types.rs +183 -0
- package/src/secrets/scanner.rs +444 -0
- package/src/zone/cli.rs +111 -0
- package/src/zone/client/cf_client.rs +405 -0
- package/src/zone/client/mod.rs +5 -0
- package/src/zone/client/provider.rs +12 -0
- package/src/zone/mock_data.rs +481 -0
- package/src/zone/mod.rs +125 -0
- package/src/zone/models/audit.rs +194 -0
- package/src/zone/models/cloudflare.rs +252 -0
- package/src/zone/models/mod.rs +7 -0
- package/src/zone/models/sarif.rs +89 -0
- package/src/zone/reporters/html_rep.rs +345 -0
- package/src/zone/reporters/json_rep.rs +7 -0
- package/src/zone/reporters/mod.rs +9 -0
- package/src/zone/reporters/sarif_rep.rs +100 -0
- package/src/zone/reporters/terminal.rs +322 -0
- package/src/zone/rules/definitions.rs +201 -0
- package/src/zone/rules/evaluator.rs +484 -0
- package/src/zone/rules/mod.rs +5 -0
- package/src/zone/scoring.rs +104 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
use crate::bindings::ast_scanner::scan_source_file;
|
|
2
|
+
use crate::bindings::types::{BindingAccess, DeclaredBinding, ValidBindingInfo, ValidationReport};
|
|
3
|
+
use crate::bindings::wrangler::WranglerConfig;
|
|
4
|
+
use std::collections::{HashMap, HashSet};
|
|
5
|
+
use std::error::Error;
|
|
6
|
+
use std::path::PathBuf;
|
|
7
|
+
use walkdir::WalkDir;
|
|
8
|
+
|
|
9
|
+
/// Configuration options for the validator.
|
|
10
|
+
#[derive(Debug, Clone)]
|
|
11
|
+
pub struct ValidatorOptions {
|
|
12
|
+
pub target_paths: Vec<PathBuf>,
|
|
13
|
+
pub environment: Option<String>,
|
|
14
|
+
pub ignore_unused: HashSet<String>,
|
|
15
|
+
pub ignore_undeclared: HashSet<String>,
|
|
16
|
+
pub strict: bool,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl Default for ValidatorOptions {
|
|
20
|
+
fn default() -> Self {
|
|
21
|
+
Self {
|
|
22
|
+
target_paths: vec![PathBuf::from(".")],
|
|
23
|
+
environment: None,
|
|
24
|
+
ignore_unused: HashSet::new(),
|
|
25
|
+
ignore_undeclared: HashSet::new(),
|
|
26
|
+
strict: false,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Discovers all candidate source files in the given paths.
|
|
32
|
+
pub fn discover_source_files(paths: &[PathBuf]) -> Vec<PathBuf> {
|
|
33
|
+
let mut files = Vec::new();
|
|
34
|
+
let ignored_dirs = [
|
|
35
|
+
"node_modules",
|
|
36
|
+
".git",
|
|
37
|
+
"target",
|
|
38
|
+
"dist",
|
|
39
|
+
"build",
|
|
40
|
+
".wrangler",
|
|
41
|
+
".next",
|
|
42
|
+
".astro",
|
|
43
|
+
".cache",
|
|
44
|
+
"coverage",
|
|
45
|
+
".output",
|
|
46
|
+
"vendor",
|
|
47
|
+
".turbo",
|
|
48
|
+
".svelte-kit",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
let supported_exts = ["ts", "tsx", "js", "jsx", "mjs", "cjs", "astro"];
|
|
52
|
+
|
|
53
|
+
for p in paths {
|
|
54
|
+
if p.is_file() {
|
|
55
|
+
if let Some(ext) = p.extension().and_then(|e| e.to_str())
|
|
56
|
+
&& supported_exts.contains(&ext) {
|
|
57
|
+
files.push(p.clone());
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if p.is_dir() {
|
|
63
|
+
for entry in WalkDir::new(p)
|
|
64
|
+
.follow_links(false)
|
|
65
|
+
.into_iter()
|
|
66
|
+
.filter_entry(|e| {
|
|
67
|
+
let name = e.file_name().to_string_lossy();
|
|
68
|
+
if e.file_type().is_dir() {
|
|
69
|
+
!ignored_dirs.contains(&name.as_ref())
|
|
70
|
+
} else {
|
|
71
|
+
true
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.filter_map(|e| e.ok())
|
|
75
|
+
{
|
|
76
|
+
if entry.file_type().is_file() {
|
|
77
|
+
let path = entry.path();
|
|
78
|
+
if let Some(ext) = path.extension().and_then(|e| e.to_str())
|
|
79
|
+
&& supported_exts.contains(&ext) {
|
|
80
|
+
files.push(path.to_path_buf());
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
files.sort();
|
|
88
|
+
files
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Runs validation of code binding accesses against declared Wrangler bindings.
|
|
92
|
+
pub fn validate_project(
|
|
93
|
+
wrangler_config: Option<&WranglerConfig>,
|
|
94
|
+
options: &ValidatorOptions,
|
|
95
|
+
) -> Result<ValidationReport, Box<dyn Error + Send + Sync>> {
|
|
96
|
+
let env_name = options.environment.as_deref().unwrap_or("root");
|
|
97
|
+
|
|
98
|
+
// 1. Get declared bindings from Wrangler config
|
|
99
|
+
let declared_bindings = if let Some(cfg) = wrangler_config {
|
|
100
|
+
cfg.get_bindings_for_env(options.environment.as_deref())?
|
|
101
|
+
} else {
|
|
102
|
+
Vec::new()
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
let declared_map: HashMap<String, DeclaredBinding> = declared_bindings
|
|
106
|
+
.iter()
|
|
107
|
+
.map(|b| (b.name.clone(), b.clone()))
|
|
108
|
+
.collect();
|
|
109
|
+
|
|
110
|
+
// 2. Discover and scan source files
|
|
111
|
+
let source_files = discover_source_files(&options.target_paths);
|
|
112
|
+
let mut all_accesses = Vec::new();
|
|
113
|
+
|
|
114
|
+
for file_path in &source_files {
|
|
115
|
+
if let Ok(accesses) = scan_source_file(file_path) {
|
|
116
|
+
all_accesses.extend(accesses);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 3. Group accesses by binding name
|
|
121
|
+
let mut accesses_by_name: HashMap<String, Vec<BindingAccess>> = HashMap::new();
|
|
122
|
+
for access in all_accesses.clone() {
|
|
123
|
+
accesses_by_name
|
|
124
|
+
.entry(access.name.clone())
|
|
125
|
+
.or_default()
|
|
126
|
+
.push(access);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 4. Cross-reference
|
|
130
|
+
let mut undeclared_accesses = Vec::new();
|
|
131
|
+
let mut ghost_bindings = Vec::new();
|
|
132
|
+
let mut valid_bindings = Vec::new();
|
|
133
|
+
|
|
134
|
+
// Check accesses for undeclared bindings
|
|
135
|
+
for (name, accesses) in &accesses_by_name {
|
|
136
|
+
if options.ignore_undeclared.contains(name) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if !declared_map.contains_key(name) {
|
|
141
|
+
// All accesses to this undeclared name are errors
|
|
142
|
+
undeclared_accesses.extend(accesses.clone());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check declared bindings for unused (ghost) bindings
|
|
147
|
+
for declared in &declared_bindings {
|
|
148
|
+
if options.ignore_unused.contains(&declared.name) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if let Some(accesses) = accesses_by_name.get(&declared.name) {
|
|
153
|
+
valid_bindings.push(ValidBindingInfo {
|
|
154
|
+
binding: declared.clone(),
|
|
155
|
+
access_count: accesses.len(),
|
|
156
|
+
accesses: accesses.clone(),
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
// Declared but never accessed in any scanned source file
|
|
160
|
+
ghost_bindings.push(declared.clone());
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Sort for deterministic results
|
|
165
|
+
undeclared_accesses
|
|
166
|
+
.sort_by(|a, b| (&a.file_path, a.line, a.column).cmp(&(&b.file_path, b.line, b.column)));
|
|
167
|
+
ghost_bindings.sort_by(|a, b| a.name.cmp(&b.name));
|
|
168
|
+
valid_bindings.sort_by(|a, b| a.binding.name.cmp(&b.binding.name));
|
|
169
|
+
|
|
170
|
+
let is_success = if options.strict {
|
|
171
|
+
undeclared_accesses.is_empty() && ghost_bindings.is_empty()
|
|
172
|
+
} else {
|
|
173
|
+
undeclared_accesses.is_empty()
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
Ok(ValidationReport {
|
|
177
|
+
config_file: wrangler_config.map(|c| c.file_path.to_string_lossy().to_string()),
|
|
178
|
+
environment: env_name.to_string(),
|
|
179
|
+
total_files_scanned: source_files.len(),
|
|
180
|
+
total_declared: declared_bindings.len(),
|
|
181
|
+
total_accesses: all_accesses.len(),
|
|
182
|
+
undeclared_accesses,
|
|
183
|
+
ghost_bindings,
|
|
184
|
+
valid_bindings,
|
|
185
|
+
is_success,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[cfg(test)]
|
|
190
|
+
mod tests {
|
|
191
|
+
use super::*;
|
|
192
|
+
use crate::bindings::types::BindingType;
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn test_cross_referencing_logic() {
|
|
196
|
+
let root_bindings = vec![
|
|
197
|
+
DeclaredBinding {
|
|
198
|
+
name: "MY_KV".to_string(),
|
|
199
|
+
binding_type: BindingType::KvNamespace,
|
|
200
|
+
file: "wrangler.jsonc".to_string(),
|
|
201
|
+
environment: "root".to_string(),
|
|
202
|
+
details: None,
|
|
203
|
+
},
|
|
204
|
+
DeclaredBinding {
|
|
205
|
+
name: "UNUSED_GHOST_DB".to_string(),
|
|
206
|
+
binding_type: BindingType::D1Database,
|
|
207
|
+
file: "wrangler.jsonc".to_string(),
|
|
208
|
+
environment: "root".to_string(),
|
|
209
|
+
details: None,
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
let config = WranglerConfig {
|
|
214
|
+
file_path: PathBuf::from("wrangler.jsonc"),
|
|
215
|
+
root_bindings,
|
|
216
|
+
environments: std::collections::BTreeMap::new(),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
let opts = ValidatorOptions {
|
|
220
|
+
target_paths: vec![],
|
|
221
|
+
..Default::default()
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
let report = validate_project(Some(&config), &opts).unwrap();
|
|
225
|
+
assert_eq!(report.ghost_bindings.len(), 2);
|
|
226
|
+
}
|
|
227
|
+
}
|