diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ec73b55b..531ea6f5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -41,7 +41,7 @@ body: attributes: label: Nyx version description: Output of `nyx --version`. - placeholder: "nyx 0.5.0" + placeholder: "nyx 0.6.0" validations: required: true - type: input diff --git a/CHANGELOG.md b/CHANGELOG.md index 75933d84..567cc798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to Nyx are documented here. The format is based on [Keep a C ## [Unreleased] -## [0.6.0] - TBD +## [0.6.0] - 2026-05-02 A focused release that splits data-exfiltration off from SSRF and ships sinks for outbound HTTP request bodies across all 10 languages, with calibration tuned so plain user input echoed back upstream does not fire. diff --git a/README.md b/README.md index ec82b993..9f789082 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Requires stable Rust 1.88+. The frontend is compiled and embedded in the binary ## 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 492-case corpus at [`tests/benchmark/ground_truth.json`](tests/benchmark/ground_truth.json) is 100% across all ten languages, 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: +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 507-case corpus at [`tests/benchmark/ground_truth.json`](tests/benchmark/ground_truth.json) is 100% across all ten languages, 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? | |---|---|---|---| @@ -137,6 +137,7 @@ The corpus also holds a small set of vulnerable/patched pairs extracted from pub | [CVE-2023-22621](https://nvd.nist.gov/vuln/detail/CVE-2023-22621) | Strapi | JavaScript | Code execution (SSTI) | | [CVE-2025-64430](https://nvd.nist.gov/vuln/detail/CVE-2025-64430) | Parse Server | JavaScript | SSRF | | [CVE-2023-26159](https://nvd.nist.gov/vuln/detail/CVE-2023-26159) | follow-redirects | TypeScript | SSRF | +| [GHSA-4x48-cgf9-q33f](https://github.com/advisories/GHSA-4x48-cgf9-q33f) | Novu | TypeScript | SSRF | | [CVE-2026-25544](https://nvd.nist.gov/vuln/detail/CVE-2026-25544) | Payload CMS | TypeScript | SQL injection | | [CVE-2022-30323](https://nvd.nist.gov/vuln/detail/CVE-2022-30323) | hashicorp/go-getter | Go | Command injection | | [CVE-2024-31450](https://nvd.nist.gov/vuln/detail/CVE-2024-31450) | owncast | Go | Path traversal | @@ -211,7 +212,7 @@ Or add rules interactively: `nyx config add-rule --lang javascript --matcher esc ## Status -Under active development. APIs, detector behavior, and configuration options may change between releases. Rule-level F1 on the 492-case corpus is the CI regression floor; per-language detail lives in [`tests/benchmark/RESULTS.md`](tests/benchmark/RESULTS.md). +Under active development. APIs, detector behavior, and configuration options may change between releases. Rule-level F1 on the 507-case corpus is the CI regression floor; per-language detail lives in [`tests/benchmark/RESULTS.md`](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`. diff --git a/SECURITY.md b/SECURITY.md index dfe222b3..5b392047 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,46 +1,88 @@ # Security Policy -## Supported Versions +## Reporting a vulnerability -| Version | Supported | Notes | -|---------|-----------|----------------------| -| 0.5.x | ✅ | Latest stable line | -| 0.4.x | ✅ | Critical fixes only | -| < 0.4 | ❌ | End-of-life | +Report privately. Do not open a public GitHub issue for a security bug. -We follow [Semantic Versioning] as soon as we hit **1.0.0**. -Before that, breaking changes may land in any minor release. +Use [GitHub Security Advisories](https://github.com/elicpeter/nyx/security/advisories/new) to file a private report. Only the maintainers see it. -## Reporting a Vulnerability +Include: -* **Private disclosure first.** - Please **do not** open public GitHub issues for security bugs. +- Affected version (`nyx --version`) and OS +- Reproduction steps or a minimal PoC +- Impact (RCE, file read or write, sandbox escape, auth bypass in `nyx serve`, etc.) +- Whether you have a fix in mind -* **How to report** - 1. To report a vulnerability, please use the GitHub disclosure in the security tab to alert us to a security issue. +You'll get an acknowledgement within 3 business days, and a status update every 7 days until the issue is closed. -* **What to include** - – A minimal PoC or reproduction steps - – Affected Nyx version (`nyx --version`) and OS - – Impact explanation (e.g. RCE, DoS, data leak) +## Scope -* **Response timeline** - We acknowledge within **3 business days** and give a status update every **7 days** thereafter until resolution. +In scope: bugs that let untrusted input reach the Nyx process and cause harm. -## Disclosure Process +- Code execution in the scanner: parser exploits, deserialization, command injection in helpers, custom-rule sandbox escape. +- Path traversal or arbitrary file access outside the target repo. +- `nyx serve` issues: auth bypass, host-header bypass, CSRF on mutating routes, XSS in the UI, cross-origin access from a non-loopback origin. +- Memory safety bugs in any unsafe Rust we introduce. +- Tampering with `.nyx/` triage state from outside the user's repo. +- Supply chain issues affecting published `nyx-scanner` crates or release artifacts. -1. We confirm the issue and assign a CVE (via GitHub or MITRE). -2. A fix is developed on a private branch and back-ported if needed. -3. Coordinated release: new version on crates.io + public advisory. -4. Credit is given to the reporter unless they request anonymity. +Out of scope: -## Scope & Severity +- False positives or missed detections in scan output. File a regular GitHub issue with the rule ID and a fixture. +- Findings Nyx reports against your own code. That's the scanner working, not a Nyx vulnerability. +- Anything requiring physical or local-account access to the user's machine. +- Self-XSS and missing security headers on `127.0.0.1` endpoints. The UI is loopback-only. +- Performance pathologies on hostile input (a 50 GB file, deeply nested grammars). We harden where we can. +- Issues only reachable by a user editing their own `nyx.conf` to weaken defaults. -This policy covers vulnerabilities that let an **untrusted Nyx input** cause: +## Supported versions -* Remote or local code execution in the Nyx process -* Privilege escalation, data exfiltration, or denial of service +| Version | Status | +|---------|-----------------------| +| 0.6.x | Supported | +| 0.5.x | Critical fixes only | +| < 0.5 | End of life | -**False positives / missed detections** in scan results are *quality issues*, not security issues. Please file normal GitHub issues for those. +The project follows [Semantic Versioning](https://semver.org) once it reaches 1.0.0. Until then, breaking changes can land in any minor release. -[Semantic Versioning]: https://semver.org +## Severity + +We use [CVSS 3.1](https://www.first.org/cvss/v3.1/specification-document) to rate reports. + +| Severity | Examples | +|----------|-----------------------------------------------------------------------------------------------| +| Critical | Unauthenticated RCE in `nyx serve`, custom-rule sandbox escape during a default scan | +| High | Auth bypass against `nyx serve`, arbitrary file write outside the repo | +| Medium | Stored XSS in the UI, CSRF on a mutating route, host-header bypass | +| Low | Information disclosure with no privilege change, log-injection, denial of service via input | + +## Disclosure + +Coordinated disclosure. + +1. We confirm the report and assign severity. +2. We request a CVE through GitHub or MITRE. +3. A fix is developed on a private branch, with backports to supported lines if needed. +4. A new release ships on crates.io and a public advisory goes out. +5. The reporter is credited in the advisory and the changelog, unless they ask to stay anonymous. + +Target window from report to fix is 90 days. If you need to publish on a shorter timeline, tell us in the report and we'll work toward it. + +## Safe harbor + +Good-faith security research is welcome. We won't pursue legal action against researchers who: + +- Report privately and give a reasonable window before publishing. +- Test against their own installations, not third-party deployments running Nyx. +- Avoid data destruction, account takeover, and service disruption. +- Stop and reach out if a test starts to affect data or systems they don't own. + +If you're not sure whether a test is in scope, ask first. + +## Bounty + +There is no paid bug bounty program. Credit, a thank-you in the advisory, and a mention in the changelog are what we offer today. + +## Security model recap + +Nyx runs locally. The browser UI binds to `127.0.0.1` by default, requires a matching `Host` header, and uses a CSRF token on every mutating request. There is no login, no telemetry, and no remote control plane. If you find a way around any of those defaults, that's a security issue and we want to hear about it. diff --git a/tests/benchmark/RESULTS.md b/tests/benchmark/RESULTS.md index d35c17b1..d03ae28f 100644 --- a/tests/benchmark/RESULTS.md +++ b/tests/benchmark/RESULTS.md @@ -8,9 +8,9 @@ Current baseline (2026-05-02): | Recall | 1.000 | 1.000 | 0.944 | | F1 | 1.000 | 1.000 | 0.901 | -Corpus: 499 cases across 10 languages, 496 evaluated (3 disabled). Per-run JSON lands in `tests/benchmark/results/` (`latest.json` plus dated snapshots). See `README.md` for what the scoring modes mean and how to run a subset. +Corpus: 507 cases across 10 languages, 504 evaluated (3 disabled). Per-run JSON lands in `tests/benchmark/results/` (`latest.json` plus dated snapshots). See `README.md` for what the scoring modes mean and how to run a subset. -The corpus is mostly synthetic 8-20 line fixtures, one vulnerability or one safe pattern per file. A smaller real-CVE replay set under `cve_corpus/` covers 20 published CVEs across all 10 languages. Both contribute to the headline numbers. +The corpus is mostly synthetic 8-20 line fixtures, one vulnerability or one safe pattern per file. A smaller real-CVE replay set under `cve_corpus/` covers 30 published advisories across all 10 languages. Both contribute to the headline numbers. ## Real CVE coverage @@ -40,6 +40,9 @@ Real disclosed CVEs reduced to minimal reproducers, vulnerable + patched pair pe | CVE-2023-38337 | Ruby | rswag | MIT | path_traversal | detected | | CVE-2017-9841 | PHP | PHPUnit | BSD-3-Clause | code_exec | detected | | CVE-2018-15133 | PHP | Laravel | MIT | Deserialization | detected | +| CVE-2018-20997 | Rust | tar-rs | MIT OR Apache-2.0 | path_traversal | detected | +| CVE-2022-36113 | Rust | cargo | MIT OR Apache-2.0 | path_traversal | detected | +| CVE-2024-24576 | Rust | Rust stdlib | MIT OR Apache-2.0 | CMDI | detected | | CVE-2016-3714 | C | ImageMagick (ImageTragick) | ImageMagick License | CMDI | detected | | CVE-2019-18634 | C | sudo (pwfeedback) | ISC | memory_safety | detected | | CVE-2019-13132 | C++ | ZeroMQ libzmq | MPL-2.0 | memory_safety | detected | diff --git a/tests/benchmark/ground_truth.json b/tests/benchmark/ground_truth.json index 0f5696f8..08f1a5dc 100644 --- a/tests/benchmark/ground_truth.json +++ b/tests/benchmark/ground_truth.json @@ -15995,9 +15995,8 @@ "sqli", "vulnerable" ], - "disabled": true, - "disabled_reason": "Validated-flow propagation through SSA-derived values and helper-summary returns is missing. The patched counterpart applies a regex allowlist (`SAFE_STRING_REGEX.test(value)` throw) PLUS a `replace()` escape chain inside `sanitizeValue`, then interpolates the result into a SQL template literal in `createJSONQuery` and returns the string to the handler, which calls `db.execute(sql)`. This session landed `classify_condition` recognition of `<*regex*>.test(value)` / `<*pattern*>.test(value)` as a ValidationCall whose target is the call's first arg (covered by `path_state::tests::target_regex_test_first_arg`, `target_regex_test_pattern_receiver`, `target_test_non_regex_receiver_is_not_validation`, plus the SSA-level `regex_test_allowlist_narrowing_clears_direct_flow` integration test). But validated_must is per-symbol and consulted only at the sink site; it does NOT propagate through the SSA Assign that templates a clean `value` into a derived `sql` string, nor does it ride a helper's `param_to_return` summary back into a caller. Disabled until that propagation path lands. Tracked in CVE_DEFERRED.md.", - "notes": "CVE-2026-25544: Payload `sanitizeValue` SQL injection via Postgres jsonb_path_exists template-string interpolation. Vulnerable form (`@payloadcms/drizzle@v3.72.0`, MIT) lets attacker-controlled JSON-query value escape the surrounding SQL string literal because `sanitizeValue` only double-quotes it without escaping `\\`/`\"`. Disabled pending validated-flow propagation engine work, see disabled_reason." + "disabled": false, + "notes": "CVE-2026-25544: Payload `sanitizeValue` SQL injection via Postgres jsonb_path_exists template-string interpolation. Vulnerable form (`@payloadcms/drizzle@v3.72.0`, MIT) lets attacker-controlled JSON-query value escape the surrounding SQL string literal because `sanitizeValue` only double-quotes it without escaping `\\`/`\"`. Enabled after validated-flow propagation landed via `SsaFuncSummary.validated_params_to_return` + `propagate_validated_params_to_return`." }, { "case_id": "cve-ts-2026-25544-patched", @@ -16024,9 +16023,8 @@ "safe", "patched" ], - "disabled": true, - "disabled_reason": "Sibling of cve-ts-2026-25544-vulnerable. Disabled together until validated-flow summary propagation lands. See vulnerable counterpart's disabled_reason for the engine gap.", - "notes": "Patched form of `sanitizeValue` from `@payloadcms/drizzle@v3.73.0` (MIT). Disabled together with its vulnerable counterpart pending validated-flow propagation work." + "disabled": false, + "notes": "Patched form of `sanitizeValue` from `@payloadcms/drizzle@v3.73.0` (MIT). Enabled after validated-flow propagation landed." } ] } \ No newline at end of file