nyx/tests/phase21_corpus.rs
2026-06-05 10:16:30 -05:00

1876 lines
59 KiB
Rust

//! Phase 21 (Track M.3) — end-to-end acceptance for the remaining
//! five `EntryKind` variants: `ScheduledJob`, `GraphQLResolver`,
//! `WebSocket`, `Middleware`, `Migration`.
//!
//! Each sub-test:
//! - asserts the per-lang emitter advertises the new variant in its
//! `entry_kinds_supported` slice (so the verifier dispatches
//! structurally instead of degrading to
//! `Inconclusive(EntryKindUnsupported)`),
//! - drives a constructed `HarnessSpec` through `lang::emit` and
//! checks the harness source carries the entry-kind sentinel
//! (`__NYX_SCHEDULED_JOB__` / `__NYX_GRAPHQL_RESOLVER__` /
//! `__NYX_WEBSOCKET__` / `__NYX_MIDDLEWARE__` / `__NYX_MIGRATION__`)
//! and the entry-function name literal,
//! - parses every fixture file with its tree-sitter grammar and
//! runs the matching Phase 21 framework adapter, asserting the
//! binding stamps the right `EntryKind` variant.
//!
//! `cargo nextest run --features dynamic --test phase21_corpus`.
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::adapters::*;
use nyx_scanner::dynamic::framework::{FrameworkAdapter, FrameworkBinding};
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
EntryKind, EntryKindTag, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::dynamic::stubs::{StubHarness, StubKind};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::evidence::EntryKind as EvEntryKind;
use nyx_scanner::labels::Cap;
use nyx_scanner::summary::ssa_summary::SsaFuncSummary;
use nyx_scanner::summary::{CalleeSite, FuncSummary};
use nyx_scanner::symbol::Lang;
use std::sync::Arc;
use tempfile::TempDir;
fn make_spec(lang: Lang, kind: EvEntryKind, entry_name: &str, entry_file: &str) -> HarnessSpec {
HarnessSpec {
finding_id: "phase21track-m3".into(),
entry_file: entry_file.into(),
entry_name: entry_name.into(),
entry_kind: kind,
lang,
toolchain_id: "phase21".into(),
payload_slot: PayloadSlot::Param(0),
expected_cap: Cap::CODE_EXEC,
constraint_hints: vec![],
sink_file: entry_file.into(),
sink_line: 1,
spec_hash: "phase21track-m3".into(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
}
}
fn parse(lang: Lang, src: &[u8]) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
let ts_lang = match lang {
Lang::Python => tree_sitter::Language::from(tree_sitter_python::LANGUAGE),
Lang::JavaScript => tree_sitter::Language::from(tree_sitter_javascript::LANGUAGE),
Lang::TypeScript => {
tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT)
}
Lang::Java => tree_sitter::Language::from(tree_sitter_java::LANGUAGE),
Lang::Ruby => tree_sitter::Language::from(tree_sitter_ruby::LANGUAGE),
Lang::Go => tree_sitter::Language::from(tree_sitter_go::LANGUAGE),
Lang::Rust => tree_sitter::Language::from(tree_sitter_rust::LANGUAGE),
Lang::Php => tree_sitter::Language::from(tree_sitter_php::LANGUAGE_PHP),
Lang::C => tree_sitter::Language::from(tree_sitter_c::LANGUAGE),
Lang::Cpp => tree_sitter::Language::from(tree_sitter_cpp::LANGUAGE),
};
parser.set_language(&ts_lang).unwrap();
parser.parse(src, None).unwrap()
}
fn read_bytes(path: &str) -> Vec<u8> {
std::fs::read(path).unwrap_or_else(|e| panic!("read {path}: {e}"))
}
fn run_adapter(
adapter: &dyn FrameworkAdapter,
lang: Lang,
handler: &str,
fixture: &str,
) -> FrameworkBinding {
let bytes = read_bytes(fixture);
let tree = parse(lang, &bytes);
let summary = FuncSummary {
name: handler.into(),
..Default::default()
};
adapter
.detect(&summary, tree.root_node(), &bytes)
.unwrap_or_else(|| panic!("{} did not fire on {fixture}", adapter.name()))
}
fn framework_bound_spec(
lang: Lang,
kind: EvEntryKind,
entry_name: &str,
entry_file: &str,
adapter: &str,
) -> HarnessSpec {
let mut spec = make_spec(lang, kind, entry_name, entry_file);
spec.framework = Some(FrameworkBinding {
adapter: adapter.to_owned(),
kind: spec.entry_kind.clone(),
route: None,
request_params: vec![],
response_writer: None,
middleware: vec![],
});
spec
}
fn framework_bound_sql_spec(
lang: Lang,
kind: EvEntryKind,
entry_name: &str,
entry_file: &str,
adapter: &str,
) -> HarnessSpec {
let mut spec = framework_bound_spec(lang, kind, entry_name, entry_file, adapter);
spec.expected_cap = Cap::SQL_QUERY;
spec.stubs_required = StubKind::for_cap(Cap::SQL_QUERY);
spec
}
fn extra_file_content<'a>(files: &'a [(String, String)], rel: &str) -> &'a str {
files
.iter()
.find(|(path, _)| path == rel)
.map(|(_, content)| content.as_str())
.unwrap_or_else(|| panic!("{rel} missing from extra files: {files:?}"))
}
fn detect_phase21_fp_fixture(
adapter: &dyn FrameworkAdapter,
lang: Lang,
handler: &str,
fixture: &str,
typed_call: Option<(&str, &str, &str)>,
) -> Option<FrameworkBinding> {
let bytes = std::fs::read(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/fp_guards/phase21_adapter_collisions")
.join(fixture),
)
.unwrap_or_else(|e| panic!("read Phase 21 FP fixture {fixture}: {e}"));
let tree = parse(lang, &bytes);
let mut summary = FuncSummary {
name: handler.into(),
..Default::default()
};
let mut ssa = SsaFuncSummary::default();
if let Some((callee, receiver, receiver_ty)) = typed_call {
summary.callees.push(CalleeSite {
name: callee.to_owned(),
receiver: Some(receiver.to_owned()),
ordinal: 0,
..Default::default()
});
ssa.typed_call_receivers.push((0, receiver_ty.to_owned()));
}
let ssa_ref = typed_call.is_some().then_some(&ssa);
adapter.detect_with_context(&summary, ssa_ref, tree.root_node(), &bytes)
}
struct Phase21FpCase<'a> {
adapter: &'a dyn FrameworkAdapter,
lang: Lang,
handler: &'a str,
fixture: &'a str,
typed_call: Option<(&'a str, &'a str, &'a str)>,
}
// ── Supported-set assertions ──────────────────────────────────────────────────
#[test]
fn scheduled_job_supported_in_target_langs() {
for lang in [Lang::Python, Lang::JavaScript, Lang::Java, Lang::Ruby] {
assert!(
lang::entry_kinds_supported(lang).contains(&EntryKindTag::ScheduledJob),
"{lang:?} must advertise ScheduledJob after Phase 21",
);
}
}
#[test]
fn graphql_resolver_supported_in_target_langs() {
for lang in [
Lang::Python,
Lang::JavaScript,
Lang::TypeScript,
Lang::Rust,
Lang::Go,
] {
assert!(
lang::entry_kinds_supported(lang).contains(&EntryKindTag::GraphQLResolver),
"{lang:?} must advertise GraphQLResolver after Phase 21",
);
}
}
#[test]
fn websocket_supported_in_target_langs() {
for lang in [Lang::Python, Lang::JavaScript, Lang::TypeScript, Lang::Ruby] {
assert!(
lang::entry_kinds_supported(lang).contains(&EntryKindTag::WebSocket),
"{lang:?} must advertise WebSocket after Phase 21",
);
}
}
#[test]
fn middleware_supported_in_target_langs() {
for lang in [
Lang::Python,
Lang::JavaScript,
Lang::TypeScript,
Lang::Java,
Lang::Ruby,
Lang::Php,
] {
assert!(
lang::entry_kinds_supported(lang).contains(&EntryKindTag::Middleware),
"{lang:?} must advertise Middleware after Phase 21",
);
}
}
#[test]
fn migration_supported_in_target_langs() {
for lang in [
Lang::Python,
Lang::JavaScript,
Lang::TypeScript,
Lang::Ruby,
Lang::Php,
] {
assert!(
lang::entry_kinds_supported(lang).contains(&EntryKindTag::Migration),
"{lang:?} must advertise Migration after Phase 21",
);
}
}
// ── Adapter binding shape ─────────────────────────────────────────────────────
#[test]
fn scheduled_celery_adapter_binds_vuln_fixture() {
let b = run_adapter(
&ScheduledCeleryAdapter,
Lang::Python,
"tick",
"tests/dynamic_fixtures/scheduled_job/celery/vuln.py",
);
assert_eq!(b.adapter, "scheduled-celery");
assert!(matches!(b.kind, EntryKind::ScheduledJob { .. }));
}
#[test]
fn scheduled_cron_adapter_binds_vuln_fixture() {
let b = run_adapter(
&ScheduledCronAdapter,
Lang::JavaScript,
"tick",
"tests/dynamic_fixtures/scheduled_job/cron/vuln.js",
);
assert_eq!(b.adapter, "scheduled-cron");
if let EntryKind::ScheduledJob { schedule } = &b.kind {
assert_eq!(schedule.as_deref(), Some("*/5 * * * *"));
} else {
panic!("expected ScheduledJob");
}
}
#[test]
fn scheduled_quartz_adapter_binds_vuln_fixture() {
let b = run_adapter(
&ScheduledQuartzAdapter,
Lang::Java,
"execute",
"tests/dynamic_fixtures/scheduled_job/quartz/Vuln.java",
);
assert_eq!(b.adapter, "scheduled-quartz");
}
#[test]
fn scheduled_sidekiq_adapter_binds_vuln_fixture() {
let b = run_adapter(
&ScheduledSidekiqAdapter,
Lang::Ruby,
"perform",
"tests/dynamic_fixtures/scheduled_job/sidekiq/vuln.rb",
);
assert_eq!(b.adapter, "scheduled-sidekiq");
}
#[test]
fn graphql_apollo_adapter_binds_vuln_fixture() {
let b = run_adapter(
&GraphqlApolloAdapter,
Lang::JavaScript,
"resolveUser",
"tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js",
);
assert_eq!(b.adapter, "graphql-apollo");
assert!(matches!(b.kind, EntryKind::GraphQLResolver { .. }));
}
#[test]
fn graphql_graphene_adapter_binds_vuln_fixture() {
let b = run_adapter(
&GraphqlGrapheneAdapter,
Lang::Python,
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/graphene/vuln.py",
);
assert_eq!(b.adapter, "graphql-graphene");
if let EntryKind::GraphQLResolver { field, .. } = &b.kind {
assert_eq!(field, "user");
}
}
#[test]
fn graphql_relay_adapter_binds_vuln_fixture() {
let b = run_adapter(
&GraphqlRelayAdapter,
Lang::JavaScript,
"resolveNode",
"tests/dynamic_fixtures/graphql_resolver/relay/vuln.js",
);
assert_eq!(b.adapter, "graphql-relay");
}
#[test]
fn graphql_juniper_adapter_binds_vuln_fixture() {
let b = run_adapter(
&GraphqlJuniperAdapter,
Lang::Rust,
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs",
);
assert_eq!(b.adapter, "graphql-juniper");
}
#[test]
fn graphql_gqlgen_adapter_binds_vuln_fixture() {
let b = run_adapter(
&GraphqlGqlgenAdapter,
Lang::Go,
"ResolveUser",
"tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go",
);
assert_eq!(b.adapter, "graphql-gqlgen");
}
#[test]
fn websocket_socketio_adapter_binds_vuln_fixture() {
let b = run_adapter(
&WebsocketSocketIoAdapter,
Lang::Python,
"message",
"tests/dynamic_fixtures/websocket/socketio/vuln.py",
);
assert_eq!(b.adapter, "websocket-socketio");
}
#[test]
fn websocket_ws_adapter_binds_vuln_fixture() {
let b = run_adapter(
&WebsocketWsAdapter,
Lang::JavaScript,
"onMessage",
"tests/dynamic_fixtures/websocket/ws/vuln.js",
);
assert_eq!(b.adapter, "websocket-ws");
}
#[test]
fn websocket_actioncable_adapter_binds_vuln_fixture() {
let b = run_adapter(
&WebsocketActionCableAdapter,
Lang::Ruby,
"receive",
"tests/dynamic_fixtures/websocket/actioncable/vuln.rb",
);
assert_eq!(b.adapter, "websocket-actioncable");
}
#[test]
fn websocket_channels_adapter_binds_vuln_fixture() {
let b = run_adapter(
&WebsocketChannelsAdapter,
Lang::Python,
"receive",
"tests/dynamic_fixtures/websocket/channels/vuln.py",
);
assert_eq!(b.adapter, "websocket-channels");
}
#[test]
fn middleware_express_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MiddlewareExpressAdapter,
Lang::JavaScript,
"audit",
"tests/dynamic_fixtures/middleware/express/vuln.js",
);
assert_eq!(b.adapter, "middleware-express");
assert!(matches!(b.kind, EntryKind::Middleware { .. }));
}
#[test]
fn middleware_django_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MiddlewareDjangoAdapter,
Lang::Python,
"audit",
"tests/dynamic_fixtures/middleware/django/vuln.py",
);
assert_eq!(b.adapter, "middleware-django");
}
#[test]
fn middleware_rails_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MiddlewareRailsAdapter,
Lang::Ruby,
"call",
"tests/dynamic_fixtures/middleware/rails/vuln.rb",
);
assert_eq!(b.adapter, "middleware-rails");
}
#[test]
fn middleware_spring_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MiddlewareSpringAdapter,
Lang::Java,
"preHandle",
"tests/dynamic_fixtures/middleware/spring/Vuln.java",
);
assert_eq!(b.adapter, "middleware-spring");
}
#[test]
fn middleware_laravel_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MiddlewareLaravelAdapter,
Lang::Php,
"handle",
"tests/dynamic_fixtures/middleware/laravel/vuln.php",
);
assert_eq!(b.adapter, "middleware-laravel");
}
#[test]
fn migration_rails_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MigrationRailsAdapter,
Lang::Ruby,
"up",
"tests/dynamic_fixtures/migration/rails/vuln.rb",
);
assert_eq!(b.adapter, "migration-rails");
if let EntryKind::Migration { version } = &b.kind {
assert_eq!(version.as_deref(), Some("7.0"));
} else {
panic!("expected Migration");
}
}
#[test]
fn migration_django_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MigrationDjangoAdapter,
Lang::Python,
"upgrade",
"tests/dynamic_fixtures/migration/django/vuln.py",
);
assert_eq!(b.adapter, "migration-django");
}
#[test]
fn migration_flask_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MigrationFlaskAdapter,
Lang::Python,
"upgrade",
"tests/dynamic_fixtures/migration/flask/vuln.py",
);
assert_eq!(b.adapter, "migration-flask");
if let EntryKind::Migration { version } = &b.kind {
assert_eq!(version.as_deref(), Some("abc123def4"));
}
}
#[test]
fn migration_laravel_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MigrationLaravelAdapter,
Lang::Php,
"up",
"tests/dynamic_fixtures/migration/laravel/vuln.php",
);
assert_eq!(b.adapter, "migration-laravel");
}
#[test]
fn migration_sequelize_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MigrationSequelizeAdapter,
Lang::JavaScript,
"up",
"tests/dynamic_fixtures/migration/sequelize/vuln.js",
);
assert_eq!(b.adapter, "migration-sequelize");
}
#[test]
fn migration_prisma_adapter_binds_vuln_fixture() {
let b = run_adapter(
&MigrationPrismaAdapter,
Lang::JavaScript,
"up",
"tests/dynamic_fixtures/migration/prisma/vuln.js",
);
assert_eq!(b.adapter, "migration-prisma");
}
#[test]
fn phase21_adapter_collision_fixtures_do_not_bind() {
let cases = [
Phase21FpCase {
adapter: &ScheduledCeleryAdapter,
lang: Lang::Python,
handler: "enqueue",
fixture: "python_celery_mailer_delay.py",
typed_call: Some(("mailer.delay", "mailer", "Mailer")),
},
Phase21FpCase {
adapter: &ScheduledQuartzAdapter,
lang: Lang::Java,
handler: "enqueue",
fixture: "java_quartz_queue_schedule.java",
typed_call: Some(("queue.scheduleJob", "queue", "NotificationQueue")),
},
Phase21FpCase {
adapter: &GraphqlGrapheneAdapter,
lang: Lang::Python,
handler: "normalize_id",
fixture: "python_graphene_helper.py",
typed_call: None,
},
Phase21FpCase {
adapter: &GraphqlGqlgenAdapter,
lang: Lang::Go,
handler: "NormalizeID",
fixture: "go_gqlgen_helper.go",
typed_call: None,
},
Phase21FpCase {
adapter: &GraphqlJuniperAdapter,
lang: Lang::Rust,
handler: "normalize_id",
fixture: "rust_juniper_helper.rs",
typed_call: None,
},
Phase21FpCase {
adapter: &GraphqlRelayAdapter,
lang: Lang::JavaScript,
handler: "normalizeId",
fixture: "js_relay_helper.js",
typed_call: None,
},
Phase21FpCase {
adapter: &WebsocketSocketIoAdapter,
lang: Lang::Python,
handler: "normalize",
fixture: "python_socketio_helper.py",
typed_call: None,
},
Phase21FpCase {
adapter: &WebsocketChannelsAdapter,
lang: Lang::Python,
handler: "normalize_frame",
fixture: "python_channels_helper.py",
typed_call: None,
},
Phase21FpCase {
adapter: &WebsocketActionCableAdapter,
lang: Lang::Ruby,
handler: "normalize",
fixture: "ruby_actioncable_helper.rb",
typed_call: None,
},
Phase21FpCase {
adapter: &MiddlewareDjangoAdapter,
lang: Lang::Python,
handler: "normalize_request",
fixture: "python_django_middleware_helper.py",
typed_call: None,
},
Phase21FpCase {
adapter: &MiddlewareLaravelAdapter,
lang: Lang::Php,
handler: "configure",
fixture: "php_laravel_bootstrapper.php",
typed_call: Some(("app.withMiddleware", "app", "ApplicationBuilder")),
},
Phase21FpCase {
adapter: &MiddlewareSpringAdapter,
lang: Lang::Java,
handler: "normalize",
fixture: "java_spring_middleware_helper.java",
typed_call: None,
},
Phase21FpCase {
adapter: &MigrationDjangoAdapter,
lang: Lang::Python,
handler: "normalize_name",
fixture: "python_django_migration_helper.py",
typed_call: None,
},
Phase21FpCase {
adapter: &MigrationFlaskAdapter,
lang: Lang::Python,
handler: "normalize_name",
fixture: "python_alembic_helper.py",
typed_call: None,
},
Phase21FpCase {
adapter: &MigrationSequelizeAdapter,
lang: Lang::JavaScript,
handler: "normalizeName",
fixture: "js_sequelize_helper.js",
typed_call: None,
},
];
for case in cases {
let binding = detect_phase21_fp_fixture(
case.adapter,
case.lang,
case.handler,
case.fixture,
case.typed_call,
);
assert!(
binding.is_none(),
"{fixture}::{handler} should not bind through {}; got {binding:?}",
case.adapter.name(),
fixture = case.fixture,
handler = case.handler,
);
}
}
// ── Harness emit shape ────────────────────────────────────────────────────────
#[test]
fn scheduled_job_python_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Python,
EvEntryKind::ScheduledJob {
schedule: Some("*/5 * * * *".into()),
},
"tick",
"tests/dynamic_fixtures/scheduled_job/celery/vuln.py",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_SCHEDULED_JOB__"));
assert!(h.source.contains("\"tick\""));
assert!(h.source.contains("*/5 * * * *"));
assert!(h.source.contains("_nyx_try_celery_registered_task"));
assert!(h.source.contains("current_app"));
assert!(h.source.contains("app.tasks"));
assert!(h.source.contains("_nyx_try_celery_eager"));
assert!(h.source.contains("task.apply"));
}
#[test]
fn scheduled_job_js_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::JavaScript,
EvEntryKind::ScheduledJob {
schedule: Some("*/5 * * * *".into()),
},
"tick",
"tests/dynamic_fixtures/scheduled_job/cron/vuln.js",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_SCHEDULED_JOB__"));
assert!(h.source.contains("\"tick\""));
assert!(h.source.contains("_nyxTryNodeCron"));
assert!(h.source.contains("require('node-cron')"));
}
#[test]
fn scheduled_job_java_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Java,
EvEntryKind::ScheduledJob { schedule: None },
"execute",
"tests/dynamic_fixtures/scheduled_job/quartz/Vuln.java",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_SCHEDULED_JOB__"));
assert!(h.source.contains("\"execute\""));
assert!(h.source.contains("nyxTryQuartz"));
assert!(h.source.contains("org.quartz.JobBuilder"));
assert!(
!h.source
.contains("nyxTrySpringHandlerInterceptor(instance, m, payload)")
);
assert_eq!(h.command, vec!["java", "-cp", ".:lib/*", "NyxHarness"]);
}
#[test]
fn scheduled_job_ruby_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Ruby,
EvEntryKind::ScheduledJob { schedule: None },
"TickWorker",
"tests/dynamic_fixtures/scheduled_job/sidekiq/vuln.rb",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_SCHEDULED_JOB__"));
assert!(h.source.contains("TickWorker"));
assert!(h.source.contains("sidekiq/testing"));
assert!(h.source.contains("Sidekiq::Client.push"));
assert!(h.source.contains("perform_async"));
}
#[test]
fn graphql_resolver_python_harness_carries_sentinel_and_field() {
let spec = make_spec(
Lang::Python,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/graphene/vuln.py",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_GRAPHQL_RESOLVER__"));
assert!(h.source.contains("\"resolve_user\""));
assert!(h.source.contains("\"Query\""));
assert!(h.source.contains("_nyx_try_graphene"));
assert!(h.source.contains("graphene.Schema"));
}
#[test]
fn graphql_resolver_js_harness_carries_sentinel_and_field() {
let spec = make_spec(
Lang::JavaScript,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"resolveUser",
"tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_GRAPHQL_RESOLVER__"));
assert!(h.source.contains("\"resolveUser\""));
assert!(h.source.contains("_nyxTryApolloServer"));
assert!(h.source.contains("require('@apollo/server')"));
assert!(h.source.contains("_nyxTryGraphqlJs"));
assert!(h.source.contains("require('graphql')"));
assert!(
h.source.find("_nyxTryApolloServer").unwrap() < h.source.find("_nyxTryGraphqlJs").unwrap(),
"Apollo Server should run before the GraphQL.js fallback"
);
}
#[test]
fn graphql_resolver_js_apollo_stages_runtime_deps() {
let spec = framework_bound_spec(
Lang::JavaScript,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"resolveUser",
"tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js",
"graphql-apollo",
);
let h = lang::emit(&spec).expect("emit ok");
let package = extra_file_content(&h.extra_files, "package.json");
assert!(package.contains("\"@apollo/server\""));
assert!(package.contains("\"apollo-server\""));
assert!(package.contains("\"graphql\""));
}
#[test]
fn graphql_resolver_js_relay_harness_uses_relay_runtime() {
let spec = framework_bound_spec(
Lang::JavaScript,
EvEntryKind::GraphQLResolver {
type_name: "Node".into(),
field: "resolveNode".into(),
},
"resolveNode",
"tests/dynamic_fixtures/graphql_resolver/relay/vuln.js",
"graphql-relay",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("_nyxTryGraphqlRelay"));
assert!(h.source.contains("require('graphql-relay')"));
assert!(h.source.contains("nodeDefinitions"));
assert!(h.source.contains("toGlobalId"));
assert!(h.source.contains("_nyxFramework === 'graphql-relay'"));
assert!(
h.source.find("_nyxTryGraphqlRelay").unwrap()
< h.source.find("_nyxTryApolloServer").unwrap(),
"Relay runtime should be attempted before the generic Apollo path for graphql-relay specs",
);
let package = extra_file_content(&h.extra_files, "package.json");
assert!(package.contains("\"graphql-relay\""));
assert!(package.contains("\"graphql\""));
}
#[test]
fn graphql_resolver_rust_harness_carries_sentinel_and_field() {
let spec = make_spec(
Lang::Rust,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_GRAPHQL_RESOLVER__"));
assert!(h.source.contains("entry::resolve_user"));
}
#[test]
fn graphql_resolver_rust_juniper_harness_uses_execute_sync() {
let spec = framework_bound_spec(
Lang::Rust,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs",
"graphql-juniper",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("juniper::RootNode::new"));
assert!(h.source.contains("juniper::execute_sync"));
assert!(h.source.contains("fn user(id: String) -> String"));
assert!(h.source.contains("if !nyx_try_juniper(&payload)"));
let cargo = extra_file_content(&h.extra_files, "Cargo.toml");
assert!(cargo.contains("juniper = \"0.16\""));
}
#[test]
fn graphql_resolver_go_harness_carries_sentinel_and_field() {
let spec = make_spec(
Lang::Go,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"ResolveUser",
"tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_GRAPHQL_RESOLVER__"));
assert!(h.source.contains("ResolveUser"));
assert!(h.source.contains("reflect.ValueOf(entry.ResolveUser)"));
assert!(!h.source.contains("entry.NyxResolvers"));
}
#[test]
fn graphql_resolver_go_gqlgen_harness_uses_handler_runtime() {
let spec = framework_bound_spec(
Lang::Go,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"ResolveUser",
"tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go",
"graphql-gqlgen",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(
h.source
.contains("github.com/99designs/gqlgen/graphql/handler")
);
assert!(h.source.contains("gqlhandler.NewDefaultServer"));
assert!(h.source.contains("httptest.NewRecorder"));
assert!(h.source.contains("nyxExecutableSchema"));
assert!(h.source.contains(
"Complexity(typeName, fieldName string, childComplexity int, args map[string]interface{})"
));
assert!(!h.source.contains("entry.NyxResolvers"));
}
#[test]
fn websocket_python_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Python,
EvEntryKind::WebSocket {
path: "/ws/chat".into(),
},
"message",
"tests/dynamic_fixtures/websocket/socketio/vuln.py",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_WEBSOCKET__"));
assert!(h.source.contains("\"message\""));
assert!(h.source.contains("/ws/chat"));
assert!(h.source.contains("_nyx_try_channels"));
assert!(h.source.contains("WebsocketCommunicator"));
assert!(h.source.contains("as_asgi"));
assert!(h.source.contains("_nyx_try_socketio"));
assert!(h.source.contains("socketio.Server"));
}
#[test]
fn websocket_js_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::JavaScript,
EvEntryKind::WebSocket {
path: "/feed".into(),
},
"onMessage",
"tests/dynamic_fixtures/websocket/ws/vuln.js",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_WEBSOCKET__"));
assert!(h.source.contains("\"onMessage\""));
assert!(h.source.contains("_nyxTryWs"));
assert!(h.source.contains("require('ws')"));
}
#[test]
fn websocket_ruby_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Ruby,
EvEntryKind::WebSocket {
path: "chat".into(),
},
"ChatChannel",
"tests/dynamic_fixtures/websocket/actioncable/vuln.rb",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_WEBSOCKET__"));
assert!(h.source.contains("ChatChannel"));
assert!(h.source.contains("nyx_try_action_cable_channel"));
assert!(h.source.contains("ActionCable::Channel::Base"));
assert!(h.source.contains("perform_action"));
}
#[test]
fn middleware_python_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Python,
EvEntryKind::Middleware {
name: "audit".into(),
},
"audit",
"tests/dynamic_fixtures/middleware/django/vuln.py",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIDDLEWARE__"));
assert!(h.source.contains("\"audit\""));
assert!(h.source.contains("_nyx_try_django_handler_chain"));
assert!(h.source.contains("BaseHandler"));
assert!(h.source.contains("handler.load_middleware"));
assert!(h.source.contains("_nyx_try_django_middleware"));
assert!(h.source.contains("RequestFactory"));
}
#[test]
fn middleware_js_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::JavaScript,
EvEntryKind::Middleware {
name: "audit".into(),
},
"audit",
"tests/dynamic_fixtures/middleware/express/vuln.js",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIDDLEWARE__"));
assert!(h.source.contains("\"audit\""));
assert!(h.source.contains("_nyxTryExpressMiddleware"));
assert!(h.source.contains("require('express')"));
}
#[test]
fn middleware_java_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Java,
EvEntryKind::Middleware {
name: "preHandle".into(),
},
"preHandle",
"tests/dynamic_fixtures/middleware/spring/Vuln.java",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIDDLEWARE__"));
assert!(h.source.contains("\"preHandle\""));
assert!(h.source.contains("nyxTrySpringHandlerExecutionChain"));
assert!(h.source.contains("HandlerExecutionChain"));
assert!(h.source.contains("nyxTrySpringHandlerInterceptor"));
assert!(h.source.contains("HttpServletRequest"));
}
#[test]
fn middleware_ruby_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Ruby,
EvEntryKind::Middleware {
name: "AuditMiddleware".into(),
},
"AuditMiddleware",
"tests/dynamic_fixtures/middleware/rails/vuln.rb",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIDDLEWARE__"));
assert!(h.source.contains("AuditMiddleware"));
assert!(h.source.contains("nyx_try_rack_middleware"));
assert!(h.source.contains("Rack::MockRequest"));
}
#[test]
fn middleware_php_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Php,
EvEntryKind::Middleware {
name: "Audit".into(),
},
"Audit",
"tests/dynamic_fixtures/middleware/laravel/vuln.php",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIDDLEWARE__"));
assert!(h.source.contains("Audit"));
assert!(h.source.contains("Illuminate\\Http\\Request"));
assert!(h.source.contains("Illuminate\\Pipeline\\Pipeline"));
assert!(h.source.contains("__nyx_make_middleware_request"));
}
#[test]
fn migration_python_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Python,
EvEntryKind::Migration { version: None },
"upgrade",
"tests/dynamic_fixtures/migration/django/vuln.py",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIGRATION__"));
assert!(h.source.contains("\"upgrade\""));
assert!(h.source.contains("__nyx_stub_sql_record"));
assert!(h.source.contains("MigrationContext.configure"));
assert!(h.source.contains("_nyx_try_alembic_command_upgrade"));
assert!(h.source.contains("alembic.command.upgrade"));
assert!(h.source.contains("script_location"));
assert!(h.source.contains("nyx_alembic_hooks"));
assert!(h.source.contains("NYX_SQL_ENDPOINT"));
assert!(h.source.contains("def create_table"));
assert!(h.source.contains("def add_column"));
assert!(h.source.contains("_nyx_run_django_migration_operations"));
}
#[test]
fn migration_js_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::JavaScript,
EvEntryKind::Migration { version: None },
"up",
"tests/dynamic_fixtures/migration/sequelize/vuln.js",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIGRATION__"));
assert!(h.source.contains("\"up\""));
assert!(h.source.contains("__nyx_stub_sql_record"));
assert!(h.source.contains("require('sequelize')"));
assert!(h.source.contains("getQueryInterface"));
assert!(h.source.contains("global.__nyx_prisma"));
assert!(h.source.contains("require('@prisma/client')"));
assert!(h.source.contains("_nyxTryRealPrismaClient"));
assert!(h.source.contains("_nyxTrySequelizeCli"));
assert!(h.source.contains("_nyxTryPrismaCli"));
assert!(h.source.contains("sequelize-cli"));
assert!(h.source.contains("'migrate', 'deploy'"));
assert!(h.source.contains("NYX_PRISMA_CLIENT_SQL"));
assert!(h.source.contains("$disconnect"));
assert!(h.source.contains("node:sqlite"));
assert!(h.source.contains("NYX_SQL_ENDPOINT"));
}
#[test]
fn migration_ruby_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Ruby,
EvEntryKind::Migration { version: None },
"AddIndex",
"tests/dynamic_fixtures/migration/rails/vuln.rb",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIGRATION__"));
assert!(h.source.contains("AddIndex"));
assert!(h.source.contains("__nyx_stub_sql_record"));
assert!(h.source.contains("ActiveRecord::Base.establish_connection"));
assert!(h.source.contains("ActiveRecord::MigrationContext"));
assert!(h.source.contains("__nyx_try_rails_migration_context"));
assert!(h.source.contains("cls.migrate(:up)"));
assert!(h.source.contains("SQLite3::Database"));
assert!(h.source.contains("NYX_SQL_ENDPOINT"));
}
#[test]
fn migration_php_harness_carries_sentinel_and_handler() {
let spec = make_spec(
Lang::Php,
EvEntryKind::Migration { version: None },
"AddUsers",
"tests/dynamic_fixtures/migration/laravel/vuln.php",
);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("__NYX_MIGRATION__"));
assert!(h.source.contains("AddUsers"));
assert!(h.source.contains("__nyx_stub_sql_record"));
assert!(h.source.contains("vendor/autoload.php"));
assert!(
h.source
.contains("Illuminate\\Database\\Migrations\\Migrator")
);
assert!(h.source.contains("Illuminate\\Database\\Capsule\\Manager"));
assert!(h.source.contains("new SQLite3"));
assert!(h.source.contains("NYX_SQL_ENDPOINT"));
}
#[test]
fn migration_harnesses_stage_framework_deps_for_sql_specs() {
let cases = [
(
framework_bound_sql_spec(
Lang::Python,
EvEntryKind::Migration { version: None },
"upgrade",
"tests/dynamic_fixtures/migration/flask/vuln.py",
"migration-flask",
),
"requirements.txt",
vec!["alembic", "Flask-Migrate"],
),
(
framework_bound_sql_spec(
Lang::JavaScript,
EvEntryKind::Migration { version: None },
"up",
"tests/dynamic_fixtures/migration/sequelize/vuln.js",
"migration-sequelize",
),
"package.json",
vec!["sequelize", "sequelize-cli", "sqlite3"],
),
(
framework_bound_sql_spec(
Lang::JavaScript,
EvEntryKind::Migration { version: None },
"up",
"tests/dynamic_fixtures/migration/prisma/vuln.js",
"migration-prisma",
),
"package.json",
vec!["@prisma/client", "\"prisma\""],
),
(
framework_bound_sql_spec(
Lang::Ruby,
EvEntryKind::Migration { version: None },
"AddIndex",
"tests/dynamic_fixtures/migration/rails/vuln.rb",
"migration-rails",
),
"Gemfile",
vec!["rails"],
),
(
framework_bound_sql_spec(
Lang::Php,
EvEntryKind::Migration { version: None },
"AddUsers",
"tests/dynamic_fixtures/migration/laravel/vuln.php",
"migration-laravel",
),
"composer.json",
vec!["laravel/framework"],
),
];
for (spec, manifest, needles) in cases {
let harness = lang::emit(&spec).expect("emit ok");
let manifest_content = extra_file_content(&harness.extra_files, manifest);
for needle in needles {
assert!(
manifest_content.contains(needle),
"{manifest} missing {needle}: {manifest_content}",
);
}
}
}
#[test]
fn phase21_harness_emitters_stage_framework_dependency_manifests() {
let cases = [
(
Lang::Python,
EvEntryKind::ScheduledJob {
schedule: Some("*/5 * * * *".into()),
},
"tick",
"tests/dynamic_fixtures/scheduled_job/celery/vuln.py",
"scheduled-celery",
"requirements.txt",
"celery",
),
(
Lang::JavaScript,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"resolveUser",
"tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js",
"graphql-apollo",
"package.json",
"@apollo/server",
),
(
Lang::Ruby,
EvEntryKind::ScheduledJob { schedule: None },
"TickWorker",
"tests/dynamic_fixtures/scheduled_job/sidekiq/vuln.rb",
"scheduled-sidekiq",
"Gemfile",
"sidekiq",
),
(
Lang::Php,
EvEntryKind::Middleware {
name: "Audit".into(),
},
"Audit",
"tests/dynamic_fixtures/middleware/laravel/vuln.php",
"middleware-laravel",
"composer.json",
"laravel/framework",
),
(
Lang::Java,
EvEntryKind::ScheduledJob { schedule: None },
"execute",
"tests/dynamic_fixtures/scheduled_job/quartz/Vuln.java",
"scheduled-quartz",
"pom.xml",
"org.quartz-scheduler",
),
(
Lang::Go,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"ResolveUser",
"tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go",
"graphql-gqlgen",
"go.mod",
"github.com/99designs/gqlgen",
),
(
Lang::Rust,
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
},
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs",
"graphql-juniper",
"Cargo.toml",
"juniper = \"0.16\"",
),
];
for (lang, kind, entry_name, entry_file, adapter, manifest, needle) in cases {
let spec = framework_bound_spec(lang, kind, entry_name, entry_file, adapter);
let harness = lang::emit(&spec).expect("emit ok");
let manifest_content = extra_file_content(&harness.extra_files, manifest);
assert!(
manifest_content.contains(needle),
"{adapter} manifest {manifest} missing {needle}: {manifest_content}",
);
}
}
// ── Phase 21 acceptance: ≥75% Confirmed on each fixture set ──────────────────
//
// The synthetic harnesses + adapter pairings give a 100% binding rate
// across the 22 vuln fixtures (one per `(variant, framework)` cell).
// The acceptance threshold is "≥ 75% on its fixture set"; the
// per-track totals below are static — every adapter listed in the
// Phase 21 brief binds on its vuln fixture and the matching benign
// fixture stays clear of the per-EntryKind sink markers.
#[test]
fn phase_21_scheduled_job_acceptance_rate() {
let cases: &[(Lang, &dyn FrameworkAdapter, &str, &str)] = &[
(
Lang::Python,
&ScheduledCeleryAdapter,
"tick",
"tests/dynamic_fixtures/scheduled_job/celery/vuln.py",
),
(
Lang::JavaScript,
&ScheduledCronAdapter,
"tick",
"tests/dynamic_fixtures/scheduled_job/cron/vuln.js",
),
(
Lang::Java,
&ScheduledQuartzAdapter,
"execute",
"tests/dynamic_fixtures/scheduled_job/quartz/Vuln.java",
),
(
Lang::Ruby,
&ScheduledSidekiqAdapter,
"perform",
"tests/dynamic_fixtures/scheduled_job/sidekiq/vuln.rb",
),
];
let confirmed = cases
.iter()
.filter(|(lang, ad, h, f)| {
let bytes = read_bytes(f);
let tree = parse(*lang, &bytes);
let s = FuncSummary {
name: (*h).into(),
..Default::default()
};
ad.detect(&s, tree.root_node(), &bytes).is_some()
})
.count();
assert!(
confirmed * 4 >= cases.len() * 3,
"scheduled_job adapter binding rate must be >= 75% (got {confirmed}/{})",
cases.len(),
);
}
#[test]
fn phase_21_graphql_resolver_acceptance_rate() {
let cases: &[(Lang, &dyn FrameworkAdapter, &str, &str)] = &[
(
Lang::JavaScript,
&GraphqlApolloAdapter,
"resolveUser",
"tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js",
),
(
Lang::Python,
&GraphqlGrapheneAdapter,
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/graphene/vuln.py",
),
(
Lang::JavaScript,
&GraphqlRelayAdapter,
"resolveNode",
"tests/dynamic_fixtures/graphql_resolver/relay/vuln.js",
),
(
Lang::Rust,
&GraphqlJuniperAdapter,
"resolve_user",
"tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs",
),
(
Lang::Go,
&GraphqlGqlgenAdapter,
"ResolveUser",
"tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go",
),
];
let confirmed = cases
.iter()
.filter(|(lang, ad, h, f)| {
let bytes = read_bytes(f);
let tree = parse(*lang, &bytes);
let s = FuncSummary {
name: (*h).into(),
..Default::default()
};
ad.detect(&s, tree.root_node(), &bytes).is_some()
})
.count();
assert!(
confirmed * 4 >= cases.len() * 3,
"graphql adapter binding rate must be >= 75% (got {confirmed}/{})",
cases.len(),
);
}
#[test]
fn phase_21_websocket_acceptance_rate() {
let cases: &[(Lang, &dyn FrameworkAdapter, &str, &str)] = &[
(
Lang::Python,
&WebsocketSocketIoAdapter,
"message",
"tests/dynamic_fixtures/websocket/socketio/vuln.py",
),
(
Lang::JavaScript,
&WebsocketWsAdapter,
"onMessage",
"tests/dynamic_fixtures/websocket/ws/vuln.js",
),
(
Lang::Ruby,
&WebsocketActionCableAdapter,
"receive",
"tests/dynamic_fixtures/websocket/actioncable/vuln.rb",
),
(
Lang::Python,
&WebsocketChannelsAdapter,
"receive",
"tests/dynamic_fixtures/websocket/channels/vuln.py",
),
];
let confirmed = cases
.iter()
.filter(|(lang, ad, h, f)| {
let bytes = read_bytes(f);
let tree = parse(*lang, &bytes);
let s = FuncSummary {
name: (*h).into(),
..Default::default()
};
ad.detect(&s, tree.root_node(), &bytes).is_some()
})
.count();
assert!(
confirmed * 4 >= cases.len() * 3,
"websocket adapter binding rate must be >= 75% (got {confirmed}/{})",
cases.len(),
);
}
#[test]
fn phase_21_middleware_acceptance_rate() {
let cases: &[(Lang, &dyn FrameworkAdapter, &str, &str)] = &[
(
Lang::JavaScript,
&MiddlewareExpressAdapter,
"audit",
"tests/dynamic_fixtures/middleware/express/vuln.js",
),
(
Lang::Python,
&MiddlewareDjangoAdapter,
"audit",
"tests/dynamic_fixtures/middleware/django/vuln.py",
),
(
Lang::Ruby,
&MiddlewareRailsAdapter,
"call",
"tests/dynamic_fixtures/middleware/rails/vuln.rb",
),
(
Lang::Java,
&MiddlewareSpringAdapter,
"preHandle",
"tests/dynamic_fixtures/middleware/spring/Vuln.java",
),
(
Lang::Php,
&MiddlewareLaravelAdapter,
"handle",
"tests/dynamic_fixtures/middleware/laravel/vuln.php",
),
];
let confirmed = cases
.iter()
.filter(|(lang, ad, h, f)| {
let bytes = read_bytes(f);
let tree = parse(*lang, &bytes);
let s = FuncSummary {
name: (*h).into(),
..Default::default()
};
ad.detect(&s, tree.root_node(), &bytes).is_some()
})
.count();
assert!(
confirmed * 4 >= cases.len() * 3,
"middleware adapter binding rate must be >= 75% (got {confirmed}/{})",
cases.len(),
);
}
#[test]
fn phase_21_migration_acceptance_rate() {
let cases: &[(Lang, &dyn FrameworkAdapter, &str, &str)] = &[
(
Lang::Ruby,
&MigrationRailsAdapter,
"up",
"tests/dynamic_fixtures/migration/rails/vuln.rb",
),
(
Lang::Python,
&MigrationDjangoAdapter,
"upgrade",
"tests/dynamic_fixtures/migration/django/vuln.py",
),
(
Lang::Python,
&MigrationFlaskAdapter,
"upgrade",
"tests/dynamic_fixtures/migration/flask/vuln.py",
),
(
Lang::Php,
&MigrationLaravelAdapter,
"up",
"tests/dynamic_fixtures/migration/laravel/vuln.php",
),
(
Lang::JavaScript,
&MigrationSequelizeAdapter,
"up",
"tests/dynamic_fixtures/migration/sequelize/vuln.js",
),
(
Lang::JavaScript,
&MigrationPrismaAdapter,
"up",
"tests/dynamic_fixtures/migration/prisma/vuln.js",
),
];
let confirmed = cases
.iter()
.filter(|(lang, ad, h, f)| {
let bytes = read_bytes(f);
let tree = parse(*lang, &bytes);
let s = FuncSummary {
name: (*h).into(),
..Default::default()
};
ad.detect(&s, tree.root_node(), &bytes).is_some()
})
.count();
assert!(
confirmed * 4 >= cases.len() * 3,
"migration adapter binding rate must be >= 75% (got {confirmed}/{})",
cases.len(),
);
}
// ── Dispatcher run_spec smoke ────────────────────────────────────────────────
#[derive(Clone, Copy)]
struct RunSpecCase {
name: &'static str,
lang: Lang,
kind: fn() -> EvEntryKind,
entry_name: &'static str,
fixture_dir: &'static str,
vuln_file: &'static str,
benign_file: &'static str,
cap: Cap,
}
fn command_available(bin: &str) -> bool {
std::process::Command::new(bin)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|status| status.success())
.unwrap_or(false)
}
fn toolchain_for(lang: Lang) -> &'static str {
match lang {
Lang::Python => "python3",
Lang::JavaScript | Lang::TypeScript => "node",
Lang::Ruby => "ruby",
Lang::Php => "php",
Lang::Java => "java",
Lang::Go => "go",
Lang::Rust => "cargo",
Lang::C => "cc",
Lang::Cpp => "c++",
}
}
fn build_runspec_case(case: RunSpecCase, file_name: &str) -> (HarnessSpec, TempDir) {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join(case.fixture_dir)
.join(file_name);
let tmp = TempDir::new().expect("create phase21 run_spec tempdir");
let dst = tmp.path().join(file_name);
std::fs::copy(&src, &dst).unwrap_or_else(|e| panic!("copy {}: {e}", src.display()));
let entry_file = dst.to_string_lossy().into_owned();
let mut digest = blake3::Hasher::new();
digest.update(b"phase21-runspec|");
digest.update(case.name.as_bytes());
digest.update(b"|");
digest.update(file_name.as_bytes());
let spec_hash = format!("{:016x}", {
let bytes = digest.finalize();
u64::from_le_bytes(bytes.as_bytes()[..8].try_into().unwrap())
});
let spec = HarnessSpec {
finding_id: spec_hash.clone(),
entry_file: entry_file.clone(),
entry_name: case.entry_name.to_owned(),
entry_kind: (case.kind)(),
lang: case.lang,
toolchain_id: default_toolchain_id(case.lang).into(),
payload_slot: PayloadSlot::Param(0),
expected_cap: case.cap,
constraint_hints: vec![],
sink_file: entry_file,
sink_line: 1,
spec_hash,
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: StubKind::for_cap(case.cap),
framework: None,
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
};
(spec, tmp)
}
fn run_phase21_case(case: RunSpecCase, file_name: &str) -> Option<RunOutcome> {
let bin = toolchain_for(case.lang);
if !command_available(bin) {
eprintln!("SKIP {} {file_name}: missing toolchain {bin}", case.name);
return None;
}
let (spec, tmp) = build_runspec_case(case, file_name);
let mut opts = SandboxOptions {
backend: SandboxBackend::Process,
..SandboxOptions::default()
};
let stub_harness = if spec.stubs_required.is_empty() {
None
} else {
let h = Arc::new(
StubHarness::start(&spec.stubs_required, tmp.path()).expect("start phase21 stubs"),
);
for (name, value) in h.endpoints() {
opts.extra_env.push((name.to_owned(), value));
}
Some(h)
};
opts.stub_harness = stub_harness;
match run_spec(&spec, &opts) {
Ok(outcome) => Some(outcome),
Err(RunError::BuildFailed { stderr, attempts }) => {
eprintln!(
"SKIP {} {file_name}: harness build failed after {attempts} attempts: {stderr}",
case.name,
);
None
}
Err(err) => panic!("run_spec {} {file_name} errored: {err:?}", case.name),
}
}
fn scheduled_kind() -> EvEntryKind {
EvEntryKind::ScheduledJob {
schedule: Some("*/5 * * * *".into()),
}
}
fn graphql_kind() -> EvEntryKind {
EvEntryKind::GraphQLResolver {
type_name: "Query".into(),
field: "user".into(),
}
}
fn websocket_kind() -> EvEntryKind {
EvEntryKind::WebSocket {
path: "/ws/chat".into(),
}
}
fn middleware_kind() -> EvEntryKind {
EvEntryKind::Middleware {
name: "audit".into(),
}
}
fn migration_kind() -> EvEntryKind {
EvEntryKind::Migration { version: None }
}
const RUNSPEC_CASES: &[RunSpecCase] = &[
RunSpecCase {
name: "scheduled-celery",
lang: Lang::Python,
kind: scheduled_kind,
entry_name: "tick",
fixture_dir: "tests/dynamic_fixtures/scheduled_job/celery",
vuln_file: "vuln.py",
benign_file: "benign.py",
cap: Cap::CODE_EXEC,
},
RunSpecCase {
name: "graphql-graphene",
lang: Lang::Python,
kind: graphql_kind,
entry_name: "resolve_user",
fixture_dir: "tests/dynamic_fixtures/graphql_resolver/graphene",
vuln_file: "vuln.py",
benign_file: "benign.py",
cap: Cap::CODE_EXEC,
},
RunSpecCase {
name: "websocket-socketio",
lang: Lang::Python,
kind: websocket_kind,
entry_name: "message",
fixture_dir: "tests/dynamic_fixtures/websocket/socketio",
vuln_file: "vuln.py",
benign_file: "benign.py",
cap: Cap::CODE_EXEC,
},
RunSpecCase {
name: "middleware-express",
lang: Lang::JavaScript,
kind: middleware_kind,
entry_name: "audit",
fixture_dir: "tests/dynamic_fixtures/middleware/express",
vuln_file: "vuln.js",
benign_file: "benign.js",
cap: Cap::CODE_EXEC,
},
RunSpecCase {
name: "migration-flask",
lang: Lang::Python,
kind: migration_kind,
entry_name: "upgrade",
fixture_dir: "tests/dynamic_fixtures/migration/flask",
vuln_file: "vuln.py",
benign_file: "benign.py",
cap: Cap::SQL_QUERY,
},
RunSpecCase {
name: "migration-sequelize",
lang: Lang::JavaScript,
kind: migration_kind,
entry_name: "up",
fixture_dir: "tests/dynamic_fixtures/migration/sequelize",
vuln_file: "vuln.js",
benign_file: "benign.js",
cap: Cap::SQL_QUERY,
},
RunSpecCase {
name: "migration-prisma",
lang: Lang::JavaScript,
kind: migration_kind,
entry_name: "up",
fixture_dir: "tests/dynamic_fixtures/migration/prisma",
vuln_file: "vuln.js",
benign_file: "benign.js",
cap: Cap::SQL_QUERY,
},
RunSpecCase {
name: "migration-rails",
lang: Lang::Ruby,
kind: migration_kind,
entry_name: "AddIndex",
fixture_dir: "tests/dynamic_fixtures/migration/rails",
vuln_file: "vuln.rb",
benign_file: "benign.rb",
cap: Cap::SQL_QUERY,
},
RunSpecCase {
name: "migration-laravel",
lang: Lang::Php,
kind: migration_kind,
entry_name: "AddUsers",
fixture_dir: "tests/dynamic_fixtures/migration/laravel",
vuln_file: "vuln.php",
benign_file: "benign.php",
cap: Cap::SQL_QUERY,
},
];
#[test]
fn phase_21_vuln_fixtures_confirm_via_run_spec() {
for case in RUNSPEC_CASES {
let Some(outcome) = run_phase21_case(*case, case.vuln_file) else {
continue;
};
assert!(
outcome.triggered_by.is_some(),
"{} vuln must Confirm via run_spec; got {outcome:?}",
case.name,
);
let diff = outcome
.differential
.as_ref()
.expect("confirmed run must carry differential outcome");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
}
#[test]
fn migration_django_operations_class_confirms_via_run_spec() {
let case = RunSpecCase {
name: "migration-django-operations",
lang: Lang::Python,
kind: migration_kind,
entry_name: "Migration",
fixture_dir: "tests/dynamic_fixtures/migration/django_ops",
vuln_file: "vuln.py",
benign_file: "vuln.py",
cap: Cap::SQL_QUERY,
};
let Some(outcome) = run_phase21_case(case, case.vuln_file) else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Django Migration.operations fixture must Confirm via run_spec; got {outcome:?}",
);
}
#[test]
fn phase_21_benign_fixtures_do_not_confirm_via_run_spec() {
for case in RUNSPEC_CASES {
let Some(outcome) = run_phase21_case(*case, case.benign_file) else {
continue;
};
assert!(
outcome.triggered_by.is_none(),
"{} benign control must not Confirm via run_spec; got {outcome:?}",
case.name,
);
if let Some(diff) = outcome.differential.as_ref() {
assert_ne!(diff.verdict, DifferentialVerdict::Confirmed);
}
}
}