* feat: Add const_bound_vars tracking to prevent false positives in ownership checks
* feat: Introduce field interner and typed bounded vars for enhanced type tracking
* feat: Add typed_call_receivers and typed_bounded_dto_fields for enhanced type tracking
* feat: Centralize method name extraction with bare_method_name helper
* feat: Implement Phase-6 hierarchy fan-out for runtime virtual dispatch
* feat: Enhance C++ taint tracking with additional container operations and inline method resolution
* feat: Introduce field-sensitive points-to analysis for enhanced resource tracking
* feat: Implement Pointer-Phase 6 subscript handling for enhanced container analysis
* test: Add comprehensive tests for JavaScript control flow constructs and lattice operations
* docs: Update advanced analysis documentation with field-sensitive points-to and hierarchy fan-out details
* test: Add comprehensive tests for lattice algebra laws and SSA edge cases
* feat: Add destructured session user handling and safe user ID access patterns
* feat: Implement row-population reverse-walk for enhanced authorization checks
* feat: Enhance authorization checks with local alias chain for self-actor types
* feat: Introduce ActiveRecord query safety checks and enhance snippet extraction
* feat: Implement chained method call inner-gate rebinding for SSRF prevention
* feat: Add observability and error modules, enhance debug functionality, and implement theme context
* feat: Remove Auth Analysis page and update navigation to redirect to Explorer
* feat: Optimize SSA lowering by sharing results between taint engine and artifact extractor
* feat: Optimize SSA lowering by sharing results between taint engine and artifact extractor
* feat: Reset path-safe-suppressed spans before lowering to maintain analysis integrity
* fix(ssa): ungate debug_assert_bfs_ordering for release-tests build
The helper at src/ssa/lower.rs was gated `#[cfg(debug_assertions)]` while
the unit test at the bottom of the file was gated only `#[cfg(test)]`.
Since `cfg(test)` is set in release builds with `--tests` but
`cfg(debug_assertions)` is not, `cargo build --release --tests` failed
with E0425. Removing the gate fixes the build; the body is `debug_assert!`
only, so the helper is free in release. Also drop the gate at the call
site to avoid a `dead_code` warning when the lib is built without
`--tests`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(closure-capture): flip JS/TS fixtures to required-finding
The JS and TS closure-capture fixtures pinned the old broken behaviour
via `forbidden_findings: [{ "id_prefix": "taint-" }]`. The engine now
correctly traces taint through the closure boundary (env source captured
by an arrow function, sunk via `child_process.exec` inside the body), so
the formerly-forbidden finding is a true positive.
Match the Python sibling's shape — `required_findings` with
`id_prefix` + `min_count` plus a small `noise_budget` — and rewrite the
companion READMEs and the phase8_fragility_tests doc-comments from
"known gap" to "regression guard".
Verified:
- cargo test --release --test phase8_fragility_tests → 8/8 pass
- cargo test --release --lib bfs_assertion → pass
- corpus benchmark F1 = 0.9976 (TP=205, FP=1, FN=0) — unchanged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: Add OWASP mapping and baseline mutation hooks for enhanced security analysis
* feat: Introduce health module and enhance health score computation with calibration tests
* feat: Add expectations configuration and cleanup .gitignore for log files
* feat: Implement theme selection and enhance settings panel for triage sync
* feat: Suppress false positives for strcpy calls with literal sources in AST
* feat: Update analyse_function_ssa to return body CFG for accurate analysis
* feat: Add bug report and feature request templates for improved issue tracking
* feat: removed dev scripts
* feat: update README.md for clarity and consistency in fixture descriptions
* feat: removed dev docs
* feat: clean up error handling and UI elements for improved user experience
* feat: adjust button sizes in HeaderBar for better UI consistency
* feat: enhance taint analysis with additional context for sanitizer and taint findings
* cargo fmt
* prettier
* refactor: simplify conditional checks and improve code readability in AST and screenshot capture scripts
* feat: add script to frame PNG screenshots with brand gradient
* feat: add fuzzing support with new targets and CI workflows
* refactor: streamline match expressions and improve formatting in CLI and output handling
* feat: enhance configuration display with detailed output options
* feat: stage demo configuration for improved CLI screenshot output
* feat: expose merge_configs function for user-configurable settings
* refactor: simplify code structure and improve readability in config handling
* refactor: improve descriptions for vulnerability patterns in various languages
* feat: update MIT License section with additional usage details and copyright information
* feat: update screenshots
* refactor: update build process and paths for frontend assets
* feat: add cross-file taint fuzzing target and supporting dictionary
* refactor: clean up formatting and comments in fuzz configuration and example files
* refactor: remove outdated comments and clean up CI configuration files
* chore: update changelog dates and improve formatting in documentation
* refactor: update Cargo.toml and CI configuration for improved packaging and build process
* refactor: enhance quote-stripping logic to prevent panics and add regression tests
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .github | ||
| action-scripts | ||
| assets | ||
| benches | ||
| docs | ||
| frontend | ||
| fuzz | ||
| scripts | ||
| src | ||
| tests | ||
| tools/docgen | ||
| .gitignore | ||
| about.hbs | ||
| about.toml | ||
| action.yml | ||
| AI-POLICY.md | ||
| book.toml | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLA.md | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| default-nyx.conf | ||
| deny.toml | ||
| LICENSE | ||
| README.md | ||
| ROADMAP.md | ||
| SECURITY.md | ||
| THIRDPARTY-LICENSES.html | ||
A local-first security scanner with a browser UI. Scan your repo and triage in your browser, with no cloud and no account.

