diff --git a/Cargo.lock b/Cargo.lock index 0d8119b7..324afaf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "directories", "filetime", "ignore", + "log", "num_cpus", "once_cell", "rusqlite", @@ -19,6 +20,7 @@ dependencies = [ "tempfile", "toml", "tracing", + "tracing-appender", "tracing-subscriber", "tree-sitter", "tree-sitter-c", @@ -588,7 +590,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -776,13 +778,33 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -888,6 +910,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror 1.0.69", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.29" diff --git a/Cargo.toml b/Cargo.toml index 41c07734..f08c8953 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ clap = { version = "4.5.40", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] } toml = "0.8.23" tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json", "ansi","time"] } +tracing-appender = "0.2.3" tracing = "0.1.41" num_cpus = "1.17.0" @@ -32,3 +33,4 @@ crossbeam-channel = "0.5.15" blake3 = "1.8.2" filetime = "0.2.25" once_cell = "1.21.3" +log = "0.4.27" diff --git a/src/commands/index.rs b/src/commands/index.rs index a96bf9f2..03e7e022 100644 --- a/src/commands/index.rs +++ b/src/commands/index.rs @@ -43,6 +43,5 @@ pub fn build_index( ) -> Result<(), Box> { // TODO: Implement actual index building fs::File::create(db_path)?; - println!("Index building logic goes here..."); Ok(()) } \ No newline at end of file diff --git a/src/patterns/javascript.rs b/src/patterns/javascript.rs index cce31a17..5f868a66 100644 --- a/src/patterns/javascript.rs +++ b/src/patterns/javascript.rs @@ -37,4 +37,86 @@ pub const PATTERNS: &[Pattern] = &[ query: "(call_expression function: (member_expression object: (identifier) @obj (#eq? @obj \"JSON\") property: (property_identifier) @prop (#eq? @prop \"parse\"))) @vuln", severity: Severity::Low, }, + Pattern { + id: "outer_html_assignment", + description: "Assignment to element.outerHTML", + query: "(assignment_expression + left: (member_expression + property: (property_identifier) @prop + (#eq? @prop \"outerHTML\"))) @vuln", + severity: Severity::Medium, + }, + Pattern { + id: "insert_adjacent_html", + description: "insertAdjacentHTML() call", + query: "(call_expression + function: (member_expression + property: (property_identifier) @prop + (#eq? @prop \"insertAdjacentHTML\"))) @vuln", + severity: Severity::Medium, + }, + Pattern { + id: "location_href_assignment", + description: "Assignment to window.location / location.href", + query: "(assignment_expression + left: (member_expression + object: (identifier)? @obj + property: (property_identifier) @prop + (#match? @prop \"location|href\"))) @vuln", + severity: Severity::High, + }, + Pattern { + id: "cookie_assignment", + description: "Write to document.cookie", + query: "(assignment_expression + left: (member_expression + object: (identifier) @obj + (#eq? @obj \"document\") + property: (property_identifier) @prop + (#eq? @prop \"cookie\"))) @vuln", + severity: Severity::Medium, + }, + Pattern { + id: "proto_pollution", + description: "Assignment to __proto__ (prototype pollution)", + query: "(assignment_expression + left: (member_expression + property: (property_identifier) @prop + (#eq? @prop \"__proto__\"))) @vuln", + severity: Severity::High, + }, + Pattern { + id: "weak_hash_md5", + description: "crypto.createHash(\"md5\")", + query: "(call_expression + function: (member_expression + object: (identifier) @obj + (#eq? @obj \"crypto\") + property: (property_identifier) @prop + (#eq? @prop \"createHash\")) + arguments: (arguments + (string) @alg + (#eq? @alg \"md5\"))) @vuln", + severity: Severity::Low, + }, + Pattern { + id: "regexp_constructor_string", + description: "new RegExp() with a dynamic string", + query: "(new_expression + constructor: (identifier) @id + (#eq? @id \"RegExp\") + arguments: (arguments (string) @pattern)) @vuln", + severity: Severity::Low, + }, + Pattern { + id: "dangerous_extend_builtin", + description: "Extending Object.prototype (may lead to collisions/pollution)", + query: "(assignment_expression + left: (member_expression + object: (identifier) @obj + (#eq? @obj \"Object\") + property: (property_identifier) @prop + (#eq? @prop \"prototype\"))) @vuln", + severity: Severity::Medium, + }, ]; diff --git a/src/patterns/rust.rs b/src/patterns/rust.rs index 635608b5..f57f6e0c 100644 --- a/src/patterns/rust.rs +++ b/src/patterns/rust.rs @@ -1,9 +1,5 @@ use crate::patterns::{Pattern, Severity}; -/// The full catalogue. -/// -/// *Feel free to prune, extend, or tweak severities to suit your own threat -/// model.* pub const PATTERNS: &[Pattern] = &[ Pattern { id: "unsafe_block", @@ -14,19 +10,57 @@ pub const PATTERNS: &[Pattern] = &[ Pattern { id: "unsafe_fn", description: "`unsafe fn` declaration", - query: "(function_item (modifier) @kw (#eq? @kw \"unsafe\")) @vuln", + query: "(function_item + (function_modifiers) @mods + (#match? @mods \"^unsafe\\b\")) @vuln", + severity: Severity::High, + }, + Pattern { + id: "transmute_call", + description: "`std::mem::transmute` call", + query: "(call_expression + function: (scoped_identifier + path: (identifier) @p (#eq? @p \"mem\") + name: (identifier) @f (#eq? @f \"transmute\"))) + @vuln", + severity: Severity::High, + }, + Pattern { + id: "copy_nonoverlapping", + description: "Raw pointer `copy_nonoverlapping`", + query: "(call_expression + function: (scoped_identifier + path: (identifier) @p (#eq? @p \"ptr\") + name: (identifier) @f (#eq? @f \"copy_nonoverlapping\"))) + @vuln", + severity: Severity::High, + }, + Pattern { + id: "get_unchecked", + description: "`get_unchecked` / `get_unchecked_mut` slice access", + query: "(call_expression + function: (field_expression + field: (field_identifier) @m + (#match? @m \"get_unchecked(_mut)?\"))) @vuln", severity: Severity::High, }, Pattern { id: "unwrap_call", description: "`.unwrap()` call (may panic)", - query: "(call_expression function: (field_expression field: (field_identifier) @name (#eq? @name \"unwrap\"))) @vuln", + query: "(call_expression + function: (field_expression + field: (field_identifier) @name + (#eq? @name \"unwrap\"))) ; exact match + @vuln", severity: Severity::Medium, }, Pattern { id: "expect_call", description: "`.expect()` call (may panic)", - query: "(call_expression function: (field_expression field: (field_identifier) @name (#eq? @name \"expect\"))) @vuln", + query: "(call_expression + function: (field_expression + field: (field_identifier) @name + (#eq? @name \"expect\"))) @vuln", severity: Severity::Medium, }, Pattern { @@ -38,31 +72,47 @@ pub const PATTERNS: &[Pattern] = &[ Pattern { id: "todo_or_unimplemented", description: "`todo!()` / `unimplemented!()` placeholder", - query: "(macro_invocation (identifier) @id (#match? @id \"todo|unimplemented\")) @vuln", + query: "(macro_invocation + (identifier) @id + (#match? @id \"todo|unimplemented\")) @vuln", severity: Severity::Low, }, - Pattern { - id: "transmute_call", - description: "`std::mem::transmute` call", - query: "(call_expression function: (scoped_identifier path: (identifier) @p (#eq? @p \"mem\") name: (identifier) @f (#eq? @f \"transmute\"))) @vuln", - severity: Severity::High, - }, - Pattern { - id: "get_unchecked", - description: "`get_unchecked` or `get_unchecked_mut` slice access", - query: "(call_expression function: (field_expression field: (field_identifier) @m (#match? @m \"get_unchecked(_mut)?\"))) @vuln", - severity: Severity::High, - }, - Pattern { - id: "copy_nonoverlapping", - description: "Raw pointer `copy_nonoverlapping`", - query: "(call_expression function: (scoped_identifier path: (identifier) @p (#eq? @p \"ptr\") name: (identifier) @f (#eq? @f \"copy_nonoverlapping\"))) @vuln", - severity: Severity::High, - }, Pattern { id: "narrow_cast_with_as", description: "`as` cast to an 8-/16-bit integer (possible truncation)", - query: "(as_expression left: (_) right: (primitive_type) @to (#match? @to \"u8|i8|u16|i16\")) @vuln", + query: "(type_cast_expression + type: (primitive_type) @to + (#match? @to \"^u?i(8|16)$\")) @vuln", severity: Severity::Low, }, + Pattern { + id: "mem_zeroed", + description: "`std::mem::zeroed()`", + query: "(call_expression function:(scoped_identifier path:(identifier)@p (#eq? @p \"mem\") name:(identifier)@n (#eq? @n \"zeroed\")))@vuln", + severity: Severity::High + }, + Pattern { + id: "mem_forget", + description: "`std::mem::forget()`", + query: "(call_expression function:(scoped_identifier path:(identifier)@p (#eq? @p \"mem\") name:(identifier)@n (#eq? @n \"forget\")))@vuln", + severity: Severity::Medium + }, + Pattern { + id: "ptr_read", + description: "`ptr::read_*` raw-ptr read", + query: "(call_expression function:(scoped_identifier path:(identifier)@p (#eq? @p \"ptr\") name:(identifier)@n (#match? @n \"read(_volatile)?\")))@vuln", + severity: Severity::High + }, + Pattern { + id: "arc_unwrap", + description: "`Arc::unwrap_or_else_unchecked`", + query: "(call_expression function:(scoped_identifier name:(identifier)@n (#eq? @n \"unwrap_or_else_unchecked\")))@vuln", + severity: Severity::High + }, + Pattern { + id: "dbg_macro", + description: "`dbg!()` left in code", + query: "(macro_invocation (identifier)@id (#eq? @id \"dbg\"))@vuln", + severity: Severity::Low + }, ]; diff --git a/src/walk.rs b/src/walk.rs index 12164166..f162ab6e 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -43,11 +43,15 @@ pub fn spawn_senders( let mut ob = OverrideBuilder::new(root); for ext in &cfg.scanner.excluded_extensions { - ob.add(&format!("!*.{ext}")).unwrap(); + if let Err(e) = ob.add(&format!("!*.{ext}")) { + tracing::warn!("could not add ignore pattern: {e}"); + } } for dir in &cfg.scanner.excluded_directories { - ob.add(&format!("!**/{dir}/**")).unwrap(); + if let Err(e) = ob.add(&format!("!**/{dir}/**")) { + tracing::warn!("could not add ignore pattern: {e}"); + } } let overrides = ob.build().unwrap();