nyx/CONTRIBUTING.md
Eli Peter 1bbe4b1cfb
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
2026-02-25 21:16:36 -05:00

12 KiB

Contributing to Nyx

Thank you for your interest in improving Nyx. This guide covers everything you need to contribute effectively.

Please read our Code of Conduct before participating.


Table of Contents

  1. Development Setup
  2. Project Layout
  3. How to Add a New AST Pattern
  4. How to Add a New Taint Rule
  5. How to Add a New Language
  6. Testing
  7. Pull Request Guidelines
  8. Bug Reports
  9. Feature Requests
  10. Release Process

Development Setup

Prerequisites

  • Rust 1.85+ (edition 2024)
  • Git

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

cargo test --bin nyx                   # Unit tests (inline in modules)
cargo clippy --all -- -D warnings      # Lint — treats warnings as errors
cargo fmt                              # Format code
cargo fmt -- --check                   # Check formatting without modifying

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

src/
  main.rs               CLI entry point
  lib.rs                 Library re-exports (benchmarks, integration tests)
  cli.rs                 Clap command definitions
  commands/
    mod.rs               Command dispatch
    scan.rs              Two-pass scan orchestration, Diag struct
  ast.rs                 Entry points for both passes; tree-sitter parsing
  cfg.rs                 CFG construction from AST
  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
  taint/
    mod.rs               Taint analysis facade + JS two-level solve
    domain.rs            TaintState lattice (VarTaint, Cap, TaintOrigin)
    transfer.rs          TaintTransfer function (source/sanitizer/sink/call)
    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.rs             FuncSummary, GlobalSummaries, conservative merge
  labels/                Per-language label rules
    mod.rs               classify() dispatch, Cap bitflags, DataLabel, LabelRule
    rust.rs              Rust sources, sinks, sanitizers
    javascript.rs        JS sources, sinks, sanitizers
    ...                  (one file per language)
  patterns/              Per-language AST pattern queries
    mod.rs               Pattern struct, Severity, SeverityFilter, registry
    rust.rs              Rust patterns
    javascript.rs        JS patterns
    ...                  (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                 Output formatting and evidence normalization
  output.rs              SARIF 2.1 builder
  walk.rs                Parallel file walker (ignore crate, respects .gitignore)
  symbol.rs              Symbol interning (SymbolId)
  interop.rs             Cross-language interop edges
  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

  1. Pick the language file under src/patterns/<lang>.rs.

  2. Choose the metadata:

    Field Options Guidelines
    ID <lang>.<category>.<specific> e.g. py.cmdi.os_popen
    Tier A or B A = presence alone is high-signal; B = query includes a heuristic guard
    Severity High, Medium, Low High: command exec, deser, banned functions. Medium: SQL concat, reflection, XSS. Low: weak crypto, code quality.
    Category See PatternCategory enum CommandExec, CodeExec, Deserialization, SqlInjection, PathTraversal, Xss, Crypto, Secrets, InsecureTransport, Reflection, MemorySafety, Prototype, CodeQuality
  3. 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 @vuln node. That node's span determines the reported location.

  4. Test it:

    cargo test --bin nyx
    
  5. 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

  1. Open the language file in src/labels/<lang>.rs.

  2. Add an entry to the RULES slice:

    LabelRule {
        matchers: &["dangerouslySetInnerHTML"],
        label: DataLabel::Sink(Cap::HTML_ESCAPE),
    },
    
  3. Choose the right label type:

    Type Purpose Example
    DataLabel::Source(cap) Introduces tainted data env::var, req.body
    DataLabel::Sanitizer(cap) Strips matching capability bits html_escape, encodeURIComponent
    DataLabel::Sink(cap) Dangerous operation requiring sanitization eval, innerHTML, Command::new
  4. Choose capabilities:

    Capability When to use
    Cap::all() Sources that produce universally dangerous data
    Cap::SHELL_ESCAPE Shell command injection sinks/sanitizers
    Cap::HTML_ESCAPE XSS sinks/sanitizers
    Cap::URL_ENCODE URL injection sinks/sanitizers
    Cap::JSON_PARSE JSON parsing sanitizers
    Cap::FILE_IO File I/O sinks
    Cap::FMT_STRING Format string sinks
    Cap::ENV_VAR Environment/config data sources
  5. 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

  1. Tree-sitter parser: Add tree-sitter-<lang> to Cargo.toml.

  2. Language registration: Register the parser in ast.rs (language detection from file extension, parser initialization).

  3. CFG node kinds: Create src/labels/<lang>.rs with a KINDS map that maps tree-sitter node types to the internal Kind enum (Block, If, While, For, Return, CallFn, CallMethod, Assignment, etc.).

  4. Parameter extraction: Add a PARAM_CONFIG constant specifying how to extract function parameters from the AST (field name for parameter list, node type for individual parameters, extraction field for parameter names).

  5. Label rules: Add RULES (sources, sinks, sanitizers) and TERMINATORS to the labels file.

  6. AST patterns: Create src/patterns/<lang>.rs with a PATTERNS constant.

  7. Registry updates:

    • src/patterns/mod.rs — add to the REGISTRY HashMap
    • src/labels/mod.rs — add to the classify() dispatch
  8. File extension mapping: Add the extension in ast.rs.

  9. Tests: Write unit tests and add test fixtures.


Testing

Unit Tests

All tests are inline #[test] blocks inside source modules. Run them with:

cargo test --bin nyx

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 -- -D warnings

Pull Request Guidelines

  1. Branch from master. Use descriptive branch names: feat/add-kotlin-support, fix/false-positive-sql-concat, docs/update-rule-reference.

  2. Keep PRs focused. One logical change per PR.

  3. Ensure CI passes:

    cargo test --bin nyx
    cargo clippy --all -- -D warnings
    cargo fmt -- --check
    
  4. 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
    
  5. Document new rules. If you add patterns or taint rules, update the corresponding docs/rules/<lang>.md page.

  6. Include test cases for any new detection rules.


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:

  1. Problem statement — what pain point does this solve?
  2. Proposed solution — high-level description, optionally with pseudo-code.
  3. Alternatives considered — why existing functionality is not enough.

Release Process

  1. Update version in Cargo.toml.
  2. Update CHANGELOG.md with the new version section.
  3. Run full test suite: cargo test --bin nyx && cargo clippy --all -- -D warnings.
  4. Create a git tag: git tag v0.x.y.
  5. Push tag: git push origin v0.x.y.
  6. 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

By contributing to Nyx, you agree that your contributions will be licensed under the GPL-3.0.