Scan locally, browse locally
Nyx runs a cross-language taint analysis on your repository, then serves the results to a React UI bound to 127.0.0.1. You get a finding list with severity, evidence, and a step-by-step flow visualiser that walks the dataflow from source → sanitizer → sink. Triage decisions persist to .nyx/triage.json, which commits alongside your code so the team shares one triage state.
cargo install nyx-scanner
nyx scan # runs the analyzer, caches findings in .nyx/
nyx serve # opens http://localhost:9700 in your browser
Everything stays on your machine: loopback-only bind, host-header enforcement, CSRF on every mutation, no telemetry, no login.

What's in the UI
| Page | What it shows |
|---|---|
| Overview | Dashboard: finding counts by severity, top offenders, engine profile summary |
| Findings | Browsable list with severity badges, triage status, rule filter, language filter |
| Finding detail | Flow-path visualiser with numbered steps (source → sanitizer → sink), code snippets, evidence, cross-file markers, triage dropdown |
| Triage | Bulk update states (open, investigating, fixed, false_positive, accepted_risk, suppressed), audit trail, import/export JSON |
| Explorer | File tree with per-file symbol list and finding overlay |
| Scans | Run history, metrics, diff two scans to see what changed |
| Rules | Built-in and custom rules per language; add rules from the UI |
| Config | Live config editor; reload without restart |
nyx serve flags: --port <N> (default 9700), --host <addr> (loopback only: 127.0.0.1, localhost, or ::1), --no-browser. See [server] in nyx.conf for persistent settings, and the Browser UI guide for the page-by-page UI tour and security model.
CLI for CI
The same engine runs headless for CI pipelines. SARIF output uploads directly to GitHub Code Scanning.

