mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
refactor(dynamic): centralize runtime dependency handling across frameworks, enhance manifest generation for Rust, Java, Python, Go, and PHP, and improve framework adapter integration
This commit is contained in:
parent
ed398e2834
commit
ed96f94bb5
12 changed files with 1202 additions and 50 deletions
|
|
@ -26,6 +26,7 @@ use nyx_scanner::dynamic::environment::{
|
|||
MAX_WORKDIR_BYTES, capture_project_dependencies, capture_project_dependencies_with_context,
|
||||
stage_workdir_full,
|
||||
};
|
||||
use nyx_scanner::dynamic::framework::FrameworkBinding;
|
||||
use nyx_scanner::dynamic::lang::materialize_runtime;
|
||||
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy};
|
||||
use nyx_scanner::labels::Cap;
|
||||
|
|
@ -190,6 +191,144 @@ fn materialize_runtime_synthesises_pinned_manifest() {
|
|||
assert!(content.contains(&spec.spec_hash));
|
||||
}
|
||||
|
||||
fn adapter_bound_spec(
|
||||
lang: Lang,
|
||||
entry_file: &str,
|
||||
adapter: &str,
|
||||
entry_kind: EntryKind,
|
||||
) -> HarnessSpec {
|
||||
HarnessSpec {
|
||||
finding_id: format!("adapter-{adapter}"),
|
||||
entry_file: entry_file.to_owned(),
|
||||
entry_name: "run".to_owned(),
|
||||
entry_kind: entry_kind.clone(),
|
||||
lang,
|
||||
toolchain_id: match lang {
|
||||
Lang::Python => "python-3.11",
|
||||
Lang::JavaScript | Lang::TypeScript => "node-20",
|
||||
Lang::Java => "java-21",
|
||||
Lang::Go => "go-1.21",
|
||||
Lang::Rust => "rust-stable",
|
||||
Lang::Php => "php-8.2",
|
||||
Lang::Ruby => "ruby-3.2",
|
||||
_ => "toolchain",
|
||||
}
|
||||
.to_owned(),
|
||||
payload_slot: PayloadSlot::Param(0),
|
||||
expected_cap: Cap::CODE_EXEC,
|
||||
constraint_hints: vec![],
|
||||
sink_file: entry_file.to_owned(),
|
||||
sink_line: 1,
|
||||
spec_hash: format!("hash-{adapter}"),
|
||||
derivation: SpecDerivationStrategy::FromFlowSteps,
|
||||
stubs_required: vec![],
|
||||
framework: Some(FrameworkBinding {
|
||||
adapter: adapter.to_owned(),
|
||||
kind: entry_kind,
|
||||
route: None,
|
||||
request_params: vec![],
|
||||
response_writer: None,
|
||||
middleware: vec![],
|
||||
}),
|
||||
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn materialize_runtime_adds_framework_adapter_deps_without_imports() {
|
||||
let root = TempDir::new().unwrap();
|
||||
let cases = [
|
||||
(
|
||||
Lang::Python,
|
||||
"task.py",
|
||||
"scheduled-celery",
|
||||
EntryKind::ScheduledJob {
|
||||
schedule: Some("* * * * *".to_owned()),
|
||||
},
|
||||
"requirements.txt",
|
||||
"celery",
|
||||
),
|
||||
(
|
||||
Lang::JavaScript,
|
||||
"resolver.js",
|
||||
"graphql-apollo",
|
||||
EntryKind::GraphQLResolver {
|
||||
type_name: "Query".to_owned(),
|
||||
field: "user".to_owned(),
|
||||
},
|
||||
"package.json",
|
||||
"@apollo/server",
|
||||
),
|
||||
(
|
||||
Lang::Ruby,
|
||||
"worker.rb",
|
||||
"scheduled-sidekiq",
|
||||
EntryKind::ScheduledJob { schedule: None },
|
||||
"Gemfile",
|
||||
"sidekiq",
|
||||
),
|
||||
(
|
||||
Lang::Php,
|
||||
"Middleware.php",
|
||||
"middleware-laravel",
|
||||
EntryKind::Middleware {
|
||||
name: "AuthMiddleware".to_owned(),
|
||||
},
|
||||
"composer.json",
|
||||
"laravel/framework",
|
||||
),
|
||||
(
|
||||
Lang::Java,
|
||||
"QuartzJob.java",
|
||||
"scheduled-quartz",
|
||||
EntryKind::ScheduledJob { schedule: None },
|
||||
"pom.xml",
|
||||
"org.quartz-scheduler",
|
||||
),
|
||||
(
|
||||
Lang::Go,
|
||||
"resolver.go",
|
||||
"graphql-gqlgen",
|
||||
EntryKind::GraphQLResolver {
|
||||
type_name: "Query".to_owned(),
|
||||
field: "user".to_owned(),
|
||||
},
|
||||
"go.mod",
|
||||
"github.com/99designs/gqlgen",
|
||||
),
|
||||
(
|
||||
Lang::Rust,
|
||||
"resolver.rs",
|
||||
"graphql-juniper",
|
||||
EntryKind::GraphQLResolver {
|
||||
type_name: "Query".to_owned(),
|
||||
field: "user".to_owned(),
|
||||
},
|
||||
"Cargo.toml",
|
||||
"juniper = \"0.16\"",
|
||||
),
|
||||
];
|
||||
|
||||
for (lang, entry_file, adapter, entry_kind, manifest, needle) in cases {
|
||||
std::fs::write(root.path().join(entry_file), "/* marker-only fixture */\n").unwrap();
|
||||
let spec = adapter_bound_spec(lang, entry_file, adapter, entry_kind);
|
||||
let captured = capture_project_dependencies(root.path(), &spec);
|
||||
let stage = TempDir::new().unwrap();
|
||||
let env = stage_workdir_full(&captured, stage.path(), &spec.spec_hash, lang)
|
||||
.expect("stage workdir");
|
||||
let artifacts = materialize_runtime(&env);
|
||||
let (_, content) = artifacts
|
||||
.files
|
||||
.iter()
|
||||
.find(|(rel, _)| rel == manifest)
|
||||
.unwrap_or_else(|| panic!("{adapter} did not materialize {manifest}"));
|
||||
assert!(
|
||||
content.contains(needle),
|
||||
"{adapter} manifest {manifest} missing {needle}: {content}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workdir_is_importable_when_python_available() {
|
||||
// Acceptance bullet: "the route boots and the verifier reaches the
|
||||
|
|
|
|||
|
|
@ -98,6 +98,33 @@ fn run_adapter(
|
|||
.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 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,
|
||||
|
|
@ -920,6 +947,98 @@ fn migration_php_harness_carries_sentinel_and_handler() {
|
|||
assert!(h.source.contains("AddUsers"));
|
||||
}
|
||||
|
||||
#[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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue