16 KiB
Contributing to Nyx
Thank you for your interest in improving Nyx. This guide covers everything you need to contribute effectively.
User-facing documentation lives at elicpeter.github.io/nyx; the source for those pages is in docs/.
Please read our Code of Conduct before participating.
Table of Contents
- Development Setup
- Project Layout
- How to Add a New AST Pattern
- How to Add a New Taint Rule
- How to Add a New Language
- Testing
- Pull Request Guidelines
- Bug Reports
- Feature Requests
- Release Process
Development Setup
Prerequisites
- Rust 1.88+ (edition 2024)
- Git
- Node 20+ — only if you touch the browser UI under
frontend/(thenyx serveweb app). Pure-Rust changes do not need it.
Building
git clone https://github.com/elicpeter/nyx.git
cd nyx
cargo build # Debug build
cargo build --release # Release build
cargo install --path . # Install as `nyx` binary
Running Quality Checks
The fastest way to reproduce CI locally is the bundled script — it runs the same commands CI runs (fmt, Clippy, tests, and the frontend checks):
./scripts/check.sh # Mirror CI: fmt + clippy + tests (+ frontend)
./scripts/check.sh --rust-only # Skip the frontend checks
./scripts/fix.sh # Auto-fix: cargo fmt + clippy --fix + prettier/eslint
Or run the steps individually:
cargo test --all-features # Tests, incl. tests/ integration suite
cargo clippy --all-targets --all-features -- -D warnings # Lint, warnings = errors
cargo fmt # Format code
cargo fmt -- --check # Check formatting without modifying
Match CI exactly. CI lints and tests with
--all-targets --all-features. The oldercargo test --bin nyx/cargo clippy --allcommands skip thetests/integration suite and feature-gated code, so they can pass locally while CI fails. Prefer./scripts/check.sh.
Note
: The first build downloads and compiles tree-sitter grammars for all 10 languages. Subsequent builds are faster.
Benchmarks
cargo bench --bench scan_bench
Benchmark fixtures live in benches/fixtures/. Criterion produces HTML reports in target/criterion/.
Project Layout
New here?
docs/how-it-works.mdwalks the analysis pipeline end to end (with a diagram), anddocs/detectors/taint.mdcovers the taint engine. The easiest first contribution is usually a new AST pattern (see below) — small, self-contained, and well templated.
src/
main.rs CLI entry point
lib.rs Library re-exports (benchmarks, integration tests)
cli.rs Clap command definitions
commands/ Subcommand handlers (scan, index, list, clean, config, serve)
ast.rs Entry points for both passes; tree-sitter parsing
cfg/ CFG construction from AST, type hierarchy
cfg_analysis/ CFG structural detectors
guards.rs Unguarded sink detection (dominator analysis)
auth.rs Auth gap detection
resources.rs Resource leak detection
error_handling.rs Error fallthrough detection
unreachable.rs Unreachable security code detection
rules.rs Guard rules, auth rules, resource pairs
ssa/ SSA IR (lowering, optimization passes, const prop)
taint/ SSA-based taint engine (sole engine since 0.5.0)
mod.rs Facade + JS two-level solve
domain.rs Shared lattice types (VarTaint, Cap, TaintOrigin)
ssa_transfer/ Block-level worklist, k=1 inline cache, gated sinks
backwards.rs Demand-driven backwards taint walk (opt-in)
path_state.rs Predicate tracking and contradiction pruning
state/
engine.rs Generic monotone dataflow engine (Transfer<S: Lattice>)
transfer.rs DefaultTransfer: resource lifecycle + auth state
summary/ FuncSummary, SsaFuncSummary, GlobalSummaries, hierarchy index
abstract_interp/ Interval + string prefix/suffix domains
pointer/ Field-sensitive points-to (Steensgaard-style)
symex/ Symbolic execution + witness generation
constraint/ Path-constraint solving (optional Z3 via `smt` feature)
auth_analysis/ Rust auth rule (`rs.auth.missing_ownership_check`) + sink classes
suppress/ Inline `nyx:ignore` directive parsing
labels/ Per-language label rules (one file per language)
patterns/ Per-language AST pattern queries (one file per language)
callgraph.rs Call graph construction (petgraph), SCC, topo sort
database.rs SQLite indexing via r2d2 pool
rank.rs Attack-surface ranking
fmt.rs Console output formatting
output.rs SARIF 2.1 builder
walk.rs Parallel file walker (ignore crate, respects .gitignore)
symbol/ Symbol interning (SymbolId)
server/ `nyx serve` HTTP layer, routes, triage sync
interop.rs Cross-language interop edges
engine_notes.rs Direction-aware engine notes (UnderReport / OverReport / Bail)
evidence.rs Structured evidence emitted with each finding
errors.rs NyxError, NyxResult types
utils/
config.rs TOML config loading, merging, Config struct
How to Add a New AST Pattern
AST patterns are the simplest detector to add. Each pattern is a tree-sitter query that matches a structural code construct.
Step-by-step
-
Pick the language file under
src/patterns/<lang>.rs. -
Choose the metadata:
Field Options Guidelines ID <lang>.<category>.<specific>e.g. py.cmdi.os_popenTier AorBA= presence alone is high-signal;B= query includes a heuristic guardSeverity High,Medium,LowHigh: command exec, deser, banned functions. Medium: SQL concat, reflection, XSS. Low: weak crypto, code quality. Category See PatternCategoryenumCommandExec,CodeExec,Deserialization,SqlInjection,PathTraversal,Xss,Crypto,Secrets,InsecureTransport,Reflection,MemorySafety,Prototype,CodeQuality -
Write the tree-sitter query:
Pattern { id: "py.cmdi.os_popen", description: "os.popen() shell command execution", query: r#"(call function: (attribute object: (identifier) @pkg (#eq? @pkg "os") attribute: (identifier) @fn (#eq? @fn "popen"))) @vuln"#, severity: Severity::High, tier: PatternTier::A, category: PatternCategory::CommandExec, },The query must capture a
@vulnnode. That node's span determines the reported location. -
Test it:
cargo test --bin nyx -
Update docs: Add the new rule to
docs/rules/<lang>.md.
Tips
- Use the tree-sitter playground to develop and test queries.
- Avoid duplicating taint coverage. If the same function is already a labeled sink in
src/labels/<lang>.rs, the AST pattern is still useful for--mode ast, but use a distinct ID namespace. The dedup pass prevents exact-duplicate findings at the same location. - Test with real-world code to check false positive rates before choosing a tier.
How to Add a New Taint Rule
Taint rules define sources (where untrusted data enters), sinks (where dangerous operations happen), and sanitizers (where data is made safe).
Step-by-step
-
Open the language file in
src/labels/<lang>.rs. -
Add an entry to the
RULESslice:LabelRule { matchers: &["dangerouslySetInnerHTML"], label: DataLabel::Sink(Cap::HTML_ESCAPE), }, -
Choose the right label type:
Type Purpose Example DataLabel::Source(cap)Introduces tainted data env::var,req.bodyDataLabel::Sanitizer(cap)Strips matching capability bits html_escape,encodeURIComponentDataLabel::Sink(cap)Dangerous operation requiring sanitization eval,innerHTML,Command::new -
Choose capabilities:
Capability When to use Cap::all()Sources that produce universally dangerous data Cap::SHELL_ESCAPEShell command injection sinks/sanitizers Cap::HTML_ESCAPEXSS sinks/sanitizers Cap::URL_ENCODEURL injection sinks/sanitizers Cap::JSON_PARSEJSON parsing sanitizers Cap::FILE_IOFile I/O sinks Cap::FMT_STRINGFormat string sinks Cap::ENV_VAREnvironment/config data sources -
Matcher semantics:
- Case-insensitive suffix matching by default.
- If a matcher ends with
_, it acts as a prefix match. - Multiple matchers in one rule are alternatives (any match triggers the rule).
User-defined rules (no code change needed)
Users can add taint rules via config:
[[analysis.languages.javascript.rules]]
matchers = ["dangerouslySetInnerHTML"]
kind = "sink"
cap = "html_escape"
Or via CLI:
nyx config add-rule --lang javascript --matcher dangerouslySetInnerHTML --kind sink --cap html_escape
How to Add a New Language
Adding a new language requires changes across several modules. Use an existing language (e.g. Go or Python) as a template.
Checklist
-
Tree-sitter parser: Add
tree-sitter-<lang>toCargo.toml. -
Language registration: Register the parser in
ast.rs(language detection from file extension, parser initialization). -
CFG node kinds: Create
src/labels/<lang>.rswith aKINDSmap that maps tree-sitter node types to the internalKindenum (Block,If,While,For,Return,CallFn,CallMethod,Assignment, etc.). -
Parameter extraction: Add a
PARAM_CONFIGconstant specifying how to extract function parameters from the AST (field name for parameter list, node type for individual parameters, extraction field for parameter names). -
Label rules: Add
RULES(sources, sinks, sanitizers) andTERMINATORSto the labels file. -
AST patterns: Create
src/patterns/<lang>.rswith aPATTERNSconstant. -
Registry updates:
src/patterns/mod.rs: add to theREGISTRYHashMapsrc/labels/mod.rs: add to theclassify()dispatch
-
File extension mapping: Add the extension in
ast.rs. -
Tests: Write unit tests and add test fixtures.
Testing
Tests
Unit tests are inline #[test] blocks inside source modules; integration tests
live under tests/. Run everything the way CI does:
cargo test --all-features
What to Test
- New AST patterns: Ensure the tree-sitter query matches the intended construct and does not match safe alternatives.
- New taint rules: Verify that source-to-sink flows are detected and that sanitizers properly neutralize findings.
- New CFG rules: Test that guard dominance logic correctly suppresses findings when guards are present.
- Edge cases: Empty files, files with syntax errors (tree-sitter is error-tolerant), deeply nested structures.
Linting
CI runs Clippy with strict settings. Before submitting:
cargo clippy --all-targets --all-features -- -D warnings
Pull Request Guidelines
First-time contributors are welcome. If you are unsure where to start, open an issue and we can help identify a focused starter task.
-
Branch from
master. Use descriptive branch names:feat/add-kotlin-support,fix/false-positive-sql-concat,docs/update-rule-reference. -
Keep PRs focused. One logical change per PR.
-
Ensure CI passes — run
./scripts/check.sh(mirrors CI), or the steps individually:cargo test --all-features cargo clippy --all-targets --all-features -- -D warnings cargo fmt -- --check -
Commit style: Use Conventional Commits.
feat(patterns): add Python subprocess.Popen pattern fix(taint): prevent false positive on sanitized innerHTML docs(rules): update JavaScript rule reference -
Document new rules. If you add patterns or taint rules, update the corresponding
docs/rules/<lang>.mdpage. -
Include test cases for any new detection rules.
-
Disclose material AI assistance in the PR description if the change was drafted, generated, or substantially refactored by an AI tool. One line is enough. See AI-POLICY.md for the full policy and the bar we hold AI-assisted contributions to.
Bug Reports
Please open an issue for:
- Crashes or panics: include the backtrace (
RUST_BACKTRACE=1 nyx scan .) - False positives: include the minimal code snippet, rule ID, and Nyx version
- False negatives: describe what you expected Nyx to find and why
- Documentation errors: point to the specific page and what's wrong
Feature Requests
We welcome well-motivated feature proposals. Please describe:
- Problem statement: what pain point does this solve?
- Proposed solution: high-level description, optionally with pseudo-code.
- Alternatives considered: why existing functionality is not enough.
Release Process
- Update version in
Cargo.toml. - Update
CHANGELOG.mdwith the new version section. - Run full checks:
./scripts/check.sh(orcargo test --all-features && cargo clippy --all-targets --all-features -- -D warnings). - Create a git tag:
git tag v0.x.y. - Push tag:
git push origin v0.x.y. - CI builds release binaries and publishes to crates.io.
Security Issues
Please do not open public issues for security-sensitive bugs. See SECURITY.md for our responsible disclosure process.
License
Contributions are released under GPL-3.0-or-later
By submitting a pull request, patch, or other contribution to Nyx, you agree that your contribution will be released under the GPL-3.0-or-later, the same license as the project.
Developer Certificate of Origin
We use the Developer Certificate of Origin (DCO) as a lightweight baseline for contributions. All commits must include a Signed-off-by: trailer, which certifies that you wrote the code yourself or otherwise have the right to submit it under the project license.
Use git commit -s to add this automatically.
Contributor License Agreement
Before your first contribution can be merged, you must sign the Nyx Contributor License Agreement.
The CLA does not transfer ownership of your work. You retain copyright to your contributions. It grants Nyx the rights needed to maintain, distribute, and evolve the project over time, including the flexibility to support long-term sustainability through future licensing or commercial offerings.
If you do not agree to these terms, please do not submit contributions to Nyx.