mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-21 20:18:06 +02:00
Phase 1 (#33)
* chore: Exclude CLAUDE.md from Cargo.toml * feat: add callgraph module and integrate into main analysis flow * feat: enhance CLI with new severity filtering and analysis modes * feat: update CHANGELOG with recent enhancements and fixes to severity filtering and output handling * feat: implement state-model dataflow analysis for resource lifecycle and auth state * feat: enhance diagnostic output formatting and add evidence structure * feat: implement attack surface ranking for diagnostics with scoring and sorting * feat: add comprehensive documentation for installation, usage, and rules reference * feat: add multiple language support for command execution and evaluation endpoints * feat: implement inline suppression for findings using `nyx:ignore` comments * feat: add confidence levels to AST patterns and update output structure * feat: implement low-noise prioritization system with category filtering, rollup grouping, and configurable budgets * feat: bump version to 0.4.0 and update changelog with new features and improvements * feat: add dead code allowances to various functions in mod.rs and real_world_tests.rs
This commit is contained in:
parent
19b578c5c4
commit
1bbe4b1cfb
456 changed files with 25628 additions and 1228 deletions
|
|
@ -31,6 +31,10 @@ pub static RULES: &[LabelRule] = &[
|
|||
matchers: &["printf", "fprintf"],
|
||||
label: DataLabel::Sink(Cap::FMT_STRING),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["fopen", "open"],
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
];
|
||||
|
||||
pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
||||
|
|
@ -39,6 +43,9 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"while_statement" => Kind::While,
|
||||
"for_statement" => Kind::For,
|
||||
"do_statement" => Kind::While,
|
||||
"switch_statement" => Kind::Block,
|
||||
"case_statement" => Kind::Block,
|
||||
"labeled_statement" => Kind::Block,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"break_statement" => Kind::Break,
|
||||
|
|
@ -47,6 +54,7 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
// structure
|
||||
"translation_unit" => Kind::SourceFile,
|
||||
"compound_statement" => Kind::Block,
|
||||
"else_clause" => Kind::Block,
|
||||
"function_definition" => Kind::Function,
|
||||
|
||||
// data-flow
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ pub static RULES: &[LabelRule] = &[
|
|||
matchers: &["printf", "fprintf"],
|
||||
label: DataLabel::Sink(Cap::FMT_STRING),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["fopen", "open"],
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
];
|
||||
|
||||
pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
||||
|
|
@ -38,15 +42,23 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"for_statement" => Kind::For,
|
||||
"for_range_loop" => Kind::For,
|
||||
"do_statement" => Kind::While,
|
||||
"switch_statement" => Kind::Block,
|
||||
"case_statement" => Kind::Block,
|
||||
"labeled_statement" => Kind::Block,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"throw_statement" => Kind::Return,
|
||||
"break_statement" => Kind::Break,
|
||||
"continue_statement" => Kind::Continue,
|
||||
|
||||
// structure
|
||||
"translation_unit" => Kind::SourceFile,
|
||||
"compound_statement" => Kind::Block,
|
||||
"else_clause" => Kind::Block,
|
||||
"function_definition" => Kind::Function,
|
||||
"try_statement" => Kind::Block,
|
||||
"catch_clause" => Kind::Block,
|
||||
"lambda_expression" => Kind::Block,
|
||||
|
||||
// data-flow
|
||||
"call_expression" => Kind::CallFn,
|
||||
|
|
@ -63,7 +75,7 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"preproc_include" => Kind::Trivia,
|
||||
"preproc_def" => Kind::Trivia,
|
||||
"using_declaration" => Kind::Trivia,
|
||||
"namespace_definition" => Kind::Trivia,
|
||||
"namespace_definition" => Kind::Block,
|
||||
};
|
||||
|
||||
pub static PARAM_CONFIG: ParamConfig = ParamConfig {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,17 @@ pub static RULES: &[LabelRule] = &[
|
|||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["http.Request", "r.FormValue", "r.URL"],
|
||||
matchers: &[
|
||||
"http.Request",
|
||||
"r.FormValue",
|
||||
"r.URL",
|
||||
"r.Body",
|
||||
"r.Header",
|
||||
"r.URL.Query",
|
||||
"r.URL.Query.Get",
|
||||
"Request.FormValue",
|
||||
"Request.URL",
|
||||
],
|
||||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
// ───────── Sanitizers ──────────
|
||||
|
|
@ -17,18 +27,40 @@ pub static RULES: &[LabelRule] = &[
|
|||
label: DataLabel::Sanitizer(Cap::HTML_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["url.QueryEscape"],
|
||||
matchers: &["url.QueryEscape", "url.PathEscape"],
|
||||
label: DataLabel::Sanitizer(Cap::URL_ENCODE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["filepath.Clean", "filepath.Base"],
|
||||
label: DataLabel::Sanitizer(Cap::FILE_IO),
|
||||
},
|
||||
// ─────────── Sinks ─────────────
|
||||
LabelRule {
|
||||
matchers: &["exec.Command"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["db.Query", "db.Exec"],
|
||||
matchers: &["db.Query", "db.Exec", "db.QueryRow", "db.Prepare"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["fmt.Fprintf", "fmt.Sprintf", "fmt.Printf"],
|
||||
label: DataLabel::Sink(Cap::FMT_STRING),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &[
|
||||
"os.Open",
|
||||
"os.OpenFile",
|
||||
"os.Create",
|
||||
"ioutil.ReadFile",
|
||||
"os.ReadFile",
|
||||
],
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["template.HTML"],
|
||||
label: DataLabel::Sink(Cap::HTML_ESCAPE),
|
||||
},
|
||||
];
|
||||
|
||||
pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
||||
|
|
@ -46,6 +78,16 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"statement_list" => Kind::Block,
|
||||
"function_declaration" => Kind::Function,
|
||||
"method_declaration" => Kind::Function,
|
||||
"func_literal" => Kind::Function,
|
||||
"expression_switch_statement" => Kind::Block,
|
||||
"type_switch_statement" => Kind::Block,
|
||||
"expression_case" => Kind::Block,
|
||||
"type_case" => Kind::Block,
|
||||
"default_case" => Kind::Block,
|
||||
"select_statement" => Kind::Block,
|
||||
"communication_case" => Kind::Block,
|
||||
"go_statement" => Kind::Block,
|
||||
"defer_statement" => Kind::Block,
|
||||
|
||||
// data-flow
|
||||
"call_expression" => Kind::CallFn,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,19 @@ pub static RULES: &[LabelRule] = &[
|
|||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["getParameter", "getInputStream", "getHeader", "getCookies"],
|
||||
matchers: &[
|
||||
"getParameter",
|
||||
"getInputStream",
|
||||
"getHeader",
|
||||
"getCookies",
|
||||
"getReader",
|
||||
"getQueryString",
|
||||
"getPathInfo",
|
||||
],
|
||||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["readObject", "readLine"],
|
||||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
// ───────── Sanitizers ──────────
|
||||
|
|
@ -18,13 +30,21 @@ pub static RULES: &[LabelRule] = &[
|
|||
},
|
||||
// ─────────── Sinks ─────────────
|
||||
LabelRule {
|
||||
matchers: &["Runtime.exec"],
|
||||
matchers: &["Runtime.exec", "ProcessBuilder"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["executeQuery", "executeUpdate", "prepareStatement"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["Class.forName"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["println", "print", "write"],
|
||||
label: DataLabel::Sink(Cap::HTML_ESCAPE),
|
||||
},
|
||||
];
|
||||
|
||||
pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
||||
|
|
@ -33,8 +53,10 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"while_statement" => Kind::While,
|
||||
"for_statement" => Kind::For,
|
||||
"enhanced_for_statement" => Kind::For,
|
||||
"do_statement" => Kind::While,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"throw_statement" => Kind::Return,
|
||||
"break_statement" => Kind::Break,
|
||||
"continue_statement" => Kind::Continue,
|
||||
|
||||
|
|
@ -46,6 +68,15 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"interface_body" => Kind::Block,
|
||||
"method_declaration" => Kind::Function,
|
||||
"constructor_declaration" => Kind::Function,
|
||||
"switch_expression" => Kind::Block,
|
||||
"switch_block" => Kind::Block,
|
||||
"switch_block_statement_group" => Kind::Block,
|
||||
"try_statement" => Kind::Block,
|
||||
"catch_clause" => Kind::Block,
|
||||
"finally_clause" => Kind::Block,
|
||||
"lambda_expression" => Kind::Block,
|
||||
"constructor_body" => Kind::Block,
|
||||
"static_initializer" => Kind::Block,
|
||||
|
||||
// data-flow
|
||||
"method_invocation" => Kind::CallMethod,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"while_statement" => Kind::While,
|
||||
"for_statement" => Kind::For,
|
||||
"for_in_statement" => Kind::For,
|
||||
"do_statement" => Kind::While,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"throw_statement" => Kind::Return,
|
||||
|
|
@ -71,9 +72,24 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
// structure
|
||||
"program" => Kind::SourceFile,
|
||||
"statement_block" => Kind::Block,
|
||||
"else_clause" => Kind::Block,
|
||||
"function_declaration" => Kind::Function,
|
||||
"function_expression" => Kind::Function,
|
||||
"arrow_function" => Kind::Function,
|
||||
"method_definition" => Kind::Function,
|
||||
"generator_function_declaration" => Kind::Function,
|
||||
"generator_function" => Kind::Function,
|
||||
"switch_statement" => Kind::Block,
|
||||
"switch_body" => Kind::Block,
|
||||
"switch_case" => Kind::Block,
|
||||
"switch_default" => Kind::Block,
|
||||
"try_statement" => Kind::Block,
|
||||
"catch_clause" => Kind::Block,
|
||||
"finally_clause" => Kind::Block,
|
||||
"class_declaration" => Kind::Block,
|
||||
"class" => Kind::Block,
|
||||
"class_body" => Kind::Block,
|
||||
"export_statement" => Kind::Block,
|
||||
|
||||
// data-flow
|
||||
"call_expression" => Kind::CallFn,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ pub enum Kind {
|
|||
InfiniteLoop,
|
||||
While,
|
||||
For,
|
||||
LoopBody,
|
||||
CallFn,
|
||||
CallMethod,
|
||||
CallMacro,
|
||||
|
|
@ -196,7 +195,7 @@ pub fn lookup(lang: &str, raw: &str) -> Kind {
|
|||
}
|
||||
|
||||
/// The kind of taint source, used to refine finding severity.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum SourceKind {
|
||||
/// Direct user input (request params, argv, stdin, form data)
|
||||
UserInput,
|
||||
|
|
@ -375,6 +374,11 @@ pub fn classify(lang: &str, text: &str, extra: Option<&[RuntimeLabelRule]>) -> O
|
|||
let head = text.split(['(', '<']).next().unwrap_or("");
|
||||
let trimmed = head.trim().as_bytes();
|
||||
|
||||
// For chained calls like `r.URL.Query().Get`, also strip internal
|
||||
// `().` segments to produce a normalized form like `r.URL.Query.Get`.
|
||||
let full_normalized = normalize_chained_call(text);
|
||||
let full_norm_bytes = full_normalized.as_bytes();
|
||||
|
||||
// ── Check runtime (config) rules first — they take priority ──────
|
||||
if let Some(extras) = extra {
|
||||
// Pass 1: exact / suffix
|
||||
|
|
@ -384,12 +388,8 @@ pub fn classify(lang: &str, text: &str, extra: Option<&[RuntimeLabelRule]>) -> O
|
|||
if m.last() == Some(&b'_') {
|
||||
continue;
|
||||
}
|
||||
if ends_with_ignore_case(trimmed, m) {
|
||||
let start = trimmed.len() - m.len();
|
||||
let ok = start == 0 || matches!(trimmed[start - 1], b'.' | b':');
|
||||
if ok {
|
||||
return Some(rule.label);
|
||||
}
|
||||
if match_suffix(trimmed, m) || match_suffix(full_norm_bytes, m) {
|
||||
return Some(rule.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -397,7 +397,10 @@ pub fn classify(lang: &str, text: &str, extra: Option<&[RuntimeLabelRule]>) -> O
|
|||
for rule in extras {
|
||||
for raw in &rule.matchers {
|
||||
let m = raw.as_bytes();
|
||||
if m.last() == Some(&b'_') && starts_with_ignore_case(trimmed, m) {
|
||||
if m.last() == Some(&b'_')
|
||||
&& (starts_with_ignore_case(trimmed, m)
|
||||
|| starts_with_ignore_case(full_norm_bytes, m))
|
||||
{
|
||||
return Some(rule.label);
|
||||
}
|
||||
}
|
||||
|
|
@ -417,12 +420,8 @@ pub fn classify(lang: &str, text: &str, extra: Option<&[RuntimeLabelRule]>) -> O
|
|||
if m.last() == Some(&b'_') {
|
||||
continue;
|
||||
}
|
||||
if ends_with_ignore_case(trimmed, m) {
|
||||
let start = trimmed.len() - m.len();
|
||||
let ok = start == 0 || matches!(trimmed[start - 1], b'.' | b':');
|
||||
if ok {
|
||||
return Some(rule.label);
|
||||
}
|
||||
if match_suffix(trimmed, m) || match_suffix(full_norm_bytes, m) {
|
||||
return Some(rule.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -431,7 +430,10 @@ pub fn classify(lang: &str, text: &str, extra: Option<&[RuntimeLabelRule]>) -> O
|
|||
for rule in *rules {
|
||||
for raw in rule.matchers {
|
||||
let m = raw.as_bytes();
|
||||
if m.last() == Some(&b'_') && starts_with_ignore_case(trimmed, m) {
|
||||
if m.last() == Some(&b'_')
|
||||
&& (starts_with_ignore_case(trimmed, m)
|
||||
|| starts_with_ignore_case(full_norm_bytes, m))
|
||||
{
|
||||
return Some(rule.label);
|
||||
}
|
||||
}
|
||||
|
|
@ -440,6 +442,58 @@ pub fn classify(lang: &str, text: &str, extra: Option<&[RuntimeLabelRule]>) -> O
|
|||
None
|
||||
}
|
||||
|
||||
/// Check if `text` ends with `matcher` at a word boundary (`.` or `:`).
|
||||
#[inline]
|
||||
fn match_suffix(text: &[u8], matcher: &[u8]) -> bool {
|
||||
if ends_with_ignore_case(text, matcher) {
|
||||
let start = text.len() - matcher.len();
|
||||
start == 0 || matches!(text[start - 1], b'.' | b':')
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize a chained method call: strip `()` between `.` segments.
|
||||
/// e.g. `r.URL.Query().Get` → `r.URL.Query.Get`
|
||||
/// e.g. `r.URL.Query().Get("host")` → `r.URL.Query.Get`
|
||||
fn normalize_chained_call(text: &str) -> String {
|
||||
let mut result = String::with_capacity(text.len());
|
||||
let bytes = text.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
match bytes[i] {
|
||||
b'(' => {
|
||||
// Skip from `(` to matching `)`, but only if followed by `.`
|
||||
// This handles `Query().Get` → `Query.Get`
|
||||
let mut depth = 1u32;
|
||||
let mut j = i + 1;
|
||||
while j < bytes.len() && depth > 0 {
|
||||
if bytes[j] == b'(' {
|
||||
depth += 1;
|
||||
} else if bytes[j] == b')' {
|
||||
depth -= 1;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
// If we're at end or next char is `.`, skip the parens
|
||||
if j >= bytes.len() || bytes[j] == b'.' {
|
||||
i = j;
|
||||
} else {
|
||||
// Keep the paren content (unusual case)
|
||||
result.push('(');
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
b'<' => break, // Stop at generic args
|
||||
_ => {
|
||||
result.push(bytes[i] as char);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,24 @@ use phf::{Map, phf_map};
|
|||
|
||||
pub static RULES: &[LabelRule] = &[
|
||||
// ─────────── Sources ───────────
|
||||
// Note: PHP `$` prefix is stripped by collect_idents, so match without `$`.
|
||||
LabelRule {
|
||||
matchers: &["$_GET", "$_POST", "$_REQUEST", "$_COOKIE"],
|
||||
matchers: &[
|
||||
"$_GET",
|
||||
"_GET",
|
||||
"$_POST",
|
||||
"_POST",
|
||||
"$_REQUEST",
|
||||
"_REQUEST",
|
||||
"$_COOKIE",
|
||||
"_COOKIE",
|
||||
"$_FILES",
|
||||
"_FILES",
|
||||
"$_SERVER",
|
||||
"_SERVER",
|
||||
"$_ENV",
|
||||
"_ENV",
|
||||
],
|
||||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
LabelRule {
|
||||
|
|
@ -20,17 +36,44 @@ pub static RULES: &[LabelRule] = &[
|
|||
matchers: &["escapeshellarg", "escapeshellcmd"],
|
||||
label: DataLabel::Sanitizer(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["basename"],
|
||||
label: DataLabel::Sanitizer(Cap::FILE_IO),
|
||||
},
|
||||
// ─────────── Sinks ─────────────
|
||||
LabelRule {
|
||||
matchers: &["system", "exec", "passthru", "shell_exec"],
|
||||
matchers: &[
|
||||
"system",
|
||||
"exec",
|
||||
"passthru",
|
||||
"shell_exec",
|
||||
"proc_open",
|
||||
"popen",
|
||||
],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["eval", "assert"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["include", "include_once", "require", "require_once"],
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["unserialize"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["move_uploaded_file", "copy", "file_put_contents", "fwrite"],
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["echo", "print"],
|
||||
label: DataLabel::Sink(Cap::HTML_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["mysqli_query", "pg_query"],
|
||||
matchers: &["mysqli_query", "pg_query", "query"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
];
|
||||
|
|
@ -41,16 +84,29 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"while_statement" => Kind::While,
|
||||
"for_statement" => Kind::For,
|
||||
"foreach_statement" => Kind::For,
|
||||
"do_statement" => Kind::While,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"throw_expression" => Kind::Return,
|
||||
"break_statement" => Kind::Break,
|
||||
"continue_statement" => Kind::Continue,
|
||||
|
||||
// structure
|
||||
"program" => Kind::SourceFile,
|
||||
"compound_statement" => Kind::Block,
|
||||
"else_clause" => Kind::Block,
|
||||
"else_if_clause" => Kind::Block,
|
||||
"function_definition" => Kind::Function,
|
||||
"method_declaration" => Kind::Function,
|
||||
"switch_statement" => Kind::Block,
|
||||
"switch_block" => Kind::Block,
|
||||
"case_statement" => Kind::Block,
|
||||
"default_statement" => Kind::Block,
|
||||
"try_statement" => Kind::Block,
|
||||
"catch_clause" => Kind::Block,
|
||||
"finally_clause" => Kind::Block,
|
||||
"colon_block" => Kind::Block,
|
||||
"class_declaration" => Kind::Block,
|
||||
|
||||
// data-flow
|
||||
"function_call_expression" => Kind::CallFn,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub static RULES: &[LabelRule] = &[
|
|||
},
|
||||
LabelRule {
|
||||
matchers: &["open"],
|
||||
label: DataLabel::Source(Cap::all()),
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &[
|
||||
|
|
@ -65,6 +65,14 @@ pub static RULES: &[LabelRule] = &[
|
|||
matchers: &["cursor.execute", "cursor.executemany"],
|
||||
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["send_file", "send_from_directory"],
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["os.path.realpath"],
|
||||
label: DataLabel::Sanitizer(Cap::FILE_IO),
|
||||
},
|
||||
];
|
||||
|
||||
pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
||||
|
|
@ -74,13 +82,24 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"for_statement" => Kind::For,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"raise_statement" => Kind::Return,
|
||||
"break_statement" => Kind::Break,
|
||||
"continue_statement" => Kind::Continue,
|
||||
|
||||
// structure
|
||||
"module" => Kind::SourceFile,
|
||||
"block" => Kind::Block,
|
||||
"else_clause" => Kind::Block,
|
||||
"elif_clause" => Kind::Block,
|
||||
"with_statement" => Kind::Block,
|
||||
"function_definition" => Kind::Function,
|
||||
"try_statement" => Kind::Block,
|
||||
"except_clause" => Kind::Block,
|
||||
"finally_clause" => Kind::Block,
|
||||
"class_definition" => Kind::Block,
|
||||
"decorated_definition" => Kind::Block,
|
||||
"match_statement" => Kind::Block,
|
||||
"case_clause" => Kind::Block,
|
||||
|
||||
// data-flow
|
||||
"call" => Kind::CallFn,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"if" => Kind::If,
|
||||
"unless" => Kind::If,
|
||||
"while" => Kind::While,
|
||||
"until" => Kind::While,
|
||||
"for" => Kind::For,
|
||||
|
||||
"return" => Kind::Return,
|
||||
|
|
@ -49,15 +50,26 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
// structure
|
||||
"program" => Kind::SourceFile,
|
||||
"body_statement" => Kind::Block,
|
||||
"do_block" => Kind::Block,
|
||||
"do_block" => Kind::Function,
|
||||
"then" => Kind::Block,
|
||||
"else" => Kind::Block,
|
||||
"elsif" => Kind::If,
|
||||
|
||||
"begin" => Kind::Block,
|
||||
"rescue" => Kind::Block,
|
||||
"ensure" => Kind::Block,
|
||||
"case" => Kind::Block,
|
||||
"when" => Kind::Block,
|
||||
"class" => Kind::Block,
|
||||
"module" => Kind::Block,
|
||||
"do" => Kind::Block,
|
||||
"block" => Kind::Function,
|
||||
|
||||
// data-flow
|
||||
"call" => Kind::CallFn,
|
||||
"method_call" => Kind::CallFn,
|
||||
"assignment" => Kind::Assignment,
|
||||
"method" => Kind::Function,
|
||||
"singleton_method" => Kind::Function,
|
||||
|
||||
// trivia
|
||||
"comment" => Kind::Trivia,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub static RULES: &[LabelRule] = &[
|
|||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &["fs::read_to_string", "source_file"],
|
||||
matchers: &["source_file"],
|
||||
label: DataLabel::Source(Cap::all()),
|
||||
},
|
||||
// ───────── Sanitizers ──────────
|
||||
|
|
@ -36,17 +36,29 @@ pub static RULES: &[LabelRule] = &[
|
|||
matchers: &["sink_html"],
|
||||
label: DataLabel::Sink(Cap::HTML_ESCAPE),
|
||||
},
|
||||
LabelRule {
|
||||
matchers: &[
|
||||
"fs::read_to_string",
|
||||
"fs::write",
|
||||
"fs::read",
|
||||
"File::open",
|
||||
"File::create",
|
||||
],
|
||||
label: DataLabel::Sink(Cap::FILE_IO),
|
||||
},
|
||||
];
|
||||
|
||||
pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
||||
// control-flow
|
||||
"if_expression" => Kind::If,
|
||||
"loop_expression" => Kind::InfiniteLoop,
|
||||
"loop_statement" => Kind::LoopBody,
|
||||
"while_statement" => Kind::While,
|
||||
"while_expression" => Kind::While,
|
||||
"for_statement" => Kind::For,
|
||||
"for_expression" => Kind::For,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"return_expression" => Kind::Return,
|
||||
"break_expression" => Kind::Break,
|
||||
"break_statement" => Kind::Break,
|
||||
"continue_expression" => Kind::Continue,
|
||||
|
|
@ -55,7 +67,17 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
// structure
|
||||
"source_file" => Kind::SourceFile,
|
||||
"block" => Kind::Block,
|
||||
"else_clause" => Kind::Block,
|
||||
"match_expression" => Kind::Block,
|
||||
"match_block" => Kind::Block,
|
||||
"match_arm" => Kind::Block,
|
||||
"unsafe_block" => Kind::Block,
|
||||
"function_item" => Kind::Function,
|
||||
"closure_expression" => Kind::Block,
|
||||
"async_block" => Kind::Block,
|
||||
"impl_item" => Kind::Block,
|
||||
"trait_item" => Kind::Block,
|
||||
"declaration_list" => Kind::Block,
|
||||
|
||||
// data-flow
|
||||
"call_expression" => Kind::CallFn,
|
||||
|
|
|
|||
|
|
@ -50,18 +50,36 @@ pub static KINDS: Map<&'static str, Kind> = phf_map! {
|
|||
"while_statement" => Kind::While,
|
||||
"for_statement" => Kind::For,
|
||||
"for_in_statement" => Kind::For,
|
||||
"for_of_statement" => Kind::For,
|
||||
"do_statement" => Kind::While,
|
||||
|
||||
"return_statement" => Kind::Return,
|
||||
"throw_statement" => Kind::Return,
|
||||
"break_statement" => Kind::Break,
|
||||
"continue_statement" => Kind::Continue,
|
||||
|
||||
// structure
|
||||
"program" => Kind::SourceFile,
|
||||
"statement_block" => Kind::Block,
|
||||
"else_clause" => Kind::Block,
|
||||
"function_declaration" => Kind::Function,
|
||||
"function_expression" => Kind::Function,
|
||||
"arrow_function" => Kind::Function,
|
||||
"method_definition" => Kind::Function,
|
||||
"generator_function_declaration" => Kind::Function,
|
||||
"generator_function" => Kind::Function,
|
||||
"switch_statement" => Kind::Block,
|
||||
"switch_body" => Kind::Block,
|
||||
"switch_case" => Kind::Block,
|
||||
"switch_default" => Kind::Block,
|
||||
"try_statement" => Kind::Block,
|
||||
"catch_clause" => Kind::Block,
|
||||
"finally_clause" => Kind::Block,
|
||||
"class_declaration" => Kind::Block,
|
||||
"class" => Kind::Block,
|
||||
"class_body" => Kind::Block,
|
||||
"abstract_class_declaration" => Kind::Block,
|
||||
"export_statement" => Kind::Block,
|
||||
"enum_declaration" => Kind::Trivia,
|
||||
|
||||
// data-flow
|
||||
"call_expression" => Kind::CallFn,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue