thuban 0.4.0 → 0.4.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.
- package/dist/package.json +63 -0
- package/dist/packages/crucible/rules/code-scanner-core.json +129 -0
- package/dist/packages/crucible/rules/command-injection.json +411 -0
- package/dist/packages/crucible/rules/crypto-misuse.json +35 -0
- package/dist/packages/crucible/rules/deserialization.json +152 -0
- package/dist/packages/crucible/rules/env-injection.json +46 -0
- package/dist/packages/crucible/rules/eval-abuse.json +104 -0
- package/dist/packages/crucible/rules/file-upload.json +46 -0
- package/dist/packages/crucible/rules/hallucination.json +22 -0
- package/dist/packages/crucible/rules/hardcoded-secret.json +397 -0
- package/dist/packages/crucible/rules/hardcoded-url.json +13 -0
- package/dist/packages/crucible/rules/insecure-crypto.json +302 -0
- package/dist/packages/crucible/rules/integer-overflow.json +35 -0
- package/dist/packages/crucible/rules/log-injection.json +33 -0
- package/dist/packages/crucible/rules/mass-assignment.json +112 -0
- package/dist/packages/crucible/rules/open-redirect.json +196 -0
- package/dist/packages/crucible/rules/path-traversal.json +422 -0
- package/dist/packages/crucible/rules/prototype-pollution.json +22 -0
- package/dist/packages/crucible/rules/sql-injection.json +597 -0
- package/dist/packages/crucible/rules/ssrf.json +557 -0
- package/dist/packages/crucible/rules/timing-attack.json +55 -0
- package/dist/packages/crucible/rules/unsafe-block.json +24 -0
- package/dist/packages/crucible/rules/xss.json +641 -0
- package/dist/packages/crucible/rules/xxe.json +66 -0
- package/dist/packages/crucible/rules/yaml-deserialization.json +22 -0
- package/dist/packages/crucible/rules/yaml-injection.json +22 -0
- package/dist/packages/scanner/executive-report.js +1 -1
- package/dist/packages/scanner/hallucination-detector.js +1 -1
- package/dist/packages/scanner/secret-scanner.js +1 -1
- package/package.json +7 -4
- package/dist/packages/crucible/seeds/python/seed-021 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-022 3.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-023 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-024 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-025 3.py +0 -40
- package/dist/packages/crucible/seeds/python/seed-026 3.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-027 3.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-028 3.py +0 -42
- package/dist/packages/crucible/seeds/python/seed-030 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-031 3.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-036 3.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-037 3.py +0 -41
- package/dist/packages/crucible/seeds/python/seed-038 3.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-039 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-040 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-041 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-042 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-043 3.py +0 -32
- package/dist/packages/crucible/seeds/python/seed-044 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-045 3.py +0 -36
- package/dist/packages/crucible/seeds/python/seed-046 3.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-047 3.py +0 -44
- package/dist/packages/crucible/seeds/python/seed-048 3.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-049 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-050 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-051 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-052 3.py +0 -41
- package/dist/packages/crucible/seeds/ruby/seed-001 3.rb +0 -15
- package/dist/packages/crucible/seeds/ruby/seed-002 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-003 3.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-004 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-005 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-006 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-007 3.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-008 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-009 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-010 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-011 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-012 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-013 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-014 3.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-015 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-016 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-017 3.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-018 3.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-019 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-020 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-021 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-022 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-023 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-024 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-025 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-026 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-027 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-028 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-029 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-030 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-031 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-032 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-033 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-034 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-035 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-036 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-037 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-038 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-039 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-040 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-041 3.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-042 3.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-043 3.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-044 3.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-045 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-046 3.rb +0 -27
- package/dist/packages/crucible/seeds/ruby/seed-047 3.rb +0 -26
- package/dist/packages/crucible/seeds/ruby/seed-048 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-049 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-050 3.rb +0 -27
- package/dist/packages/crucible/seeds/rust/seed-001 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-002 3.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-003 3.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-004 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-005 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-006 3.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-007 3.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-008 3.rs +0 -19
- package/dist/packages/crucible/seeds/rust/seed-009 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-010 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-011 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-012 3.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-013 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-014 3.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-015 3.rs +0 -33
- package/dist/packages/crucible/seeds/rust/seed-016 3.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-017 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-018 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-019 3.rs +0 -36
- package/dist/packages/crucible/seeds/rust/seed-020 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-021 3.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-022 3.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-023 3.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-024 3.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-025 3.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-026 3.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-027 3.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-028 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-029 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-030 3.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-031 3.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-032 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-033 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-034 3.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-035 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-036 3.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-037 3.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-038 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-039 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-040 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-041 3.rs +0 -32
- package/dist/packages/crucible/seeds/rust/seed-042 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-043 3.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-044 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-045 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-046 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-047 3.rs +0 -34
- package/dist/packages/crucible/seeds/rust/seed-048 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-049 3.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-050 3.rs +0 -23
- package/dist/packages/crucible/seeds/ts/seed-001 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-002 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-003 3.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-004 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-005 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-006 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-007 3.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-008 3.ts +0 -40
- package/dist/packages/crucible/seeds/ts/seed-009 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-010 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-011 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-012 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-013 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-014 3.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-015 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-016 3.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-017 3.ts +0 -44
- package/dist/packages/crucible/seeds/ts/seed-018 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-019 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-020 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-021 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-022 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-023 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-024 3.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-025 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-026 3.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-027 3.ts +0 -30
- package/dist/packages/crucible/seeds/ts/seed-028 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-029 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-030 3.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-031 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-032 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-033 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-034 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-035 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-036 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-037 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-038 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-039 3.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-040 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-041 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-042 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-043 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-044 3.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-045 3.ts +0 -39
- package/dist/packages/crucible/seeds/ts/seed-046 3.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-047 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-048 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-049 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-050 3.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-051 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-052 3.ts +0 -36
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: crypto_misuse
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: rust
|
|
5
|
-
// expected: RC4 stream cipher implementation — broken cipher, insecure
|
|
6
|
-
|
|
7
|
-
pub struct Rc4 {
|
|
8
|
-
s: [u8; 256],
|
|
9
|
-
i: usize,
|
|
10
|
-
j: usize,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
impl Rc4 {
|
|
14
|
-
pub fn new(key: &[u8]) -> Self {
|
|
15
|
-
let mut s = [0u8; 256];
|
|
16
|
-
for i in 0..256 { s[i] = i as u8; }
|
|
17
|
-
let mut j = 0usize;
|
|
18
|
-
for i in 0..256 {
|
|
19
|
-
j = (j + s[i] as usize + key[i % key.len()] as usize) % 256;
|
|
20
|
-
s.swap(i, j);
|
|
21
|
-
}
|
|
22
|
-
Rc4 { s, i: 0, j: 0 }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
pub fn apply(&mut self, data: &mut [u8]) {
|
|
26
|
-
for byte in data.iter_mut() {
|
|
27
|
-
self.i = (self.i + 1) % 256;
|
|
28
|
-
self.j = (self.j + self.s[self.i] as usize) % 256;
|
|
29
|
-
self.s.swap(self.i, self.j);
|
|
30
|
-
let k = self.s[(self.s[self.i] as usize + self.s[self.j] as usize) % 256];
|
|
31
|
-
*byte ^= k;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: env_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: rust
|
|
5
|
-
// expected: Secret from env var logged to stdout/stderr — credential exposure in logs
|
|
6
|
-
|
|
7
|
-
use std::env;
|
|
8
|
-
|
|
9
|
-
pub fn initialize_client() {
|
|
10
|
-
let api_key = env::var("API_KEY").expect("API_KEY not set");
|
|
11
|
-
let db_password = env::var("DB_PASSWORD").expect("DB_PASSWORD not set");
|
|
12
|
-
println!("Starting client with API_KEY={}", api_key);
|
|
13
|
-
eprintln!("DB connection using password: {}", db_password);
|
|
14
|
-
log::info!("Auth token: {}", env::var("AUTH_TOKEN").unwrap_or_default());
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
pub fn debug_config() {
|
|
18
|
-
for (key, value) in env::vars() {
|
|
19
|
-
println!("{}={}", key, value);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: env_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: rust
|
|
5
|
-
// expected: Environment variable values reflected in HTTP response — credential leak
|
|
6
|
-
|
|
7
|
-
use axum::{response::IntoResponse, http::StatusCode};
|
|
8
|
-
use std::env;
|
|
9
|
-
|
|
10
|
-
pub async fn debug_handler() -> impl IntoResponse {
|
|
11
|
-
let config = serde_json::json!({
|
|
12
|
-
"database_url": env::var("DATABASE_URL").unwrap_or_default(),
|
|
13
|
-
"redis_url": env::var("REDIS_URL").unwrap_or_default(),
|
|
14
|
-
"api_key": env::var("API_KEY").unwrap_or_default(),
|
|
15
|
-
"jwt_secret": env::var("JWT_SECRET").unwrap_or_default(),
|
|
16
|
-
"aws_key": env::var("AWS_ACCESS_KEY_ID").unwrap_or_default(),
|
|
17
|
-
"aws_secret": env::var("AWS_SECRET_ACCESS_KEY").unwrap_or_default(),
|
|
18
|
-
});
|
|
19
|
-
(StatusCode::OK, config.to_string())
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
pub fn get_env_or_log(key: &str) -> String {
|
|
23
|
-
let value = env::var(key).unwrap_or_default();
|
|
24
|
-
log::debug!("Reading env {} = {}", key, value);
|
|
25
|
-
value
|
|
26
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: env_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: rust
|
|
5
|
-
// expected: User-controlled key used to read arbitrary env var — information disclosure
|
|
6
|
-
|
|
7
|
-
use std::env;
|
|
8
|
-
use axum::{extract::Query, response::IntoResponse};
|
|
9
|
-
use std::collections::HashMap;
|
|
10
|
-
|
|
11
|
-
pub async fn get_config(Query(params): Query<HashMap<String, String>>) -> impl IntoResponse {
|
|
12
|
-
let key = params.get("key").cloned().unwrap_or_default();
|
|
13
|
-
let value = env::var(&key).unwrap_or_else(|_| "not set".to_string());
|
|
14
|
-
format!("{}={}", key, value)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
pub fn lookup_env(user_key: &str) -> String {
|
|
18
|
-
env::var(user_key).unwrap_or_default()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
pub fn set_env_from_input(name: &str, value: &str) {
|
|
22
|
-
unsafe { env::set_var(name, value) }
|
|
23
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded AWS credentials in typed config object
|
|
6
|
-
|
|
7
|
-
import * as AWS from 'aws-sdk';
|
|
8
|
-
|
|
9
|
-
interface AwsCredentials {
|
|
10
|
-
accessKeyId: string;
|
|
11
|
-
secretAccessKey: string;
|
|
12
|
-
region: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const credentials: AwsCredentials = {
|
|
16
|
-
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
|
17
|
-
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
18
|
-
region: 'us-east-1',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
AWS.config.update(credentials);
|
|
22
|
-
|
|
23
|
-
const s3 = new AWS.S3();
|
|
24
|
-
const sqs = new AWS.SQS();
|
|
25
|
-
|
|
26
|
-
export async function putObject(bucket: string, key: string, body: Buffer): Promise<void> {
|
|
27
|
-
await s3.putObject({ Bucket: bucket, Key: key, Body: body }).promise();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function sendMessage(queueUrl: string, payload: object): Promise<void> {
|
|
31
|
-
await sqs.sendMessage({ QueueUrl: queueUrl, MessageBody: JSON.stringify(payload) }).promise();
|
|
32
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded JWT secret in typed auth service
|
|
6
|
-
|
|
7
|
-
import jwt, { SignOptions, JwtPayload } from 'jsonwebtoken';
|
|
8
|
-
|
|
9
|
-
interface TokenPayload {
|
|
10
|
-
userId: string;
|
|
11
|
-
role: 'admin' | 'user' | 'moderator';
|
|
12
|
-
email: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const JWT_SECRET: string = 'super-secret-jwt-signing-key-never-rotate-me-abc123!';
|
|
16
|
-
const JWT_REFRESH_SECRET: string = 'refresh-token-secret-xyz-456-do-not-share';
|
|
17
|
-
|
|
18
|
-
const signOptions: SignOptions = { expiresIn: '1h', algorithm: 'HS256' };
|
|
19
|
-
|
|
20
|
-
export function signAccessToken(payload: TokenPayload): string {
|
|
21
|
-
return jwt.sign(payload, JWT_SECRET, signOptions);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function signRefreshToken(userId: string): string {
|
|
25
|
-
return jwt.sign({ userId }, JWT_REFRESH_SECRET, { expiresIn: '30d' });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function verifyAccessToken(token: string): JwtPayload {
|
|
29
|
-
return jwt.verify(token, JWT_SECRET) as JwtPayload;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function verifyRefreshToken(token: string): JwtPayload {
|
|
33
|
-
return jwt.verify(token, JWT_REFRESH_SECRET) as JwtPayload;
|
|
34
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded database credentials in TypeORM DataSource config
|
|
6
|
-
|
|
7
|
-
import { DataSource } from 'typeorm';
|
|
8
|
-
import { User } from './entities/User';
|
|
9
|
-
import { Order } from './entities/Order';
|
|
10
|
-
|
|
11
|
-
export const AppDataSource = new DataSource({
|
|
12
|
-
type: 'postgres',
|
|
13
|
-
host: 'prod-postgres.internal',
|
|
14
|
-
port: 5432,
|
|
15
|
-
username: 'app_user',
|
|
16
|
-
password: 'Pr0dPassw0rd!@#$',
|
|
17
|
-
database: 'production_db',
|
|
18
|
-
synchronize: false,
|
|
19
|
-
logging: false,
|
|
20
|
-
ssl: { rejectUnauthorized: false },
|
|
21
|
-
entities: [User, Order],
|
|
22
|
-
migrations: ['dist/migrations/*.js'],
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
export async function initializeDatabase(): Promise<void> {
|
|
26
|
-
await AppDataSource.initialize();
|
|
27
|
-
console.log('Database connection established');
|
|
28
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded Stripe secret key and webhook secret in typed service
|
|
6
|
-
|
|
7
|
-
import Stripe from 'stripe';
|
|
8
|
-
|
|
9
|
-
const STRIPE_SECRET_KEY = 'sk_live_51HqT3kLkjhGHJKL9mNoPqRstuvWxYz1234567890abcdef';
|
|
10
|
-
const STRIPE_WEBHOOK_SECRET = 'whsec_abcdefghijklmnopqrstuvwxyz1234567890ABCDEF';
|
|
11
|
-
|
|
12
|
-
const stripe = new Stripe(STRIPE_SECRET_KEY, { apiVersion: '2023-10-16' });
|
|
13
|
-
|
|
14
|
-
export async function createPaymentIntent(
|
|
15
|
-
amount: number,
|
|
16
|
-
currency: string = 'usd',
|
|
17
|
-
customerId?: string
|
|
18
|
-
): Promise<Stripe.PaymentIntent> {
|
|
19
|
-
return stripe.paymentIntents.create({
|
|
20
|
-
amount,
|
|
21
|
-
currency,
|
|
22
|
-
customer: customerId,
|
|
23
|
-
automatic_payment_methods: { enabled: true },
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function constructWebhookEvent(
|
|
28
|
-
payload: Buffer,
|
|
29
|
-
signature: string
|
|
30
|
-
): Stripe.Event {
|
|
31
|
-
return stripe.webhooks.constructEvent(payload, signature, STRIPE_WEBHOOK_SECRET);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export { stripe };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded SendGrid API key and SMTP credentials in typed mailer service
|
|
6
|
-
|
|
7
|
-
import sgMail from '@sendgrid/mail';
|
|
8
|
-
import nodemailer from 'nodemailer';
|
|
9
|
-
|
|
10
|
-
const SENDGRID_KEY = 'SG.abcDEFghiJKLmnoPQRstUVwxYZ.1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcd';
|
|
11
|
-
sgMail.setApiKey(SENDGRID_KEY);
|
|
12
|
-
|
|
13
|
-
interface EmailOptions {
|
|
14
|
-
to: string;
|
|
15
|
-
subject: string;
|
|
16
|
-
text?: string;
|
|
17
|
-
html?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function sendTransactional(opts: EmailOptions): Promise<void> {
|
|
21
|
-
await sgMail.send({ ...opts, from: 'noreply@myapp.com' });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const smtpTransport = nodemailer.createTransport({
|
|
25
|
-
host: 'smtp.myapp.com',
|
|
26
|
-
port: 587,
|
|
27
|
-
auth: { user: 'mailer@myapp.com', pass: 'Smtp$Pass#2024!' },
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
export async function sendInternal(opts: EmailOptions): Promise<void> {
|
|
31
|
-
await smtpTransport.sendMail({ ...opts, from: 'internal@myapp.com' });
|
|
32
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded GitHub token and Slack webhook URL in typed integration config
|
|
6
|
-
|
|
7
|
-
import { Octokit } from '@octokit/rest';
|
|
8
|
-
import axios from 'axios';
|
|
9
|
-
|
|
10
|
-
interface IntegrationConfig {
|
|
11
|
-
githubToken: string;
|
|
12
|
-
slackWebhookUrl: string;
|
|
13
|
-
slackSigningSecret: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const config: IntegrationConfig = {
|
|
17
|
-
githubToken: 'ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890',
|
|
18
|
-
slackWebhookUrl: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX',
|
|
19
|
-
slackSigningSecret: 'abc123def456ghi789jkl012mno345pqr678stu',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const octokit = new Octokit({ auth: config.githubToken });
|
|
23
|
-
|
|
24
|
-
export async function postToSlack(text: string): Promise<void> {
|
|
25
|
-
await axios.post(config.slackWebhookUrl, { text });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function createIssue(owner: string, repo: string, title: string): Promise<number> {
|
|
29
|
-
const { data } = await octokit.issues.create({ owner, repo, title });
|
|
30
|
-
return data.number;
|
|
31
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded encryption key and static IV in typed crypto utility
|
|
6
|
-
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
|
-
|
|
9
|
-
const ENCRYPTION_KEY: Buffer = Buffer.from('12345678901234567890123456789012', 'utf8'); // 32 bytes
|
|
10
|
-
const STATIC_IV: Buffer = Buffer.from('abcdef1234567890', 'utf8'); // 16 bytes — never rotated
|
|
11
|
-
|
|
12
|
-
export function encrypt(plaintext: string): string {
|
|
13
|
-
const cipher = crypto.createCipheriv('aes-256-cbc', ENCRYPTION_KEY, STATIC_IV);
|
|
14
|
-
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
|
|
15
|
-
encrypted += cipher.final('base64');
|
|
16
|
-
return encrypted;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function decrypt(ciphertext: string): string {
|
|
20
|
-
const decipher = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, STATIC_IV);
|
|
21
|
-
let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
|
|
22
|
-
decrypted += decipher.final('utf8');
|
|
23
|
-
return decrypted;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function hashToken(token: string): string {
|
|
27
|
-
return crypto.createHash('md5').update(token + 'static-pepper').digest('hex');
|
|
28
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: hardcoded_secret
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: hardcoded OAuth2 client credentials and admin API key in typed config
|
|
6
|
-
|
|
7
|
-
import { Router, Request, Response } from 'express';
|
|
8
|
-
|
|
9
|
-
interface OAuthConfig {
|
|
10
|
-
clientId: string;
|
|
11
|
-
clientSecret: string;
|
|
12
|
-
redirectUri: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface AdminConfig {
|
|
16
|
-
apiKey: string;
|
|
17
|
-
adminPassword: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const oauthConfig: OAuthConfig = {
|
|
21
|
-
clientId: 'oauth2-client-production-id-abc123',
|
|
22
|
-
clientSecret: 'oauth2-client-secret-XyZ9876543210abcdefGHIJKL',
|
|
23
|
-
redirectUri: 'https://app.company.com/oauth/callback',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const adminConfig: AdminConfig = {
|
|
27
|
-
apiKey: 'admin-api-key-supersecret-prod-2024',
|
|
28
|
-
adminPassword: 'AdminP@ssw0rd!#$',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const router = Router();
|
|
32
|
-
|
|
33
|
-
router.post('/oauth/token', (req: Request, res: Response) => {
|
|
34
|
-
if (req.body.client_secret !== oauthConfig.clientSecret) {
|
|
35
|
-
return res.status(401).json({ error: 'invalid_client' });
|
|
36
|
-
}
|
|
37
|
-
res.json({ access_token: 'generated', token_type: 'bearer' });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
export default router;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection via TypeORM query() with string concatenation
|
|
6
|
-
|
|
7
|
-
import { AppDataSource } from '../data-source';
|
|
8
|
-
|
|
9
|
-
export async function findUserByUsername(username: string) {
|
|
10
|
-
// Raw query with direct string interpolation — bypasses parameterization
|
|
11
|
-
const result = await AppDataSource.query(
|
|
12
|
-
`SELECT * FROM users WHERE username = '${username}'`
|
|
13
|
-
);
|
|
14
|
-
return result[0] ?? null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function searchProducts(term: string, category: string) {
|
|
18
|
-
const sql = `
|
|
19
|
-
SELECT p.*, c.name as category_name
|
|
20
|
-
FROM products p
|
|
21
|
-
JOIN categories c ON p.category_id = c.id
|
|
22
|
-
WHERE p.name ILIKE '%` + term + `%'
|
|
23
|
-
AND c.slug = '` + category + `'
|
|
24
|
-
ORDER BY p.created_at DESC
|
|
25
|
-
`;
|
|
26
|
-
return AppDataSource.query(sql);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function deleteUserSessions(userId: string) {
|
|
30
|
-
await AppDataSource.query('DELETE FROM sessions WHERE user_id = ' + userId);
|
|
31
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection in TypeORM repository createQueryBuilder with raw where clause
|
|
6
|
-
|
|
7
|
-
import { Repository } from 'typeorm';
|
|
8
|
-
import { User } from '../entities/User';
|
|
9
|
-
|
|
10
|
-
export class UserRepository {
|
|
11
|
-
constructor(private repo: Repository<User>) {}
|
|
12
|
-
|
|
13
|
-
async findByEmail(email: string): Promise<User | null> {
|
|
14
|
-
return this.repo
|
|
15
|
-
.createQueryBuilder('user')
|
|
16
|
-
// Direct interpolation into where() — SQL injection
|
|
17
|
-
.where(`user.email = '${email}'`)
|
|
18
|
-
.getOne();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async searchByName(query: string, role: string): Promise<User[]> {
|
|
22
|
-
return this.repo
|
|
23
|
-
.createQueryBuilder('user')
|
|
24
|
-
.where(`user.name ILIKE '%${query}%' AND user.role = '${role}'`)
|
|
25
|
-
.orderBy('user.createdAt', 'DESC')
|
|
26
|
-
.getMany();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async getByIds(ids: string[]): Promise<User[]> {
|
|
30
|
-
const idList = ids.join(', ');
|
|
31
|
-
return this.repo.query(`SELECT * FROM users WHERE id IN (${idList})`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection in Prisma $queryRaw with tagged template bypassed via string concat
|
|
6
|
-
|
|
7
|
-
import { PrismaClient } from '@prisma/client';
|
|
8
|
-
|
|
9
|
-
const prisma = new PrismaClient();
|
|
10
|
-
|
|
11
|
-
export async function getUserOrders(userId: string, status: string) {
|
|
12
|
-
// $queryRawUnsafe does NOT parameterize — direct string concatenation
|
|
13
|
-
const orders = await prisma.$queryRawUnsafe(
|
|
14
|
-
`SELECT * FROM orders WHERE user_id = '${userId}' AND status = '${status}'`
|
|
15
|
-
);
|
|
16
|
-
return orders;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function searchInventory(searchTerm: string, warehouseId: string) {
|
|
20
|
-
const query = `
|
|
21
|
-
SELECT i.*, w.name as warehouse
|
|
22
|
-
FROM inventory i
|
|
23
|
-
JOIN warehouses w ON i.warehouse_id = w.id
|
|
24
|
-
WHERE i.sku ILIKE '%` + searchTerm + `%'
|
|
25
|
-
OR i.description ILIKE '%` + searchTerm + `%'
|
|
26
|
-
AND w.id = '` + warehouseId + `'
|
|
27
|
-
`;
|
|
28
|
-
return prisma.$queryRawUnsafe(query);
|
|
29
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection in NestJS service using raw pg query with string interpolation
|
|
6
|
-
|
|
7
|
-
import { Injectable } from '@nestjs/common';
|
|
8
|
-
import { InjectConnection } from '@nestjs/typeorm';
|
|
9
|
-
import { Connection } from 'typeorm';
|
|
10
|
-
|
|
11
|
-
@Injectable()
|
|
12
|
-
export class ReportService {
|
|
13
|
-
constructor(@InjectConnection() private connection: Connection) {}
|
|
14
|
-
|
|
15
|
-
async getRevenueReport(fromDate: string, toDate: string, groupBy: string): Promise<any[]> {
|
|
16
|
-
const sql = `
|
|
17
|
-
SELECT
|
|
18
|
-
${groupBy} as period,
|
|
19
|
-
SUM(amount) as total_revenue,
|
|
20
|
-
COUNT(*) as transaction_count
|
|
21
|
-
FROM transactions
|
|
22
|
-
WHERE created_at BETWEEN '${fromDate}' AND '${toDate}'
|
|
23
|
-
GROUP BY ${groupBy}
|
|
24
|
-
ORDER BY period ASC
|
|
25
|
-
`;
|
|
26
|
-
return this.connection.query(sql);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async getUserActivity(userId: string, action: string): Promise<any[]> {
|
|
30
|
-
return this.connection.query(
|
|
31
|
-
`SELECT * FROM audit_log WHERE user_id = '` + userId + `' AND action = '` + action + `'`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection via dynamic ORDER BY column name from user input
|
|
6
|
-
|
|
7
|
-
import { Pool } from 'pg';
|
|
8
|
-
|
|
9
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
10
|
-
|
|
11
|
-
interface ListOptions {
|
|
12
|
-
sortField: string; // user-controlled
|
|
13
|
-
sortDir: 'asc' | 'desc'; // type claim but not validated
|
|
14
|
-
limit: number;
|
|
15
|
-
offset: number;
|
|
16
|
-
filter?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function listProducts(opts: ListOptions) {
|
|
20
|
-
// sortField and sortDir inserted verbatim — type annotations don't prevent injection
|
|
21
|
-
const query = `
|
|
22
|
-
SELECT id, name, price, stock, created_at
|
|
23
|
-
FROM products
|
|
24
|
-
WHERE archived = false
|
|
25
|
-
${opts.filter ? `AND name ILIKE '%${opts.filter}%'` : ''}
|
|
26
|
-
ORDER BY ${opts.sortField} ${opts.sortDir}
|
|
27
|
-
LIMIT ${opts.limit} OFFSET ${opts.offset}
|
|
28
|
-
`;
|
|
29
|
-
const { rows } = await pool.query(query);
|
|
30
|
-
return rows;
|
|
31
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection in authentication middleware via raw query with login credentials
|
|
6
|
-
|
|
7
|
-
import { Request, Response, NextFunction } from 'express';
|
|
8
|
-
import { Pool } from 'pg';
|
|
9
|
-
|
|
10
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
11
|
-
|
|
12
|
-
export async function authenticateMiddleware(
|
|
13
|
-
req: Request,
|
|
14
|
-
res: Response,
|
|
15
|
-
next: NextFunction
|
|
16
|
-
): Promise<void> {
|
|
17
|
-
const { username, password } = req.body as { username: string; password: string };
|
|
18
|
-
|
|
19
|
-
// Classic auth bypass: username = "admin'--"
|
|
20
|
-
const query = `
|
|
21
|
-
SELECT id, username, role, email
|
|
22
|
-
FROM users
|
|
23
|
-
WHERE username = '${username}'
|
|
24
|
-
AND password_hash = crypt('${password}', password_hash)
|
|
25
|
-
AND active = true
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const { rows } = await pool.query(query);
|
|
29
|
-
if (!rows.length) {
|
|
30
|
-
res.status(401).json({ error: 'Invalid credentials' });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
req.user = rows[0];
|
|
35
|
-
next();
|
|
36
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection via Sequelize literal() with user-controlled search input
|
|
6
|
-
|
|
7
|
-
import { Sequelize, QueryTypes } from 'sequelize';
|
|
8
|
-
|
|
9
|
-
const sequelize = new Sequelize(process.env.DATABASE_URL!);
|
|
10
|
-
|
|
11
|
-
export async function fullTextSearch(
|
|
12
|
-
query: string,
|
|
13
|
-
table: 'products' | 'articles' | 'users',
|
|
14
|
-
fields: string[]
|
|
15
|
-
): Promise<Record<string, unknown>[]> {
|
|
16
|
-
const fieldList = fields.join(', ');
|
|
17
|
-
const whereClause = fields
|
|
18
|
-
.map(f => `${f} ILIKE '%${query}%'`)
|
|
19
|
-
.join(' OR ');
|
|
20
|
-
|
|
21
|
-
// Both fieldList and whereClause derived from user input — injection possible
|
|
22
|
-
const sql = `SELECT ${fieldList} FROM "${table}" WHERE ${whereClause} LIMIT 50`;
|
|
23
|
-
return sequelize.query(sql, { type: QueryTypes.SELECT });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function bulkUpdate(ids: number[], status: string): Promise<void> {
|
|
27
|
-
const idList = ids.join(',');
|
|
28
|
-
await sequelize.query(
|
|
29
|
-
`UPDATE orders SET status = '${status}' WHERE id IN (${idList})`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via React dangerouslySetInnerHTML with user-controlled content
|
|
6
|
-
|
|
7
|
-
import React from 'react';
|
|
8
|
-
|
|
9
|
-
interface Comment {
|
|
10
|
-
id: string;
|
|
11
|
-
author: string;
|
|
12
|
-
body: string;
|
|
13
|
-
avatarHtml: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface CommentCardProps {
|
|
17
|
-
comment: Comment;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const CommentCard: React.FC<CommentCardProps> = ({ comment }) => {
|
|
21
|
-
return (
|
|
22
|
-
<div className="comment-card">
|
|
23
|
-
{/* avatarHtml and body come from server/user — XSS if unsanitized */}
|
|
24
|
-
<div className="avatar" dangerouslySetInnerHTML={{ __html: comment.avatarHtml }} />
|
|
25
|
-
<div className="author">{comment.author}</div>
|
|
26
|
-
<div className="body" dangerouslySetInnerHTML={{ __html: comment.body }} />
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
interface RichTextProps {
|
|
32
|
-
content: string; // user-submitted rich text
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const RichTextRenderer: React.FC<RichTextProps> = ({ content }) => (
|
|
36
|
-
<article dangerouslySetInnerHTML={{ __html: content }} />
|
|
37
|
-
);
|