Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

All notable changes to Nyx are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). For where Nyx is going, see the [Roadmap](ROADMAP.md).

## [Unreleased]

## [0.8.0] - 2026-06-06

The dynamic-verification release. An attack-surface map, a sandboxed dynamic verifier, a framework adapter registry that grounds both, the per-language build infrastructure that makes per-finding verification affordable at corpus scale, and the first real-corpus acceptance gates.
Expand Down Expand Up @@ -81,6 +79,11 @@ The attack-surface map and chain composer turn the flat finding list into a rout
- **`nyx verify-feedback <finding_id> --wrong <reason> | --right`** records a correction or confirmation for a finding's verdict in the local telemetry log.
- **`nyx scan --explain-engine`** prints the effective engine configuration and exits without scanning.
- **`nyx surface`** (described above) with `--format {text,json,dot,svg}` and `--build`.
- **`nyx repro` subcommand.** Replays dynamic repro bundles by finding id,
spec hash, or explicit bundle path, with `--docker`, `--print-path`, and
`--list` helpers. The CLI now matches the browser UI's reproduced command
and uses bundle manifests to bridge stable finding ids to spec-hash cache
directories.

### Frontend

Expand Down
37 changes: 37 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,43 @@ nyx scan . --max-low 50 --max-low-per-file 5

---

## `nyx repro`

Replay a dynamic repro bundle for a confirmed finding.

```
nyx repro (--finding <ID> | --spec-hash <HASH> | --bundle <DIR>) [OPTIONS]
```

Nyx writes repro bundles under the platform cache directory and keys them by
`spec_hash`. The browser UI and scan output show `finding_id`, so
`--finding` scans cached bundle manifests and replays the newest match.

| Flag | Description |
|------|-------------|
| `--finding <ID>` | Find the newest cached bundle whose manifest carries this stable finding ID |
| `--spec-hash <HASH>` | Replay an exact cache bundle by spec hash |
| `--bundle <DIR>` | Replay an explicit bundle directory |
| `--docker` | Run the bundle's Docker replay path (`./reproduce.sh --docker`) |
| `--print-path` | Print the resolved bundle path and exit without replaying |
| `--list` | With `--finding`, list all matching cached bundles newest first |

Examples:

```bash
nyx repro --finding b9caa35df2213040
nyx repro --finding b9caa35df2213040 --docker
nyx repro --finding b9caa35df2213040 --print-path
nyx repro --spec-hash 8bca7f8e0311d6c9
nyx repro --bundle /path/to/repro/8bca7f8e0311d6c9
```

Exit codes mirror `reproduce.sh`: `0` pass, `1` replay mismatch, `2` Docker
unavailable, `3` process-backend toolchain mismatch. Any other script exit is
passed through.

---

## `nyx index`

Manage the SQLite file index.
Expand Down
22 changes: 19 additions & 3 deletions docs/dynamic.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,18 +224,34 @@ fails.

## Repro artifacts

Confirmed findings write a hermetic bundle:
Confirmed findings write a hermetic bundle under Nyx's platform cache
directory:

```text
~/.cache/nyx/dynamic/repro/<spec_hash>/
<cache-dir>/nyx/dynamic/repro/<spec_hash>/
```

On Linux this is usually `~/.cache/nyx/dynamic/repro/<spec_hash>/`; on macOS
it is usually `~/Library/Caches/nyx/dynamic/repro/<spec_hash>/`.

The bundle carries the harness spec, payload, expected output, trace, and a
`reproduce.sh`. When the toolchain is pinned in `tools/image-builder/images.toml`
it also writes a `docker_pull.sh`.

The easiest replay path starts from the finding id shown in scan output or the
browser UI:

```bash
nyx repro --finding <finding_id>
nyx repro --finding <finding_id> --docker
```

You can also replay an exact bundle by spec hash, or inspect the shell script
directly:

```bash
cd ~/.cache/nyx/dynamic/repro/<spec_hash>
nyx repro --spec-hash <spec_hash>
cd <cache-dir>/nyx/dynamic/repro/<spec_hash>
./reproduce.sh
./reproduce.sh --docker
```
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/FindingDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ export function DynamicVerdictSection({ verdict }: { verdict: VerifyResult }) {
const attempts = verdict.attempts ?? [];
// The repro bundle is keyed by spec_hash (not finding_id) inside the Nyx
// cache. Rather than showing a path that may not match, surface the CLI
// command that locates and opens the bundle regardless of the hash.
// command that resolves and replays the newest matching bundle.
const reproCmd = `nyx repro --finding ${verdict.finding_id}`;

const copyCmd = () => {
Expand Down
42 changes: 41 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! [`Commands::is_structured_output`], [`Commands::is_serve`], and
//! [`Commands::is_informational`].

use clap::{Parser, Subcommand, ValueEnum};
use clap::{ArgGroup, Parser, Subcommand, ValueEnum};
use serde::{Deserialize, Serialize};

#[derive(Parser)]
Expand Down Expand Up @@ -61,6 +61,7 @@ impl Commands {
matches!(action, ConfigAction::Show { .. } | ConfigAction::Path)
}
Commands::Index { action } => matches!(action, IndexAction::Status { .. }),
Commands::Repro { .. } => true,
_ => false,
}
}
Expand Down Expand Up @@ -589,6 +590,45 @@ pub enum Commands {
upload: bool,
},

/// Replay a dynamic repro bundle for a confirmed finding.
///
/// Repro bundles are keyed by spec hash in Nyx's cache, but findings shown
/// in scan output and the browser UI use a stable finding id. `--finding`
/// locates the newest matching cached bundle by reading each bundle's
/// manifest. Use `--spec-hash` when you already know the cache key, or
/// `--bundle` for an explicit bundle directory.
#[cfg_attr(not(feature = "dynamic"), command(hide = true))]
#[command(group(
ArgGroup::new("target")
.required(true)
.args(["finding", "spec_hash", "bundle"])
))]
Repro {
/// Stable finding ID shown in dynamic verdict output and the UI.
#[arg(long, value_name = "ID")]
finding: Option<String>,

/// Exact spec hash / cache directory name to replay.
#[arg(long = "spec-hash", value_name = "HASH")]
spec_hash: Option<String>,

/// Explicit repro bundle directory.
#[arg(long, value_name = "DIR")]
bundle: Option<std::path::PathBuf>,

/// Replay with the bundle's Docker backend.
#[arg(long)]
docker: bool,

/// Print the resolved bundle path and exit without replaying.
#[arg(long, conflicts_with = "list")]
print_path: bool,

/// List every cached bundle matching --finding, newest first.
#[arg(long, requires = "finding")]
list: bool,
},

/// Manage project indexes
Index {
#[command(subcommand)]
Expand Down
19 changes: 19 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub mod clean;
pub mod config;
pub mod index;
pub mod list;
#[cfg(feature = "dynamic")]
pub mod repro;
pub mod rules;
pub mod scan;
#[cfg(feature = "serve")]
Expand Down Expand Up @@ -409,6 +411,23 @@ pub fn handle_command(
"The `dynamic` feature is not enabled. Rebuild with `cargo build --features dynamic`.".into(),
));
}
#[cfg(feature = "dynamic")]
Commands::Repro {
finding,
spec_hash,
bundle,
docker,
print_path,
list,
} => {
repro::handle(finding, spec_hash, bundle, docker, print_path, list)?;
}
#[cfg(not(feature = "dynamic"))]
Commands::Repro { .. } => {
return Err(crate::errors::NyxError::Msg(
"The `dynamic` feature is not enabled. Rebuild with `cargo build --features dynamic`.".into(),
));
}
Commands::Index { action } => {
install_from_config(config);
index::handle(action, database_dir, config)?;
Expand Down
Loading
Loading