mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss] sweep after phase 03: 3 deferred items resolved
This commit is contained in:
parent
364d09d6a8
commit
3b660ba1d3
11 changed files with 131 additions and 33 deletions
19
.config/nextest.toml
Normal file
19
.config/nextest.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# nextest configuration
|
||||
#
|
||||
# See https://nexte.st/docs/configuration/ for the full schema.
|
||||
|
||||
# ── Test groups ──────────────────────────────────────────────────────────────
|
||||
#
|
||||
# `hostile-input-timing` serialises the two timing-bounded
|
||||
# `hostile_input_tests` cases that pass under nextest in isolation but fail
|
||||
# under the full-suite parallel run on darwin (resource contention from the
|
||||
# other ~4000 tests pushes them past their internal budget). Pinning them to
|
||||
# a single thread within their own group keeps their wall-clock predictable
|
||||
# without slowing the rest of the suite.
|
||||
|
||||
[test-groups]
|
||||
hostile-input-timing = { max-threads = 1 }
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(hostile_input_tests) and (test(very_long_single_line_parses) or test(many_small_functions_do_not_explode))'
|
||||
test-group = 'hostile-input-timing'
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
//! Payload slot support:
|
||||
//! - `PayloadSlot::Param(0)` — pass payload as `string` first argument.
|
||||
//! - `PayloadSlot::EnvVar(name)` — set env var before calling entry.
|
||||
//! - Other slots produce `UnsupportedReason::EntryKindUnsupported`.
|
||||
//! - Other slots produce `UnsupportedReason::PayloadSlotUnsupported`.
|
||||
//!
|
||||
//! Build container: `nyx-build-go:{toolchain_id}` (deferred; §19.1).
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ impl LangEmitter for GoEmitter {
|
|||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
match &spec.payload_slot {
|
||||
PayloadSlot::Param(0) | PayloadSlot::EnvVar(_) => {}
|
||||
_ => return Err(UnsupportedReason::EntryKindUnsupported),
|
||||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
||||
}
|
||||
|
||||
let main_go = generate_main_go(spec);
|
||||
|
|
@ -218,14 +218,14 @@ mod tests {
|
|||
fn emit_param_gt_0_is_unsupported() {
|
||||
let spec = make_spec(PayloadSlot::Param(1));
|
||||
let err = emit(&spec).unwrap_err();
|
||||
assert_eq!(err, UnsupportedReason::EntryKindUnsupported);
|
||||
assert_eq!(err, UnsupportedReason::PayloadSlotUnsupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_stdin_is_unsupported() {
|
||||
let spec = make_spec(PayloadSlot::Stdin);
|
||||
let err = emit(&spec).unwrap_err();
|
||||
assert_eq!(err, UnsupportedReason::EntryKindUnsupported);
|
||||
assert_eq!(err, UnsupportedReason::PayloadSlotUnsupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
//! Payload slot support:
|
||||
//! - `PayloadSlot::Param(0)` — pass payload as `String` first argument.
|
||||
//! - `PayloadSlot::EnvVar(name)` — set system property before calling entry.
|
||||
//! - Other slots produce `UnsupportedReason::EntryKindUnsupported`.
|
||||
//! - Other slots produce `UnsupportedReason::PayloadSlotUnsupported`.
|
||||
//!
|
||||
//! Build container: `nyx-build-java:{toolchain_id}` (deferred; §19.1).
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ impl LangEmitter for JavaEmitter {
|
|||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
match &spec.payload_slot {
|
||||
PayloadSlot::Param(0) | PayloadSlot::EnvVar(_) => {}
|
||||
_ => return Err(UnsupportedReason::EntryKindUnsupported),
|
||||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
||||
}
|
||||
|
||||
let source = generate_harness_java(spec);
|
||||
|
|
@ -197,14 +197,14 @@ mod tests {
|
|||
fn emit_param_gt_0_is_unsupported() {
|
||||
let spec = make_spec(PayloadSlot::Param(1));
|
||||
let err = emit(&spec).unwrap_err();
|
||||
assert_eq!(err, UnsupportedReason::EntryKindUnsupported);
|
||||
assert_eq!(err, UnsupportedReason::PayloadSlotUnsupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_stdin_is_unsupported() {
|
||||
let spec = make_spec(PayloadSlot::Stdin);
|
||||
let err = emit(&spec).unwrap_err();
|
||||
assert_eq!(err, UnsupportedReason::EntryKindUnsupported);
|
||||
assert_eq!(err, UnsupportedReason::PayloadSlotUnsupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
//! - `PayloadSlot::Param(n)` — n-th positional argument.
|
||||
//! - `PayloadSlot::EnvVar(name)` — set env var before calling.
|
||||
//! - `PayloadSlot::Stdin` — pipe payload to process.stdin.
|
||||
//! - Other slots produce `UnsupportedReason::EntryKindUnsupported`.
|
||||
//! - Other slots produce `UnsupportedReason::PayloadSlotUnsupported`.
|
||||
//!
|
||||
//! Build: no compilation step. Command is `node harness.js`.
|
||||
//! Build container: `nyx-build-node:{toolchain_id}` (deferred; §19.1).
|
||||
|
|
@ -53,7 +53,7 @@ impl LangEmitter for JavaScriptEmitter {
|
|||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
match &spec.payload_slot {
|
||||
PayloadSlot::Param(_) | PayloadSlot::EnvVar(_) | PayloadSlot::Stdin => {}
|
||||
_ => return Err(UnsupportedReason::EntryKindUnsupported),
|
||||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
||||
}
|
||||
|
||||
let source = generate_source(spec);
|
||||
|
|
@ -246,7 +246,7 @@ mod tests {
|
|||
fn emit_http_body_is_unsupported() {
|
||||
let spec = make_spec(PayloadSlot::HttpBody);
|
||||
let err = emit(&spec).unwrap_err();
|
||||
assert_eq!(err, UnsupportedReason::EntryKindUnsupported);
|
||||
assert_eq!(err, UnsupportedReason::PayloadSlotUnsupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
//! - `PayloadSlot::Param(n)` — n-th positional argument.
|
||||
//! - `PayloadSlot::EnvVar(name)` — set `$_ENV`/`putenv()` before calling.
|
||||
//! - `PayloadSlot::Stdin` — wrap `STDIN` with the payload.
|
||||
//! - Other slots produce `UnsupportedReason::EntryKindUnsupported`.
|
||||
//! - Other slots produce `UnsupportedReason::PayloadSlotUnsupported`.
|
||||
//!
|
||||
//! Build: no compilation step. Command is `php harness.php`.
|
||||
//! Build container: `nyx-build-php:{toolchain_id}` (deferred; §19.1).
|
||||
|
|
@ -51,7 +51,7 @@ impl LangEmitter for PhpEmitter {
|
|||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
match &spec.payload_slot {
|
||||
PayloadSlot::Param(_) | PayloadSlot::EnvVar(_) | PayloadSlot::Stdin => {}
|
||||
_ => return Err(UnsupportedReason::EntryKindUnsupported),
|
||||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
||||
}
|
||||
|
||||
let source = generate_source(spec);
|
||||
|
|
@ -208,7 +208,7 @@ mod tests {
|
|||
fn emit_http_body_is_unsupported() {
|
||||
let spec = make_spec(PayloadSlot::HttpBody);
|
||||
let err = emit(&spec).unwrap_err();
|
||||
assert_eq!(err, UnsupportedReason::EntryKindUnsupported);
|
||||
assert_eq!(err, UnsupportedReason::PayloadSlotUnsupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
//! Payload slot support:
|
||||
//! - `PayloadSlot::Param(n)` — n-th positional argument.
|
||||
//! - `PayloadSlot::EnvVar(name)` — set env var before calling.
|
||||
//! - Other slots produce `UnsupportedReason::EntryKindUnsupported`.
|
||||
//! - Other slots produce `UnsupportedReason::PayloadSlotUnsupported`.
|
||||
|
||||
use crate::dynamic::lang::{HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
|
|
@ -47,7 +47,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
// Validate payload slot.
|
||||
match &spec.payload_slot {
|
||||
PayloadSlot::Param(_) | PayloadSlot::EnvVar(_) | PayloadSlot::Stdin => {}
|
||||
_ => return Err(UnsupportedReason::EntryKindUnsupported),
|
||||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
||||
}
|
||||
|
||||
let source = generate_source(spec);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
//! - `PayloadSlot::Param(0)` — pass payload as `&str` first argument.
|
||||
//! - `PayloadSlot::EnvVar(name)` — set env var before calling entry.
|
||||
//! - All other slots (`Stdin`, `Param(n>0)`, `QueryParam`, `HttpBody`, `Argv`)
|
||||
//! produce `UnsupportedReason::EntryKindUnsupported`. Stdin piping into the
|
||||
//! produce `UnsupportedReason::PayloadSlotUnsupported`. Stdin piping into the
|
||||
//! generated harness is not yet wired (deferred).
|
||||
//!
|
||||
//! HTML_ESCAPE is n/a for Rust (§15.4).
|
||||
|
|
@ -55,7 +55,7 @@ impl LangEmitter for RustEmitter {
|
|||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
match &spec.payload_slot {
|
||||
PayloadSlot::Param(0) | PayloadSlot::EnvVar(_) => {}
|
||||
_ => return Err(UnsupportedReason::EntryKindUnsupported),
|
||||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
||||
}
|
||||
|
||||
let cargo_toml = generate_cargo_toml(spec.expected_cap);
|
||||
|
|
@ -262,7 +262,7 @@ mod tests {
|
|||
fn emit_param_gt_0_is_unsupported() {
|
||||
let spec = make_spec(PayloadSlot::Param(1));
|
||||
let err = emit(&spec).unwrap_err();
|
||||
assert_eq!(err, UnsupportedReason::EntryKindUnsupported);
|
||||
assert_eq!(err, UnsupportedReason::PayloadSlotUnsupported);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -567,24 +567,28 @@ fn build_verdict(
|
|||
toolchain_match: None,
|
||||
},
|
||||
Err(RunError::Harness(e)) => {
|
||||
// EntryKindUnsupported coming back from the lang emitter is
|
||||
// promoted to a structured `Inconclusive(EntryKindUnsupported)`
|
||||
// verdict so the operator sees the supported list + hint, not a
|
||||
// bare `Unsupported`. The pre-flight gate in `verify_finding`
|
||||
// catches the common case (entry_kind decided by spec
|
||||
// derivation); this arm covers the residual where an emitter
|
||||
// rejects a payload-slot / shape combination internally.
|
||||
// Defence-in-depth residual for `EntryKindUnsupported` from the
|
||||
// lang dispatcher. Promote to `Inconclusive(EntryKindUnsupported)`
|
||||
// so the operator sees the supported list + hint, but only when
|
||||
// the spec's entry kind is genuinely outside the supported list —
|
||||
// otherwise the pre-flight gate already handled it (or a stray
|
||||
// emitter mis-tagged a payload-slot rejection, which now uses
|
||||
// `PayloadSlotUnsupported` and falls through to the generic
|
||||
// `Unsupported(reason)` arm below).
|
||||
if let crate::dynamic::harness::HarnessError::Unsupported(
|
||||
UnsupportedReason::EntryKindUnsupported,
|
||||
) = &e
|
||||
{
|
||||
return entry_kind_unsupported_verdict(
|
||||
finding_id.to_owned(),
|
||||
None,
|
||||
&spec.entry_file,
|
||||
spec.lang,
|
||||
spec.entry_kind,
|
||||
);
|
||||
let supported = crate::dynamic::lang::entry_kinds_supported(spec.lang);
|
||||
if !supported.contains(&spec.entry_kind) {
|
||||
return entry_kind_unsupported_verdict(
|
||||
finding_id.to_owned(),
|
||||
None,
|
||||
&spec.entry_file,
|
||||
spec.lang,
|
||||
spec.entry_kind,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Typed `Unsupported(reason)` carries its semantics in `reason`; the
|
||||
// free-form `detail` is reserved for `Inconclusive`/unexpected paths
|
||||
|
|
|
|||
|
|
@ -172,6 +172,11 @@ pub enum UnsupportedReason {
|
|||
/// The entry kind (e.g. `HttpRoute`, `CliSubcommand`) is not yet supported;
|
||||
/// only `EntryKind::Function` is driven in current milestones.
|
||||
EntryKindUnsupported,
|
||||
/// The lang emitter does not yet support the spec's [`crate::dynamic::spec::PayloadSlot`]
|
||||
/// shape (e.g. `PayloadSlot::Param(n>0)` on Rust, `PayloadSlot::HttpBody`
|
||||
/// on JavaScript). Distinct from [`UnsupportedReason::EntryKindUnsupported`]:
|
||||
/// the entry kind is driveable, only the payload-injection slot is not.
|
||||
PayloadSlotUnsupported,
|
||||
/// Finding confidence is below `Medium`; dynamic verification is not
|
||||
/// attempted for low-confidence findings to avoid noise.
|
||||
ConfidenceTooLow,
|
||||
|
|
|
|||
|
|
@ -502,6 +502,7 @@ fn format_unsupported_reason(r: &crate::evidence::UnsupportedReason) -> String {
|
|||
match r {
|
||||
UnsupportedReason::BackendUnavailable => "backend unavailable".to_string(),
|
||||
UnsupportedReason::EntryKindUnsupported => "entry kind not supported".to_string(),
|
||||
UnsupportedReason::PayloadSlotUnsupported => "payload slot not supported".to_string(),
|
||||
UnsupportedReason::ConfidenceTooLow => "confidence too low".to_string(),
|
||||
UnsupportedReason::NoFlowSteps => "no flow steps".to_string(),
|
||||
UnsupportedReason::NoPayloadsForCap => "no payloads for cap".to_string(),
|
||||
|
|
|
|||
|
|
@ -312,4 +312,73 @@ mod spec_strategies {
|
|||
let spec = HarnessSpec::from_finding(&diag).unwrap();
|
||||
assert_eq!(spec.derivation, SpecDerivationStrategy::FromFlowSteps);
|
||||
}
|
||||
|
||||
// ── Phase 03 acceptance: entry-kind gate produces Inconclusive ───────────
|
||||
|
||||
/// Phase 03 promised that findings whose [`EntryKind`] is not in the
|
||||
/// emitter's supported list surface as
|
||||
/// `Inconclusive(EntryKindUnsupported { lang, attempted, supported, hint })`
|
||||
/// rather than `Unsupported`. End-to-end coverage:
|
||||
/// - construct an HttpRoute spec via `derive_from_callgraph_entry`
|
||||
/// (Python emitter currently advertises `[Function]` only);
|
||||
/// - drive it through `verify_finding`;
|
||||
/// - assert the verdict shape matches the promise.
|
||||
#[test]
|
||||
fn entry_kind_gate_promotes_unsupported_to_inconclusive_with_hint() {
|
||||
let mut diag = make_diag(
|
||||
"py.http.flask_route",
|
||||
"tests/dynamic_fixtures/spec_strategies/callgraph_entry_http.py",
|
||||
8,
|
||||
);
|
||||
let mut ev = Evidence::default();
|
||||
ev.sink_caps = Cap::SSRF.bits();
|
||||
diag.evidence = Some(ev);
|
||||
|
||||
// Sanity: the spec really does carry an HttpRoute entry kind.
|
||||
let spec = HarnessSpec::from_finding(&diag).unwrap();
|
||||
assert!(matches!(spec.entry_kind, EntryKind::HttpRoute));
|
||||
|
||||
let result = verify_finding(&diag, &VerifyOptions::default());
|
||||
assert_eq!(
|
||||
result.status,
|
||||
VerifyStatus::Inconclusive,
|
||||
"entry-kind gate must emit Inconclusive; got {:?}",
|
||||
result.status
|
||||
);
|
||||
assert!(
|
||||
result.reason.is_none(),
|
||||
"Inconclusive verdicts carry inconclusive_reason, not reason; got {:?}",
|
||||
result.reason
|
||||
);
|
||||
match result.inconclusive_reason {
|
||||
Some(InconclusiveReason::EntryKindUnsupported {
|
||||
lang,
|
||||
attempted,
|
||||
supported,
|
||||
hint,
|
||||
}) => {
|
||||
assert_eq!(lang, nyx_scanner::symbol::Lang::Python);
|
||||
assert!(matches!(attempted, EntryKind::HttpRoute));
|
||||
assert!(
|
||||
!supported.is_empty(),
|
||||
"supported list must be non-empty so operators can triage"
|
||||
);
|
||||
assert!(
|
||||
supported.contains(&EntryKind::Function),
|
||||
"Python emitter must advertise Function support; got {supported:?}"
|
||||
);
|
||||
assert!(
|
||||
!hint.is_empty(),
|
||||
"hint must guide the operator toward the gap"
|
||||
);
|
||||
assert!(
|
||||
hint.contains("HttpRoute"),
|
||||
"hint must name the attempted entry kind; got {hint:?}"
|
||||
);
|
||||
}
|
||||
other => panic!(
|
||||
"expected InconclusiveReason::EntryKindUnsupported, got {other:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue