seaworthycode 1.1.1 → 1.1.4
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/dist/index.js +269 -59
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
- package/rules/external/configuration/insecure-cookie-flags.test.yaml +114 -0
- package/rules/external/configuration/insecure-cookie-flags.yaml +77 -0
- package/rules/external/configuration/missing-ssl-verification.test.yaml +130 -0
- package/rules/external/configuration/missing-ssl-verification.yaml +123 -0
- package/rules/external/data-exposure/debug-artifacts.test.yaml +143 -0
- package/rules/external/data-exposure/debug-artifacts.yaml +57 -0
- package/rules/external/data-exposure/hardcoded-ip.test.yaml +131 -0
- package/rules/external/data-exposure/hardcoded-ip.yaml +67 -0
- package/rules/external/data-exposure/localstorage-sensitive-data.test.yaml +58 -0
- package/rules/external/data-exposure/localstorage-sensitive-data.yaml +60 -0
- package/rules/external/dependencies/deprecated-functions.test.yaml +92 -0
- package/rules/external/dependencies/deprecated-functions.yaml +66 -0
- package/rules/external/ops/hardcoded-port.test.yaml +141 -0
- package/rules/external/ops/hardcoded-port.yaml +91 -0
- package/rules/external/ops/insecure-file-permissions.test.yaml +111 -0
- package/rules/external/ops/insecure-file-permissions.yaml +91 -0
- package/rules/external/resilience/missing-memory-limit.test.yaml +54 -0
- package/rules/external/resilience/missing-memory-limit.yaml +59 -0
- package/rules/external/resilience/missing-timeout-config.test.yaml +179 -0
- package/rules/external/resilience/missing-timeout-config.yaml +124 -0
- package/rules/external/security/broken-crypto-algorithm.test.yaml +104 -0
- package/rules/external/security/broken-crypto-algorithm.yaml +79 -0
- package/rules/external/security/http-method-override.test.yaml +40 -0
- package/rules/external/security/http-method-override.yaml +35 -0
- package/rules/external/security/http-no-tls.test.yaml +115 -0
- package/rules/external/security/http-no-tls.yaml +59 -0
- package/rules/external/security/path-traversal.test.yaml +188 -0
- package/rules/external/security/path-traversal.yaml +210 -0
- package/rules/external/security/unsafe-innerhtml.test.yaml +72 -0
- package/rules/external/security/unsafe-innerhtml.yaml +46 -0
- package/rules/external/security/untrusted-deserialization.test.yaml +79 -0
- package/rules/external/security/untrusted-deserialization.yaml +48 -0
- package/rules/external/security/weak-encryption-mode.test.yaml +126 -0
- package/rules/external/security/weak-encryption-mode.yaml +104 -0
- package/rules/external/security/weak-hash-algorithm.test.yaml +121 -0
- package/rules/external/security/weak-hash-algorithm.yaml +87 -0
- package/rules/external/security/xxe-parsing.test.yaml +76 -0
- package/rules/external/security/xxe-parsing.yaml +63 -0
- package/rules/internal/security/dangerous-eval.test.yaml +126 -0
- package/rules/internal/security/dangerous-eval.yaml +134 -0
- package/rules/internal/security/no-document-write.test.yaml +33 -0
- package/rules/internal/security/no-document-write.yaml +40 -0
- package/rules/internal/taint/MATRIX.md +16 -0
- package/rules/internal/taint/bash.test.yaml +84 -0
- package/rules/internal/taint/bash.yaml +166 -0
- package/rules/internal/taint/dvwa.test.yaml +217 -0
- package/rules/internal/taint/go.test.yaml +207 -0
- package/rules/internal/taint/go.yaml +325 -0
- package/rules/internal/taint/java.test.yaml +158 -0
- package/rules/internal/taint/java.yaml +318 -0
- package/rules/internal/taint/javascript.test.yaml +374 -0
- package/rules/internal/taint/javascript.yaml +439 -0
- package/rules/internal/taint/php.test.yaml +134 -0
- package/rules/internal/taint/php.yaml +331 -0
- package/rules/internal/taint/python.test.yaml +427 -0
- package/rules/internal/taint/python.yaml +636 -0
- package/rules/internal/taint/ruby.test.yaml +148 -0
- package/rules/internal/taint/ruby.yaml +233 -0
- package/rules/internal/taint/rust.test.yaml +585 -0
- package/rules/internal/taint/rust.yaml +483 -0
- package/rules/internal/taint/tsx.test.yaml +140 -0
- package/rules/internal/taint/tsx.yaml +51 -0
- package/rules/internal/taint/typescript.test.yaml +393 -0
- package/rules/internal/taint/typescript.yaml +660 -0
- package/wasm/tree-sitter-sql.wasm +0 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
language: rust
|
|
2
|
+
cases:
|
|
3
|
+
# === POSITIVE FLOW TESTS ===
|
|
4
|
+
|
|
5
|
+
- name: "Axum Json to sqlx::query via format! (SQL injection)"
|
|
6
|
+
language: rust
|
|
7
|
+
code: |
|
|
8
|
+
async fn handler(Json(payload): Json<UserPayload>) {
|
|
9
|
+
let query_str = format!("SELECT * FROM users WHERE name = '{}'", payload.name);
|
|
10
|
+
sqlx::query(&query_str).execute(&pool).await;
|
|
11
|
+
}
|
|
12
|
+
expect:
|
|
13
|
+
count: 2
|
|
14
|
+
flows:
|
|
15
|
+
- sourceId: axum.extract.Json
|
|
16
|
+
sinkId: sqlx.query
|
|
17
|
+
- sourceId: axum.extract.Json
|
|
18
|
+
sinkId: format.sql-sqli
|
|
19
|
+
|
|
20
|
+
- name: "Axum Query to diesel::sql_query (SQL injection)"
|
|
21
|
+
language: rust
|
|
22
|
+
code: |
|
|
23
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
24
|
+
let sql = format!("SELECT * FROM items WHERE query = '{}'", params.q);
|
|
25
|
+
diesel::sql_query(sql).execute(&conn);
|
|
26
|
+
}
|
|
27
|
+
expect:
|
|
28
|
+
count: 2
|
|
29
|
+
flows:
|
|
30
|
+
- sourceId: axum.extract.Query
|
|
31
|
+
sinkId: diesel.sql_query
|
|
32
|
+
- sourceId: axum.extract.Query
|
|
33
|
+
sinkId: format.sql-sqli
|
|
34
|
+
|
|
35
|
+
- name: "SQL injection via format! SQL pattern"
|
|
36
|
+
language: rust
|
|
37
|
+
code: |
|
|
38
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
39
|
+
let query = format!("SELECT * FROM items WHERE query = '{}'", params.q);
|
|
40
|
+
}
|
|
41
|
+
expect:
|
|
42
|
+
count: 1
|
|
43
|
+
flows:
|
|
44
|
+
- sourceId: axum.extract.Query
|
|
45
|
+
sinkId: format.sql-sqli
|
|
46
|
+
|
|
47
|
+
- name: "Command injection — env var to Command::new"
|
|
48
|
+
language: rust
|
|
49
|
+
code: |
|
|
50
|
+
fn run() {
|
|
51
|
+
let target = std::env::var("DEPLOY_TARGET").unwrap();
|
|
52
|
+
std::process::Command::new(target).spawn().unwrap();
|
|
53
|
+
}
|
|
54
|
+
expect:
|
|
55
|
+
count: 2
|
|
56
|
+
flows:
|
|
57
|
+
- sourceId: std.env.var
|
|
58
|
+
sinkId: std.process.Command.new
|
|
59
|
+
- sourceId: std.env.var
|
|
60
|
+
sinkId: std.process.Command.code-exec
|
|
61
|
+
|
|
62
|
+
- name: "Command injection — CLI args to Command::new"
|
|
63
|
+
language: rust
|
|
64
|
+
code: |
|
|
65
|
+
fn main() {
|
|
66
|
+
let arg = std::env::args().nth(1).unwrap();
|
|
67
|
+
std::process::Command::new(arg).output().unwrap();
|
|
68
|
+
}
|
|
69
|
+
expect:
|
|
70
|
+
count: 2
|
|
71
|
+
flows:
|
|
72
|
+
- sourceId: std.env.args
|
|
73
|
+
sinkId: std.process.Command.new
|
|
74
|
+
- sourceId: std.env.args
|
|
75
|
+
sinkId: std.process.Command.code-exec
|
|
76
|
+
|
|
77
|
+
- name: "Path traversal — Axum Query to fs::File::open"
|
|
78
|
+
language: rust
|
|
79
|
+
code: |
|
|
80
|
+
async fn get_file(Query(params): Query<FileParams>) {
|
|
81
|
+
std::fs::File::open(params.filename).unwrap();
|
|
82
|
+
}
|
|
83
|
+
expect:
|
|
84
|
+
count: 1
|
|
85
|
+
flows:
|
|
86
|
+
- sourceId: axum.extract.Query
|
|
87
|
+
sinkId: std.fs.File.open
|
|
88
|
+
|
|
89
|
+
- name: "Path traversal — env var to fs::read_to_string"
|
|
90
|
+
language: rust
|
|
91
|
+
code: |
|
|
92
|
+
fn main() {
|
|
93
|
+
let path = std::env::var("CONFIG_PATH").unwrap();
|
|
94
|
+
std::fs::read_to_string(path).unwrap();
|
|
95
|
+
}
|
|
96
|
+
expect:
|
|
97
|
+
count: 1
|
|
98
|
+
flows:
|
|
99
|
+
- sourceId: std.env.var
|
|
100
|
+
sinkId: std.fs.File.open
|
|
101
|
+
|
|
102
|
+
- name: "XSS — Axum Json to Html response"
|
|
103
|
+
language: rust
|
|
104
|
+
code: |
|
|
105
|
+
async fn render(Json(body): Json<TemplateBody>) {
|
|
106
|
+
axum::response::Html(body.content);
|
|
107
|
+
}
|
|
108
|
+
expect:
|
|
109
|
+
count: 1
|
|
110
|
+
flows:
|
|
111
|
+
- sourceId: axum.extract.Json
|
|
112
|
+
sinkId: axum.response.Html
|
|
113
|
+
|
|
114
|
+
- name: "XSS — Actix Json to HttpResponse::body"
|
|
115
|
+
language: rust
|
|
116
|
+
code: |
|
|
117
|
+
async fn render(body: actix_web::web::Json<TemplateBody>) {
|
|
118
|
+
HttpResponse::Ok().body(body.content);
|
|
119
|
+
}
|
|
120
|
+
expect:
|
|
121
|
+
count: 1
|
|
122
|
+
flows:
|
|
123
|
+
- sourceId: actix.web.Json
|
|
124
|
+
sinkId: actix.HttpResponse.body
|
|
125
|
+
|
|
126
|
+
- name: "URL injection — Axum Query to Redirect"
|
|
127
|
+
language: rust
|
|
128
|
+
code: |
|
|
129
|
+
async fn handle(Query(params): Query<RedirectParams>) {
|
|
130
|
+
axum::response::Redirect::to(¶ms.next);
|
|
131
|
+
}
|
|
132
|
+
expect:
|
|
133
|
+
count: 1
|
|
134
|
+
flows:
|
|
135
|
+
- sourceId: axum.extract.Query
|
|
136
|
+
sinkId: axum.response.Redirect
|
|
137
|
+
|
|
138
|
+
- name: "URL injection — Actix Query to redirect"
|
|
139
|
+
language: rust
|
|
140
|
+
code: |
|
|
141
|
+
async fn handle(params: actix_web::web::Query<RedirectParams>) {
|
|
142
|
+
HttpResponse::Found().insert_header(("Location", params.next.clone()));
|
|
143
|
+
}
|
|
144
|
+
expect:
|
|
145
|
+
count: 1
|
|
146
|
+
flows:
|
|
147
|
+
- sourceId: actix.web.Query
|
|
148
|
+
sinkId: actix.HttpResponse.redirect
|
|
149
|
+
|
|
150
|
+
- name: "Code execution — env var to libloading"
|
|
151
|
+
language: rust
|
|
152
|
+
code: |
|
|
153
|
+
fn load() {
|
|
154
|
+
let path = std::env::var("LIB_PATH").unwrap();
|
|
155
|
+
unsafe { libloading::Library::new(path).unwrap() };
|
|
156
|
+
}
|
|
157
|
+
expect:
|
|
158
|
+
count: 1
|
|
159
|
+
flows:
|
|
160
|
+
- sourceId: std.env.var
|
|
161
|
+
sinkId: libloading.Library.new
|
|
162
|
+
|
|
163
|
+
- name: "Network response to SQL injection"
|
|
164
|
+
language: rust
|
|
165
|
+
code: |
|
|
166
|
+
async fn fetch() {
|
|
167
|
+
let res = reqwest::get("http://example.com").await.unwrap().text().await.unwrap();
|
|
168
|
+
sqlx::query(&res).execute(&pool).await;
|
|
169
|
+
}
|
|
170
|
+
expect:
|
|
171
|
+
count: 1
|
|
172
|
+
flows:
|
|
173
|
+
- sourceId: reqwest.response.text
|
|
174
|
+
sinkId: sqlx.query
|
|
175
|
+
|
|
176
|
+
- name: "Network response to command injection"
|
|
177
|
+
language: rust
|
|
178
|
+
code: |
|
|
179
|
+
async fn run() {
|
|
180
|
+
let res = reqwest::get("http://example.com").await.unwrap().text().await.unwrap();
|
|
181
|
+
std::process::Command::new(res).spawn().unwrap();
|
|
182
|
+
}
|
|
183
|
+
expect:
|
|
184
|
+
count: 2
|
|
185
|
+
flows:
|
|
186
|
+
- sourceId: reqwest.response.text
|
|
187
|
+
sinkId: std.process.Command.new
|
|
188
|
+
- sourceId: reqwest.response.text
|
|
189
|
+
sinkId: std.process.Command.code-exec
|
|
190
|
+
|
|
191
|
+
- name: "Cookie to SQL injection"
|
|
192
|
+
language: rust
|
|
193
|
+
code: |
|
|
194
|
+
async fn handle(cookie: axum::extract::CookieJar) {
|
|
195
|
+
let val = cookie.get("session").unwrap().value();
|
|
196
|
+
sqlx::query(val).execute(&pool).await;
|
|
197
|
+
}
|
|
198
|
+
expect:
|
|
199
|
+
count: 1
|
|
200
|
+
flows:
|
|
201
|
+
- sourceId: axum.extract.CookieJar
|
|
202
|
+
sinkId: sqlx.query
|
|
203
|
+
|
|
204
|
+
- name: "Actix Form to path traversal"
|
|
205
|
+
language: rust
|
|
206
|
+
code: |
|
|
207
|
+
async fn handle(form: actix_web::web::Form<UploadForm>) {
|
|
208
|
+
std::fs::File::open(&form.path).unwrap();
|
|
209
|
+
}
|
|
210
|
+
expect:
|
|
211
|
+
count: 1
|
|
212
|
+
flows:
|
|
213
|
+
- sourceId: actix.web.Form
|
|
214
|
+
sinkId: std.fs.File.open
|
|
215
|
+
|
|
216
|
+
- name: "stdin to command injection"
|
|
217
|
+
language: rust
|
|
218
|
+
code: |
|
|
219
|
+
fn main() {
|
|
220
|
+
let mut input = String::new();
|
|
221
|
+
std::io::stdin().read_line(&mut input).unwrap();
|
|
222
|
+
std::process::Command::new(input).spawn().unwrap();
|
|
223
|
+
}
|
|
224
|
+
expect:
|
|
225
|
+
count: 2
|
|
226
|
+
flows:
|
|
227
|
+
- sourceId: std.io.stdin
|
|
228
|
+
sinkId: std.process.Command.new
|
|
229
|
+
- sourceId: std.io.stdin
|
|
230
|
+
sinkId: std.process.Command.code-exec
|
|
231
|
+
|
|
232
|
+
# === SANITISED PATH TESTS ===
|
|
233
|
+
|
|
234
|
+
- name: "SQL injection sanitised by parameterised query"
|
|
235
|
+
language: rust
|
|
236
|
+
code: |
|
|
237
|
+
async fn handler(Json(payload): Json<UserPayload>) {
|
|
238
|
+
sqlx::query("SELECT * FROM users WHERE name = ?").bind(payload.name).execute(&pool).await;
|
|
239
|
+
}
|
|
240
|
+
expect:
|
|
241
|
+
count: 0
|
|
242
|
+
|
|
243
|
+
- name: "SQL injection sanitised by parse::<i32>()"
|
|
244
|
+
language: rust
|
|
245
|
+
code: |
|
|
246
|
+
async fn handler(Query(params): Query<UserParams>) {
|
|
247
|
+
let id = params.id.parse::<i32>().unwrap();
|
|
248
|
+
let sql = format!("SELECT * FROM users WHERE id = {}", id);
|
|
249
|
+
sqlx::query(&sql).execute(&pool).await;
|
|
250
|
+
}
|
|
251
|
+
expect:
|
|
252
|
+
count: 0
|
|
253
|
+
|
|
254
|
+
- name: "XSS sanitised by html_escape::encode_safe"
|
|
255
|
+
language: rust
|
|
256
|
+
code: |
|
|
257
|
+
async fn render(Json(body): Json<TemplateBody>) {
|
|
258
|
+
let escaped = html_escape::encode_safe(&body.content);
|
|
259
|
+
axum::response::Html(escaped);
|
|
260
|
+
}
|
|
261
|
+
expect:
|
|
262
|
+
count: 0
|
|
263
|
+
|
|
264
|
+
- name: "Path traversal sanitised by Path::canonicalize"
|
|
265
|
+
language: rust
|
|
266
|
+
code: |
|
|
267
|
+
async fn get_file(Query(params): Query<FileParams>) {
|
|
268
|
+
let path = std::path::Path::new(¶ms.filename).canonicalize().unwrap();
|
|
269
|
+
std::fs::File::open(path).unwrap();
|
|
270
|
+
}
|
|
271
|
+
expect:
|
|
272
|
+
count: 0
|
|
273
|
+
|
|
274
|
+
- name: "Command injection sanitised by shell_words::join"
|
|
275
|
+
language: rust
|
|
276
|
+
code: |
|
|
277
|
+
fn run() {
|
|
278
|
+
let arg = std::env::var("CMD_ARG").unwrap();
|
|
279
|
+
let safe_arg = shell_words::join(vec![arg]);
|
|
280
|
+
std::process::Command::new("ls").arg(safe_arg).spawn().unwrap();
|
|
281
|
+
}
|
|
282
|
+
expect:
|
|
283
|
+
count: 0
|
|
284
|
+
|
|
285
|
+
- name: "URL injection sanitised by url::Url::parse"
|
|
286
|
+
language: rust
|
|
287
|
+
code: |
|
|
288
|
+
async fn handle(Query(params): Query<RedirectParams>) {
|
|
289
|
+
let safe_url = url::Url::parse(¶ms.next).unwrap();
|
|
290
|
+
axum::response::Redirect::to(safe_url.as_str());
|
|
291
|
+
}
|
|
292
|
+
expect:
|
|
293
|
+
count: 0
|
|
294
|
+
|
|
295
|
+
# === SANITISER CROSS-CLASS TESTS ===
|
|
296
|
+
|
|
297
|
+
- name: "html_escape does not cover sql-injection"
|
|
298
|
+
language: rust
|
|
299
|
+
code: |
|
|
300
|
+
async fn render(Json(body): Json<TemplateBody>) {
|
|
301
|
+
let escaped = html_escape::encode_safe(&body.content);
|
|
302
|
+
sqlx::query(&escaped).execute(&pool).await;
|
|
303
|
+
}
|
|
304
|
+
expect:
|
|
305
|
+
count: 1
|
|
306
|
+
flows:
|
|
307
|
+
- sourceId: axum.extract.Json
|
|
308
|
+
sinkId: sqlx.query
|
|
309
|
+
|
|
310
|
+
- name: "parse::<i32>() then to_string() — taint engine still tracks flow"
|
|
311
|
+
language: rust
|
|
312
|
+
code: |
|
|
313
|
+
async fn handler(Query(params): Query<UserParams>) {
|
|
314
|
+
let id = params.id.parse::<i32>().unwrap().to_string();
|
|
315
|
+
axum::response::Html(id);
|
|
316
|
+
}
|
|
317
|
+
expect:
|
|
318
|
+
count: 1
|
|
319
|
+
flows:
|
|
320
|
+
- sourceId: axum.extract.Query
|
|
321
|
+
sinkId: axum.response.Html
|
|
322
|
+
|
|
323
|
+
# === BFS PROPAGATION TESTS ===
|
|
324
|
+
|
|
325
|
+
- name: "format! interpolation propagates taint to sink"
|
|
326
|
+
language: rust
|
|
327
|
+
code: |
|
|
328
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
329
|
+
let query = format!("{}", params.q);
|
|
330
|
+
sqlx::query(&query).execute(&pool).await;
|
|
331
|
+
}
|
|
332
|
+
expect:
|
|
333
|
+
count: 1
|
|
334
|
+
flows:
|
|
335
|
+
- sourceId: axum.extract.Query
|
|
336
|
+
sinkId: sqlx.query
|
|
337
|
+
|
|
338
|
+
- name: "String concatenation (+) propagates taint"
|
|
339
|
+
language: rust
|
|
340
|
+
code: |
|
|
341
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
342
|
+
let query = "SELECT * FROM items WHERE query = ".to_string() + ¶ms.q;
|
|
343
|
+
sqlx::query(&query).execute(&pool).await;
|
|
344
|
+
}
|
|
345
|
+
expect:
|
|
346
|
+
count: 1
|
|
347
|
+
flows:
|
|
348
|
+
- sourceId: axum.extract.Query
|
|
349
|
+
sinkId: sqlx.query
|
|
350
|
+
|
|
351
|
+
- name: "let binding propagation"
|
|
352
|
+
language: rust
|
|
353
|
+
code: |
|
|
354
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
355
|
+
let tainted = params.q;
|
|
356
|
+
sqlx::query(&tainted).execute(&pool).await;
|
|
357
|
+
}
|
|
358
|
+
expect:
|
|
359
|
+
count: 1
|
|
360
|
+
flows:
|
|
361
|
+
- sourceId: axum.extract.Query
|
|
362
|
+
sinkId: sqlx.query
|
|
363
|
+
|
|
364
|
+
- name: "Struct destructuring propagation"
|
|
365
|
+
language: rust
|
|
366
|
+
code: |
|
|
367
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
368
|
+
let SearchParams { q: query_val } = params;
|
|
369
|
+
sqlx::query(&query_val).execute(&pool).await;
|
|
370
|
+
}
|
|
371
|
+
expect:
|
|
372
|
+
count: 1
|
|
373
|
+
flows:
|
|
374
|
+
- sourceId: axum.extract.Query
|
|
375
|
+
sinkId: sqlx.query
|
|
376
|
+
|
|
377
|
+
- name: "match arm merging — taint in one arm reaches sink"
|
|
378
|
+
language: rust
|
|
379
|
+
code: |
|
|
380
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
381
|
+
let query = match Some(¶ms.q) {
|
|
382
|
+
Some(val) => val.clone(),
|
|
383
|
+
None => "safe".to_string()
|
|
384
|
+
};
|
|
385
|
+
sqlx::query(&query).execute(&pool).await;
|
|
386
|
+
}
|
|
387
|
+
expect:
|
|
388
|
+
count: 1
|
|
389
|
+
flows:
|
|
390
|
+
- sourceId: axum.extract.Query
|
|
391
|
+
sinkId: sqlx.query
|
|
392
|
+
|
|
393
|
+
- name: "if let branch merging"
|
|
394
|
+
language: rust
|
|
395
|
+
code: |
|
|
396
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
397
|
+
let mut query = String::new();
|
|
398
|
+
if let Some(val) = Some(¶ms.q) {
|
|
399
|
+
query = val.clone();
|
|
400
|
+
} else {
|
|
401
|
+
query = "safe".to_string();
|
|
402
|
+
}
|
|
403
|
+
sqlx::query(&query).execute(&pool).await;
|
|
404
|
+
}
|
|
405
|
+
expect:
|
|
406
|
+
count: 1
|
|
407
|
+
flows:
|
|
408
|
+
- sourceId: axum.extract.Query
|
|
409
|
+
sinkId: sqlx.query
|
|
410
|
+
|
|
411
|
+
- name: "if/else branch merging"
|
|
412
|
+
language: rust
|
|
413
|
+
code: |
|
|
414
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
415
|
+
let mut query = String::new();
|
|
416
|
+
if true {
|
|
417
|
+
query = params.q.clone();
|
|
418
|
+
} else {
|
|
419
|
+
query = "safe".to_string();
|
|
420
|
+
}
|
|
421
|
+
sqlx::query(&query).execute(&pool).await;
|
|
422
|
+
}
|
|
423
|
+
expect:
|
|
424
|
+
count: 1
|
|
425
|
+
flows:
|
|
426
|
+
- sourceId: axum.extract.Query
|
|
427
|
+
sinkId: sqlx.query
|
|
428
|
+
|
|
429
|
+
- name: "for loop fixpoint iteration"
|
|
430
|
+
language: rust
|
|
431
|
+
code: |
|
|
432
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
433
|
+
let mut query = String::new();
|
|
434
|
+
for item in vec![params.q] {
|
|
435
|
+
query = item;
|
|
436
|
+
}
|
|
437
|
+
sqlx::query(&query).execute(&pool).await;
|
|
438
|
+
}
|
|
439
|
+
expect:
|
|
440
|
+
count: 1
|
|
441
|
+
flows:
|
|
442
|
+
- sourceId: axum.extract.Query
|
|
443
|
+
sinkId: sqlx.query
|
|
444
|
+
|
|
445
|
+
- name: "Reference propagation (&tainted)"
|
|
446
|
+
language: rust
|
|
447
|
+
code: |
|
|
448
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
449
|
+
let ref_val = ¶ms.q;
|
|
450
|
+
sqlx::query(ref_val).execute(&pool).await;
|
|
451
|
+
}
|
|
452
|
+
expect:
|
|
453
|
+
count: 1
|
|
454
|
+
flows:
|
|
455
|
+
- sourceId: axum.extract.Query
|
|
456
|
+
sinkId: sqlx.query
|
|
457
|
+
|
|
458
|
+
- name: "Dereference propagation (*tainted)"
|
|
459
|
+
language: rust
|
|
460
|
+
code: |
|
|
461
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
462
|
+
let ref_val = ¶ms.q;
|
|
463
|
+
let deref_val = *ref_val;
|
|
464
|
+
sqlx::query(deref_val).execute(&pool).await;
|
|
465
|
+
}
|
|
466
|
+
expect:
|
|
467
|
+
count: 1
|
|
468
|
+
flows:
|
|
469
|
+
- sourceId: axum.extract.Query
|
|
470
|
+
sinkId: sqlx.query
|
|
471
|
+
|
|
472
|
+
- name: "Type cast propagation (as)"
|
|
473
|
+
language: rust
|
|
474
|
+
code: |
|
|
475
|
+
fn main() {
|
|
476
|
+
let path = std::env::var("LIB_PATH").unwrap();
|
|
477
|
+
let cast_val = path as *const u8;
|
|
478
|
+
unsafe { libloading::Library::new(cast_val).unwrap() };
|
|
479
|
+
}
|
|
480
|
+
expect:
|
|
481
|
+
count: 1
|
|
482
|
+
flows:
|
|
483
|
+
- sourceId: std.env.var
|
|
484
|
+
sinkId: libloading.Library.new
|
|
485
|
+
|
|
486
|
+
- name: "Closure parameter propagation"
|
|
487
|
+
language: rust
|
|
488
|
+
code: |
|
|
489
|
+
async fn handler(Query(params): Query<SearchParams>) {
|
|
490
|
+
let closure = |x| {
|
|
491
|
+
sqlx::query(x).execute(&pool);
|
|
492
|
+
};
|
|
493
|
+
closure(¶ms.q);
|
|
494
|
+
}
|
|
495
|
+
expect:
|
|
496
|
+
count: 1
|
|
497
|
+
flows:
|
|
498
|
+
- sourceId: axum.extract.Query
|
|
499
|
+
sinkId: sqlx.query
|
|
500
|
+
|
|
501
|
+
- name: "Mutator method — vec.push(tainted)"
|
|
502
|
+
language: rust
|
|
503
|
+
code: |
|
|
504
|
+
fn run() {
|
|
505
|
+
let mut args = vec![];
|
|
506
|
+
let tainted = std::env::var("CMD_ARG").unwrap();
|
|
507
|
+
args.push(tainted);
|
|
508
|
+
std::process::Command::new("ls").arg(&args[0]).spawn().unwrap();
|
|
509
|
+
}
|
|
510
|
+
expect:
|
|
511
|
+
count: 1
|
|
512
|
+
flows:
|
|
513
|
+
- sourceId: std.env.var
|
|
514
|
+
sinkId: std.process.Command.arg
|
|
515
|
+
|
|
516
|
+
- name: "Inter-function propagation"
|
|
517
|
+
language: rust
|
|
518
|
+
code: |
|
|
519
|
+
fn helper(x: String) -> String {
|
|
520
|
+
x
|
|
521
|
+
}
|
|
522
|
+
fn main() {
|
|
523
|
+
let tainted = std::env::var("CMD").unwrap();
|
|
524
|
+
let propagated = helper(tainted);
|
|
525
|
+
std::process::Command::new(propagated).spawn().unwrap();
|
|
526
|
+
}
|
|
527
|
+
expect:
|
|
528
|
+
count: 2
|
|
529
|
+
flows:
|
|
530
|
+
- sourceId: std.env.var
|
|
531
|
+
sinkId: std.process.Command.new
|
|
532
|
+
- sourceId: std.env.var
|
|
533
|
+
sinkId: std.process.Command.code-exec
|
|
534
|
+
|
|
535
|
+
- name: "Attribute assignment — obj.field = tainted"
|
|
536
|
+
language: rust
|
|
537
|
+
code: |
|
|
538
|
+
struct Config {
|
|
539
|
+
path: String,
|
|
540
|
+
}
|
|
541
|
+
fn main() {
|
|
542
|
+
let mut cfg = Config { path: "safe".to_string() };
|
|
543
|
+
let tainted = std::env::var("PATH").unwrap();
|
|
544
|
+
cfg.path = tainted;
|
|
545
|
+
std::fs::File::open(cfg.path).unwrap();
|
|
546
|
+
}
|
|
547
|
+
expect:
|
|
548
|
+
count: 1
|
|
549
|
+
flows:
|
|
550
|
+
- sourceId: std.env.var
|
|
551
|
+
sinkId: std.fs.File.open
|
|
552
|
+
|
|
553
|
+
- name: "Subscript assignment — map[key] = tainted"
|
|
554
|
+
language: rust
|
|
555
|
+
code: |
|
|
556
|
+
use std::collections::HashMap;
|
|
557
|
+
fn main() {
|
|
558
|
+
let mut map: HashMap<String, String> = HashMap::new();
|
|
559
|
+
let tainted = std::env::var("KEY").unwrap();
|
|
560
|
+
map.insert("key".to_string(), tainted);
|
|
561
|
+
}
|
|
562
|
+
expect:
|
|
563
|
+
count: 0
|
|
564
|
+
|
|
565
|
+
# === NO-SOURCE-MATCH AND ANTI-FIXTURE TESTS ===
|
|
566
|
+
|
|
567
|
+
- name: "No source match — plain code"
|
|
568
|
+
language: rust
|
|
569
|
+
code: |
|
|
570
|
+
fn main() {
|
|
571
|
+
let path = "/etc/passwd";
|
|
572
|
+
std::fs::File::open(path).unwrap();
|
|
573
|
+
}
|
|
574
|
+
expect:
|
|
575
|
+
count: 0
|
|
576
|
+
|
|
577
|
+
- name: "Safe anti-fixture"
|
|
578
|
+
language: rust
|
|
579
|
+
code: |
|
|
580
|
+
async fn handler(Query(params): Query<UserParams>) {
|
|
581
|
+
let id = params.id.parse::<i32>().unwrap();
|
|
582
|
+
sqlx::query("SELECT * FROM users WHERE id = ?").bind(id).execute(&pool).await;
|
|
583
|
+
}
|
|
584
|
+
expect:
|
|
585
|
+
count: 0
|