mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-21 20:18:06 +02:00
Fix fn and bump frontend packages (#57)
* chore(deps): update frontend dependencies to latest versions * fix: update reconnectTimer type and adjust tsconfig paths for consistency * fix: add toast to dependencies in FindingsPage component * fix: add toast to dependencies in FindingsPage component * fix: update language maturity metrics and improve Go validation handling * fix: update CHANGELOG with recent enhancements and dependency bumps * fix: format reconnectTimer initialization for improved readability
This commit is contained in:
parent
281699faae
commit
832533a8cd
15 changed files with 1210 additions and 1334 deletions
|
|
@ -146,7 +146,10 @@ pub(super) fn extract_const_string_arg(
|
|||
let mut cursor = args.walk();
|
||||
let arg = args.named_children(&mut cursor).nth(index)?;
|
||||
match arg.kind() {
|
||||
"string" | "string_literal" => {
|
||||
// `string` / `string_literal` cover JS/TS, Python, Java, PHP, C/C++, Ruby, Rust;
|
||||
// `interpreted_string_literal` / `raw_string_literal` cover Go's
|
||||
// tree-sitter grammar (double-quoted vs. backtick-quoted forms).
|
||||
"string" | "string_literal" | "interpreted_string_literal" | "raw_string_literal" => {
|
||||
let raw = text_of(arg, code)?;
|
||||
if raw.len() >= 2 {
|
||||
Some(raw[1..raw.len() - 1].to_string())
|
||||
|
|
@ -906,8 +909,10 @@ pub(super) fn extract_kwargs(call_node: Node, code: &[u8]) -> Vec<(String, Vec<S
|
|||
/// Policy is deliberately narrow and conservative: only literals that contain
|
||||
/// *known-dangerous* payloads earn a strip credit, so an arbitrary
|
||||
/// `.replace("foo", "bar")` is never promoted to a sanitizer.
|
||||
/// * `..`, `/`, `\\` → path-traversal → `Cap::FILE_IO`
|
||||
/// * `<`, `>` → HTML metachars → `Cap::HTML_ESCAPE`
|
||||
/// * `..`, `/`, `\\` → path-traversal → `Cap::FILE_IO`
|
||||
/// * `<`, `>` → HTML metachars → `Cap::HTML_ESCAPE`
|
||||
/// * `;`, `|`, `&`, `$`, `\`` → shell metachars → `Cap::SHELL_ESCAPE`
|
||||
/// * `'`, `"`, `--` → SQL metachars → `Cap::SQL_QUERY`
|
||||
pub(super) fn caps_stripped_by_literal_pattern(search: &str) -> Cap {
|
||||
let mut caps = Cap::empty();
|
||||
if search.contains("..") || search.contains('/') || search.contains('\\') {
|
||||
|
|
@ -916,6 +921,17 @@ pub(super) fn caps_stripped_by_literal_pattern(search: &str) -> Cap {
|
|||
if search.contains('<') || search.contains('>') {
|
||||
caps |= Cap::HTML_ESCAPE;
|
||||
}
|
||||
if search.contains(';')
|
||||
|| search.contains('|')
|
||||
|| search.contains('&')
|
||||
|| search.contains('$')
|
||||
|| search.contains('`')
|
||||
{
|
||||
caps |= Cap::SHELL_ESCAPE;
|
||||
}
|
||||
if search.contains('\'') || search.contains('"') || search.contains("--") {
|
||||
caps |= Cap::SQL_QUERY;
|
||||
}
|
||||
caps
|
||||
}
|
||||
|
||||
|
|
@ -1032,6 +1048,51 @@ pub(super) fn detect_rust_replace_chain_sanitizer(call_ast: Node, code: &[u8]) -
|
|||
None
|
||||
}
|
||||
|
||||
/// Recognise a Go `strings.Replace(s, OLD, NEW, n)` /
|
||||
/// `strings.ReplaceAll(s, OLD, NEW)` call that provably strips one of the
|
||||
/// known-dangerous metacharacter classes from its first argument.
|
||||
///
|
||||
/// Returns the union of caps stripped, or `None` when the pattern doesn't
|
||||
/// apply (so the caller falls back to normal unresolved-call propagation).
|
||||
///
|
||||
/// Mirrors [`detect_rust_replace_chain_sanitizer`] but for the single-call
|
||||
/// (non-method-chain) Go shape. The caller wires the resulting cap into
|
||||
/// the call's [`crate::labels::DataLabel::Sanitizer`] label, which the
|
||||
/// taint engine consumes via the standard sanitizer pathway — taint flows
|
||||
/// in on `s`, the matching cap is stripped from the result.
|
||||
pub(super) fn detect_go_replace_call_sanitizer(call_ast: Node, code: &[u8]) -> Option<Cap> {
|
||||
if call_ast.kind() != "call_expression" {
|
||||
return None;
|
||||
}
|
||||
// The call's `function` field is a `selector_expression` — `operand`
|
||||
// is the package ident (`strings`), `field` is the method ident.
|
||||
let func = call_ast.child_by_field_name("function")?;
|
||||
if func.kind() != "selector_expression" {
|
||||
return None;
|
||||
}
|
||||
let operand = func.child_by_field_name("operand")?;
|
||||
if text_of(operand, code).as_deref() != Some("strings") {
|
||||
return None;
|
||||
}
|
||||
let field = func.child_by_field_name("field")?;
|
||||
let method_name = text_of(field, code)?;
|
||||
if method_name != "Replace" && method_name != "ReplaceAll" {
|
||||
return None;
|
||||
}
|
||||
// Args layout: (s, old, new[, n]). Need positional args 1 (old) and
|
||||
// 2 (new) to be string literals.
|
||||
let old_lit = extract_const_string_arg(call_ast, 1, code)?;
|
||||
let new_lit = extract_const_string_arg(call_ast, 2, code)?;
|
||||
|
||||
// If the replacement itself reintroduces a dangerous sequence, don't
|
||||
// credit the strip — matches the Rust chain detector's policy.
|
||||
if !caps_stripped_by_literal_pattern(&new_lit).is_empty() {
|
||||
return None;
|
||||
}
|
||||
let caps = caps_stripped_by_literal_pattern(&old_lit);
|
||||
if caps.is_empty() { None } else { Some(caps) }
|
||||
}
|
||||
|
||||
/// Like `first_call_ident`, but also checks if `n` itself is a call node.
|
||||
/// `first_call_ident` only searches children, so when `n` IS the call
|
||||
/// expression (e.g. the argument `sanitize(cmd)`), this function catches it.
|
||||
|
|
|
|||
|
|
@ -49,12 +49,13 @@ use imports::{extract_import_bindings, extract_promisify_aliases};
|
|||
#[cfg(test)]
|
||||
use literals::has_sql_placeholders;
|
||||
use literals::{
|
||||
arg0_kind_and_interpolation, call_ident_of, def_use, detect_rust_replace_chain_sanitizer,
|
||||
extract_arg_callees, extract_arg_string_literals, extract_arg_uses, extract_const_keyword_arg,
|
||||
extract_const_string_arg, extract_destination_field_idents, extract_kwargs,
|
||||
extract_literal_rhs, find_call_node, find_call_node_deep, find_chained_inner_call,
|
||||
has_keyword_arg, has_only_literal_args, is_parameterized_query_call,
|
||||
java_chain_arg0_kind_for_method, ruby_chain_arg0_for_method, walk_chain_inner_call_args,
|
||||
arg0_kind_and_interpolation, call_ident_of, def_use, detect_go_replace_call_sanitizer,
|
||||
detect_rust_replace_chain_sanitizer, extract_arg_callees, extract_arg_string_literals,
|
||||
extract_arg_uses, extract_const_keyword_arg, extract_const_string_arg,
|
||||
extract_destination_field_idents, extract_kwargs, extract_literal_rhs, find_call_node,
|
||||
find_call_node_deep, find_chained_inner_call, has_keyword_arg, has_only_literal_args,
|
||||
is_parameterized_query_call, java_chain_arg0_kind_for_method, ruby_chain_arg0_for_method,
|
||||
walk_chain_inner_call_args,
|
||||
};
|
||||
use params::{
|
||||
compute_container_and_kind, extract_param_meta, inject_framework_param_sources,
|
||||
|
|
@ -1788,6 +1789,27 @@ pub(super) fn push_node<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// Pattern-based sanitizer synthesis for Go's `strings.Replace` /
|
||||
// `strings.ReplaceAll`. When the call's OLD literal contains a known
|
||||
// dangerous payload (shell metachars, path-traversal, HTML, SQL) and
|
||||
// the NEW literal does not reintroduce one, treat the call as a
|
||||
// Sanitizer over the matching caps. Same precedence as the Rust
|
||||
// chain synthesis: explicit Sanitizer labels win, but otherwise the
|
||||
// synthesised label feeds the standard sanitizer pathway in the
|
||||
// taint engine. Motivated by helpers like
|
||||
// `func validate(s string) string { return strings.ReplaceAll(s, ";", "") }`
|
||||
// whose return is appended to a slice that later flows into
|
||||
// `exec.Command(slice[i])`.
|
||||
if lang == "go" && !labels.iter().any(|l| matches!(l, DataLabel::Sanitizer(_))) {
|
||||
if let Some(cn) = call_ast {
|
||||
if cn.kind() == "call_expression" {
|
||||
if let Some(caps) = detect_go_replace_call_sanitizer(cn, code) {
|
||||
labels.push(DataLabel::Sanitizer(caps));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shape-based sanitizer synthesis for Ruby ActiveRecord query methods.
|
||||
// The static label table marks `where` / `order` / `pluck` / `group` /
|
||||
// `having` / `joins` as `Sink(SQL_QUERY)` because their string-interpolation
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue