mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] cleanup session-0004 (20260522T163126Z-7d60)
This commit is contained in:
parent
0e4e393000
commit
0d4ab22c4c
7 changed files with 119 additions and 45 deletions
|
|
@ -71,9 +71,10 @@ nyx scan --unsafe-sandbox # alias for --backend process
|
|||
```
|
||||
|
||||
Docker is the preferred backend. It mounts only the entry file's directory and
|
||||
blocks outbound network by default. If out-of-band detection is enabled with
|
||||
`oob_listener`, Docker uses bridge networking with a host-gateway route so the
|
||||
harness can reach the listener.
|
||||
blocks outbound network by default. Nyx binds a loopback OOB listener at scan
|
||||
start for callback-style payloads (SSRF, blind SSTI). When the bind succeeds,
|
||||
Docker switches to bridge networking with a host-gateway route so the harness
|
||||
can reach the listener; OOB payloads are skipped if the bind fails.
|
||||
|
||||
The process backend is useful for development and machines without Docker. It
|
||||
does not provide the same isolation.
|
||||
|
|
@ -141,7 +142,7 @@ The literal `nyx_version` and `corpus_version` values shift between releases; se
|
|||
| `schema_version` | Event schema version. Readers reject mismatches. |
|
||||
| `nyx_version` | Version of the Nyx binary that wrote the event. |
|
||||
| `corpus_version` | Payload corpus version used for the verdict. |
|
||||
| `kind` | `verdict`, `rank_delta`, or `feedback`. |
|
||||
| `kind` | `verdict` or `rank_delta`. Feedback rows use an `event: "verify_feedback"` field instead and may pre-date the schema envelope. |
|
||||
| `ts` | Write time in RFC 3339 format. |
|
||||
| `finding_id` | Stable finding identifier. |
|
||||
| `spec_hash` | Hash of the harness spec. |
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ impl Commands {
|
|||
&& (fmt == OutputFormat::Json || fmt == OutputFormat::Sarif)
|
||||
}
|
||||
|
||||
/// Whether the user explicitly asked this invocation to suppress
|
||||
/// human-readable output.
|
||||
pub fn quiet_requested(&self) -> bool {
|
||||
matches!(self, Commands::Scan { quiet: true, .. })
|
||||
}
|
||||
|
||||
/// Whether this is a long-running server command (skip timing output).
|
||||
pub fn is_serve(&self) -> bool {
|
||||
matches!(self, Commands::Serve { .. })
|
||||
|
|
|
|||
22
src/main.rs
22
src/main.rs
|
|
@ -14,10 +14,16 @@ use tracing_subscriber::{EnvFilter, Registry, fmt as tracing_fmt};
|
|||
// use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
// use tracing_appender::non_blocking;
|
||||
|
||||
fn init_tracing() {
|
||||
fn init_tracing(quiet: bool) {
|
||||
// let file_appender = RollingFileAppender::new(Rotation::HOURLY, "logs", "nyx-scanner.log");
|
||||
// let (file_writer, guard) = non_blocking(file_appender);
|
||||
|
||||
let filter = if quiet {
|
||||
EnvFilter::new("off")
|
||||
} else {
|
||||
EnvFilter::from_default_env()
|
||||
};
|
||||
|
||||
let fmt_layer = tracing_fmt::layer()
|
||||
.pretty()
|
||||
.with_writer(std::io::stderr)
|
||||
|
|
@ -29,17 +35,11 @@ fn init_tracing() {
|
|||
// .without_time()
|
||||
// .json();
|
||||
|
||||
Registry::default()
|
||||
.with(EnvFilter::from_default_env())
|
||||
.with(fmt_layer)
|
||||
.init();
|
||||
Registry::default().with(filter).with(fmt_layer).init();
|
||||
}
|
||||
|
||||
fn main() -> NyxResult<()> {
|
||||
let now = Instant::now();
|
||||
init_tracing();
|
||||
|
||||
tracing::debug!("CLI starting up");
|
||||
|
||||
if std::env::args().count() == 1 {
|
||||
eprint!("{}", fmt::render_welcome());
|
||||
|
|
@ -60,6 +60,10 @@ fn main() -> NyxResult<()> {
|
|||
|
||||
let (mut config, config_note) = Config::load(config_dir)?;
|
||||
|
||||
let explicit_quiet = config.output.quiet || cli.command.quiet_requested();
|
||||
init_tracing(explicit_quiet);
|
||||
tracing::debug!("CLI starting up");
|
||||
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.stack_size(config.performance.rayon_thread_stack_size)
|
||||
.build_global()
|
||||
|
|
@ -67,7 +71,7 @@ fn main() -> NyxResult<()> {
|
|||
|
||||
let is_serve = cli.command.is_serve();
|
||||
let is_info = cli.command.is_informational();
|
||||
let quiet = config.output.quiet || cli.command.is_structured_output(&config);
|
||||
let quiet = explicit_quiet || cli.command.is_structured_output(&config);
|
||||
|
||||
// Print config note before scanning (human-readable mode only). Pure
|
||||
// informational commands suppress it too, their output is usually
|
||||
|
|
|
|||
|
|
@ -788,6 +788,7 @@ impl GlobalSummaries {
|
|||
.wrapping_mul(0x9E37_79B9)
|
||||
.wrapping_add(probe);
|
||||
key.disambig = Some(SYNTHETIC_DISAMBIG_BIT | (synth & !SYNTHETIC_DISAMBIG_BIT));
|
||||
key.arity = Some(body.param_count);
|
||||
probe = probe.wrapping_add(1);
|
||||
if probe >= 1024 {
|
||||
tracing::warn!(
|
||||
|
|
|
|||
|
|
@ -3272,27 +3272,17 @@ fn insert_body_param_count_mismatch_rekeys() {
|
|||
assert_eq!(head.param_count, 2);
|
||||
|
||||
// Invariant 2: the conflicting body is preserved under a synthetic
|
||||
// disambig, not dropped. Reconstruct the expected synth disambig
|
||||
// using the same formula as `reconcile_body_key`.
|
||||
let mut found_conflicting = false;
|
||||
// disambig at its own arity, not dropped.
|
||||
let base = (4u32).wrapping_mul(0x9E37_79B9);
|
||||
for probe in 0u32..1024 {
|
||||
let synth = base.wrapping_add(probe);
|
||||
let synth_key = FuncKey {
|
||||
disambig: Some(0x8000_0000 | (synth & 0x7FFF_FFFF)),
|
||||
..key.clone()
|
||||
};
|
||||
if let Some(body) = gs.get_body(&synth_key)
|
||||
&& body.param_count == 4
|
||||
{
|
||||
found_conflicting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
found_conflicting,
|
||||
"the 4-param body must be preserved under a synthetic disambig key"
|
||||
);
|
||||
let synth_key = FuncKey {
|
||||
arity: Some(4),
|
||||
disambig: Some(0x8000_0000 | (base & 0x7FFF_FFFF)),
|
||||
..key.clone()
|
||||
};
|
||||
let conflicting = gs
|
||||
.get_body(&synth_key)
|
||||
.expect("the 4-param body must be preserved under a synthetic disambig key");
|
||||
assert_eq!(conflicting.param_count, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1940,18 +1940,10 @@ pub(crate) fn extract_intra_file_ssa_summaries(
|
|||
Err(_) => continue,
|
||||
};
|
||||
|
||||
// Param count = number of formal params (from CFG), falling back to
|
||||
// counting all SsaOp::Param ops when no local summary is available.
|
||||
let param_count = if !formal_params.is_empty() {
|
||||
formal_params.len()
|
||||
} else {
|
||||
func_ssa
|
||||
.blocks
|
||||
.iter()
|
||||
.flat_map(|b| b.phis.iter().chain(b.body.iter()))
|
||||
.filter(|i| matches!(i.op, crate::ssa::ir::SsaOp::Param { .. }))
|
||||
.count()
|
||||
};
|
||||
// `formal_params` is authoritative even when it is empty. SSA lowering
|
||||
// also emits Param ops for external captures; counting those as arity
|
||||
// makes zero-arg functions look like synthetic overloads.
|
||||
let param_count = formal_params.len();
|
||||
|
||||
// Zero-param helpers are normally elided, a fixture with no
|
||||
// parameters cannot carry per-parameter taint transforms. But
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
use serde_json::Value;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Build a scan command with a fresh config dir and a writable tempdir as
|
||||
|
|
@ -164,6 +165,85 @@ fn scan_with_no_extra_flags_on_clean_target_succeeds() {
|
|||
cmd.assert().success();
|
||||
}
|
||||
|
||||
fn assert_stdout_is_json_from_byte_zero(output: &[u8], context: &str) -> Value {
|
||||
assert_eq!(
|
||||
output.first().copied(),
|
||||
Some(b'{'),
|
||||
"{context}: stdout must start with a JSON object, got prefix {:?}",
|
||||
String::from_utf8_lossy(&output[..output.len().min(80)])
|
||||
);
|
||||
serde_json::from_slice(output).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"{context}: stdout did not parse as JSON: {e}\n--- stdout prefix ---\n{}",
|
||||
String::from_utf8_lossy(&output[..output.len().min(400)])
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_json_stdout_is_machine_clean_when_tracing_warns() {
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let target = prepare_scan_target();
|
||||
let (mut cmd, _) = scan_cmd(home.path(), target.path());
|
||||
cmd.env("RUST_LOG", "warn")
|
||||
.args(["--format", "json", "--no-index", "--parse-timeout-ms", "0"]);
|
||||
|
||||
let assert = cmd.assert().success();
|
||||
let value =
|
||||
assert_stdout_is_json_from_byte_zero(&assert.get_output().stdout, "nyx scan --format json");
|
||||
assert!(
|
||||
value.get("findings").is_some(),
|
||||
"JSON scan payload missing findings"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_sarif_stdout_is_machine_clean_when_tracing_warns() {
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let target = prepare_scan_target();
|
||||
let (mut cmd, _) = scan_cmd(home.path(), target.path());
|
||||
cmd.env("RUST_LOG", "warn").args([
|
||||
"--format",
|
||||
"sarif",
|
||||
"--no-index",
|
||||
"--parse-timeout-ms",
|
||||
"0",
|
||||
]);
|
||||
|
||||
let assert = cmd.assert().success();
|
||||
let value = assert_stdout_is_json_from_byte_zero(
|
||||
&assert.get_output().stdout,
|
||||
"nyx scan --format sarif",
|
||||
);
|
||||
assert_eq!(value["version"], "2.1.0", "SARIF version missing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_quiet_suppresses_tracing_warnings() {
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let target = prepare_scan_target();
|
||||
let (mut cmd, _) = scan_cmd(home.path(), target.path());
|
||||
cmd.env("RUST_LOG", "warn").args([
|
||||
"--format",
|
||||
"json",
|
||||
"--quiet",
|
||||
"--no-index",
|
||||
"--parse-timeout-ms",
|
||||
"0",
|
||||
]);
|
||||
|
||||
let assert = cmd.assert().success();
|
||||
assert_stdout_is_json_from_byte_zero(
|
||||
&assert.get_output().stdout,
|
||||
"nyx scan --format json --quiet",
|
||||
);
|
||||
assert!(
|
||||
assert.get_output().stderr.is_empty(),
|
||||
"--quiet should suppress tracing/status stderr, got:\n{}",
|
||||
String::from_utf8_lossy(&assert.get_output().stderr)
|
||||
);
|
||||
}
|
||||
|
||||
/// `--explain-engine` short-circuits the scan path and prints the resolved
|
||||
/// engine configuration to stdout. Exit code 0, non-empty stdout, and the
|
||||
/// "Effective engine configuration" header present.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue