Skip to main content

nyx_scanner/
lib.rs

1//! Multi-language static vulnerability scanner.
2//!
3//! Tree-sitter parsing, petgraph CFGs, SSA-based dataflow, and cross-file
4//! taint analysis with a capability-based sanitizer system. Supports Rust,
5//! C, C++, Java, Go, PHP, Python, Ruby, TypeScript, and JavaScript.
6//!
7//! This crate is both the `nyx` binary and a library for programmatic
8//! scanning. Most internal modules are public for testing and downstream
9//! tooling, but the stable contract is [`scan_no_index`] plus the types
10//! it returns.
11//!
12//! For a description of how the analysis pipeline works, see the
13//! [how-it-works handbook](https://github.com/elicpeter/nyx/blob/master/docs/how-it-works.md).
14//! Per-detector documentation lives on the [`taint`], [`cfg_analysis`],
15//! [`state`], [`patterns`], and [`auth_analysis`] module pages.
16//!
17//! # Entry points
18//!
19//! [`scan_no_index`] runs a full two-pass scan over a directory tree and
20//! returns a flat list of [`commands::scan::Diag`] values. It does not
21//! touch a SQLite index; every file is analysed from disk on each call.
22//!
23//! ```no_run
24//! use nyx_scanner::{scan_no_index, utils::Config};
25//! use std::path::Path;
26//!
27//! let config = Config::default();
28//! let findings = scan_no_index(Path::new("/path/to/project"), &config).unwrap();
29//! for diag in &findings {
30//!     println!("{} at {}:{}", diag.id, diag.path, diag.line);
31//! }
32//! ```
33//!
34//! For incremental rescanning backed by a SQLite index, use
35//! [`commands::scan::scan_with_index_parallel`] directly.
36//!
37//! # Key types
38//!
39//! | Type | Purpose |
40//! |------|---------|
41//! | [`utils::config::Config`] | Top-level scanner config (load from `nyx.conf` or construct in code) |
42//! | [`commands::scan::Diag`] | A single finding: location, severity, rule ID, structured evidence |
43//! | [`evidence::Evidence`] | Source/sink spans, flow steps, sanitizer annotations, engine notes |
44//! | [`evidence::Confidence`] | Low / Medium / High confidence tag |
45//! | [`labels::Cap`] | Bitflag capability set describing what a taint flow can reach |
46//! | [`symbol::Lang`] | Supported language enum |
47//! | [`symbol::FuncKey`] | Canonical cross-file function identity |
48//!
49//! # Reading findings
50//!
51//! Each [`commands::scan::Diag`] carries:
52//!
53//! - `path`, `line`, `col` — source location of the sink
54//! - `id` — rule identifier (e.g. `taint-unsanitised-flow`, `cfg-auth-gap`)
55//! - `severity` — Critical / High / Medium / Low / Info
56//! - `confidence` — Low / Medium / High; capped at Medium when an engine
57//!   budget was hit
58//! - `rank_score` — deterministic attack-surface score for truncation ordering
59//! - `evidence` — optional [`evidence::Evidence`] with source/sink spans,
60//!   flow steps, and [`engine_notes::EngineNote`] values describing precision loss
61//!
62//! Engine notes communicate when a bound was hit. A finding carrying
63//! `EngineNote::OriginsTruncated` or `EngineNote::SccBudgetExhausted` is
64//! still real, but the engine had less information than it would have had
65//! without the cap.
66//!
67//! # Module map
68//!
69//! | Module | Role |
70//! |--------|------|
71//! | [`ast`] | Tree-sitter parsing and two-pass analysis dispatch |
72//! | [`mod@cfg`] | CFG construction from ASTs |
73//! | [`ssa`] | SSA lowering and optimization passes |
74//! | [`taint`] | Forward SSA taint analysis |
75//! | [`cfg_analysis`] | Structural CFG checks (auth gaps, resource leaks, error paths) |
76//! | [`state`] | Resource lifecycle and state-machine analysis |
77//! | [`patterns`] | Pattern-based AST checks |
78//! | [`auth_analysis`] | Missing authorization / ownership checks |
79//! | [`callgraph`] | Whole-program call graph and SCC analysis |
80//! | [`summary`] | Per-function summaries for cross-file resolution |
81//! | [`labels`] | Source, sanitizer, and sink rule registries per language |
82//! | [`symex`] | Symbolic execution for witness generation and path feasibility |
83//! | [`abstract_interp`] | Interval and string bounds propagation for sink suppression |
84//! | [`constraint`] | Path constraint solving and infeasible-path pruning |
85//! | [`evidence`] | Finding provenance and confidence types |
86//! | [`suppress`] | Inline `nyx:ignore` directive handling |
87//! | [`output`] | JSON and SARIF serialization |
88//! | [`database`] | SQLite index pool and schema |
89//! | [`walk`] | Filesystem traversal with batched delivery |
90
91pub mod abstract_interp;
92pub mod ast;
93pub mod auth_analysis;
94pub mod callgraph;
95pub mod cfg;
96pub mod cfg_analysis;
97pub mod cli;
98pub mod commands;
99pub mod constraint;
100pub mod convergence_telemetry;
101pub mod database;
102pub mod engine_notes;
103pub mod errors;
104pub mod evidence;
105pub mod fmt;
106pub mod interop;
107pub mod labels;
108pub mod output;
109pub mod patterns;
110pub mod pointer;
111pub mod rank;
112pub mod rust_resolve;
113#[cfg(feature = "serve")]
114pub mod server;
115pub mod ssa;
116pub mod state;
117pub mod summary;
118pub mod suppress;
119pub mod symbol;
120pub mod symex;
121pub mod taint;
122pub mod utils;
123pub mod walk;
124
125use errors::NyxResult;
126use std::path::Path;
127use utils::config::Config;
128
129/// Run a two-pass scan over `root` without an incremental index.
130///
131/// Every file under `root` is analysed from disk on each call; no SQLite
132/// state is read or written. The walker respects `.gitignore` files when
133/// `cfg.scanner.read_vcsignore` is true (the default), skips hidden files
134/// and symlinks unless the config enables them, and excludes the directories
135/// and extensions listed in `cfg.scanner.excluded_*`.
136///
137/// Returns one [`commands::scan::Diag`] per finding. The list is unsorted;
138/// call [`rank::rank_diags`] if you need findings ordered by exploitability.
139///
140/// For indexed / incremental rescanning use
141/// [`commands::scan::scan_with_index_parallel`] instead.
142pub fn scan_no_index(root: &Path, cfg: &Config) -> NyxResult<Vec<commands::scan::Diag>> {
143    commands::scan::scan_filesystem(root, cfg, false)
144}