# Fail the job on medium or higher, emit SARIF
nyx scan --format sarif --fail-on MEDIUM > results.sarif
# Ad-hoc JSON, no index
nyx scan ./server --format json --index off
# AST patterns only (fastest; skips CFG + taint)
nyx scan --mode ast
# Engine-depth shortcut: fast | balanced (default) | deep
# `deep` adds symex + demand-driven backwards taint for higher precision at ~2-3× cost
nyx scan --engine-profile deep
Forward cross-file taint runs in every profile. Symex and the demand-driven backwards walk are opt-in. Turn them on either via --engine-profile deep, or individually (--symex, --backwards-analysis). See the CLI reference for the full toggle matrix.
GitHub Action
- uses: elicpeter/nyx@v0.5.0
with:
format: sarif
fail-on: MEDIUM
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: nyx-results.sarif
Inputs: path, version, format (sarif|json|console), fail-on, args, token. Outputs: finding-count, sarif-file, exit-code, nyx-version. Linux and macOS runners (x86_64, ARM64).
Install
Cargo (recommended):
cargo install nyx-scanner
Pre-built binaries: Grab the archive for your platform from Releases, verify against SHA256SUMS (and the detached SHA256SUMS.asc GPG signature, when present), unzip, and drop nyx on your PATH.
# Optional: verify the checksum file's GPG signature (when SHA256SUMS.asc is published)
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing
unzip nyx-x86_64-unknown-linux-gnu.zip && chmod +x nyx && sudo mv nyx /usr/local/bin/
From source:
git clone https://github.com/elicpeter/nyx.git
cd nyx && cargo build --release
Requires stable Rust 1.88+. The frontend is compiled and embedded in the binary at build time, so there is no separate install step for nyx serve.
Languages
All 10 languages parse via tree-sitter and run through the full pipeline, but rule depth and engine coverage are uneven. Benchmark F1 on the 433-case corpus at tests/benchmark/ground_truth.json is 100% for nine of ten languages and 94.1% for Go, so F1 alone no longer separates the tiers. Tiering reflects rule depth, gated-sink coverage, and structural idioms the synthetic corpus does not fully stress:
| Tier | Languages | F1 | Use as a CI gate? |
|---|---|---|---|
| Stable | Python, JavaScript, TypeScript | 100% | Yes |
| Beta | Java, PHP, Ruby, Rust, Go | 94.1% to 100% | Yes, with light FP triage |
| Preview | C, C++ | 100% on synthetic corpus | No. STL container flow, builder chains, and inline class member functions are tracked, but deep pointer aliasing and function pointers are not. Pair with clang-tidy or Clang Static Analyzer |
Aggregate rule-level F1: 99.3% (P=0.991, R=0.995). The single open FN is cve-go-2023-3188-vulnerable (owncast SSRF); the two open FPs (go-safe-007, go-safe-009) also sit on the Go side. Per-dimension detail and known blind spots live on the Language maturity page.
Validated against real CVEs
The corpus also holds a small set of vulnerable/patched pairs extracted from published advisories, so the benchmark floor is defended by regression protection on demonstrably real bugs rather than just synthetic analogues. Nyx fires on the vulnerable file and emits zero findings on the patched file for each pair.
| CVE | Project | Language | Class |
|---|---|---|---|
| CVE-2023-48022 | Ray | Python | Command injection |
| CVE-2017-18342 | PyYAML | Python | Deserialization |
| CVE-2019-14939 | mongo-express | JavaScript | Code execution (eval) |
| CVE-2025-64430 | Parse Server | JavaScript | SSRF |
| CVE-2023-26159 | follow-redirects | TypeScript | SSRF |
| CVE-2022-30323 | hashicorp/go-getter | Go | Command injection |
| CVE-2024-31450 | owncast | Go | Path traversal |
| CVE-2015-7501 | Apache Commons Collections | Java | Deserialization |
| CVE-2017-12629 | Apache Solr | Java | Command injection |
| CVE-2013-0156 | Ruby on Rails | Ruby | Deserialization |
| CVE-2020-8130 | Rake | Ruby | Command injection |
| CVE-2017-9841 | PHPUnit | PHP | Code execution (eval) |
| CVE-2018-15133 | Laravel | PHP | Deserialization |
| CVE-2016-3714 | ImageMagick (ImageTragick) | C | Command injection |
| CVE-2019-18634 | sudo (pwfeedback) | C | Memory safety |
| CVE-2019-13132 | ZeroMQ libzmq | C++ | Memory safety |
| CVE-2022-1941 | Protocol Buffers | C++ | Memory safety |
cve-go-2023-3188-vulnerable (owncast SSRF) ships in the corpus too but is currently a known FN; it will move into the table once the engine fires on it.
Fixtures live under tests/benchmark/cve_corpus/ with upstream attribution headers.
How it works
Two passes over the filesystem, with an optional SQLite index to skip unchanged files:
- Pass 1: parse each file via tree-sitter, build an intra-procedural CFG (petgraph), lower to pruned SSA (Cytron phi insertion over dominance frontiers), and export per-function summaries (source/sanitizer/sink caps, taint transforms, points-to, callees).
- Summary merge: union all per-file summaries into a
GlobalSummariesmap. - Pass 2: re-analyze each file with cross-file context under bounded context sensitivity (k=1 inlining for intra-file callees, SCC fixpoint capped at 64 iterations, and summary fallback for callees above the inline body-size cap). A forward dataflow worklist propagates taint through the SSA lattice with guaranteed convergence. Call-graph SCCs iterate to fixed-point (within the cap) so mutually recursive functions get accurate summaries.
- Rank, dedupe, emit: findings are scored by severity × evidence strength × source-kind exploitability, then emitted to console, JSON, or SARIF.
Detector families: taint (cross-file source→sink), CFG structural (auth gaps, unguarded sinks, resource leaks), state model (use-after-close, double-close, must-leak, unauthed-access), AST patterns (tree-sitter structural match). Full detector docs: Detectors.
Configuration
Config merges nyx.conf (defaults) and nyx.local (your overrides) from the platform config directory (~/.config/nyx/ on Linux, ~/Library/Application Support/nyx/ on macOS, %APPDATA%\elicpeter\nyx\config\ on Windows).
[scanner]
mode = "full" # full | ast | cfg | taint
min_severity = "Medium"
[server]
host = "127.0.0.1"
port = 9700
open_browser = true
# Project-specific sanitizer
[[analysis.languages.javascript.rules]]
matchers = ["escapeHtml"]
kind = "sanitizer"
cap = "html_escape"
Or add rules interactively: nyx config add-rule --lang javascript --matcher escapeHtml --kind sanitizer --cap html_escape. Caps: env_var, html_escape, shell_escape, url_encode, json_parse, file_io, fmt_string, sql_query, deserialize, ssrf, code_exec, crypto, unauthorized_id, all. Full schema: Configuration.
Status
Under active development. APIs, detector behavior, and configuration options may change between releases. Rule-level F1 on the 433-case corpus is the CI regression floor; per-language detail lives in tests/benchmark/RESULTS.md.
Taint analysis is interprocedural. Persisted per-function SSA summaries carry per-return-path transforms and parameter-granularity points-to, and call-graph SCCs (including SCCs that span files) iterate to a joint fixed-point. The default balanced profile also runs k=1 context-sensitive inlining for intra-file callees. Symex (with cross-file and interprocedural frames) and the demand-driven backwards walk are opt-in. Enable them individually with --symex and --backwards-analysis, or together with --engine-profile deep.
Limitations:
- Interprocedural precision is bounded rather than unlimited. Context-sensitive inlining is k=1 with a callee body-size cap, and SCC fixed-point has an iteration cap. When the engine hits a bound it falls back to summaries and records an
engine_noteon the finding. - Cross-language calls (FFI, subprocess, WASM) are not traversed. Each language is analysed independently.
- Several language features are not modeled: macros, most dynamic dispatch, aliased imports, reflection.
- C/C++ are preview tier. STL container flow, builder chains, and inline class member functions are tracked now; deep pointer aliasing and function pointers are not. A clean report should not be read as a clean audit. Pair with a clang-based tool before using as a hard CI gate.
- Results may contain false positives or false negatives; manual review is expected.
Documentation
Browse the full docs site at elicpeter.github.io/nyx.
- Quick Start · CLI Reference · Installation
nyx serve· Output Formats · Configuration- How it works · Detectors (Taint, CFG, State, AST Patterns)
- Rule Reference · Language Maturity · Advanced Analysis · Auth Analysis
Contributing
Contributions are welcome.
Nyx is open source and will always have a fully open-source core. To support long-term development and keep the project sustainable, contributors may be asked to sign a Contributor License Agreement before their first merged contribution.
Run sh scripts/check.sh before submitting. See CONTRIBUTING.md for the full guide, including how to add rules and support new languages. Open an issue for crashes, panics, or suspicious results; attach a minimal snippet and the Nyx version.
AI Disclosure
- Engine code (taint, SSA, CFG, call graph, abstract interp, symbolic exec): predominantly human-written. AI was used selectively for refactors and boilerplate, with all merges human-reviewed.
- Docs and most of this README: AI-generated from the code and hand-edited. Report doc/code drift as a bug.
- Test fixtures and
expected.yamlfiles: AI-assisted drafting, human-audited before landing. - Frontend UI (React app): built with AI assistance, human-reviewed.
As with any static analyzer, validate findings against your own corpus before using Nyx as a CI gate.
License
GNU General Public License v3.0 or later (GPL-3.0-or-later). The optional smt feature bundles Z3 (MIT-licensed); distributors of binaries built with --features smt should include Z3's license in their attribution. Full text in LICENSE; third-party dependencies in THIRDPARTY-LICENSES.html.