From fe17a286ab51afbb07b91959de393c5cec299d66 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 27 May 2026 14:49:27 +0100 Subject: [PATCH] feat(assail): exempt Julia *Ext.jl / ext/ dirs from DynamicCodeExecution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Julia's package-extension mechanism uses `eval` and `Meta.parse` as a core idiom in `*Ext.jl` files (and the conventional `ext/.jl` layout). Treating these as DCE findings produces mass false positives in any Julia repository — julia-ecosystem#6 logged 209 findings with ~202 of them this pattern. Match the shape of PR #53 (JSON-LD InsecureProtocol exemption): add a small predicate at the detection site that subtracts the known- idiomatic pattern before the WeakPoint is constructed. Regression tests cover the *Ext.jl filename, ext/ directory, and non-extension control case (which must still flag). Closes the bulk of julia-ecosystem#6. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/assail/analyzer.rs | 60 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/assail/analyzer.rs b/src/assail/analyzer.rs index e7fc725..92a3e53 100644 --- a/src/assail/analyzer.rs +++ b/src/assail/analyzer.rs @@ -4457,8 +4457,18 @@ impl Analyzer { weak_points: &mut Vec, file_path: &str, ) -> Result<()> { + // Julia package-extension pattern: *Ext.jl files use eval/Meta.parse + // idiomatically as part of the language's extension mechanism. Skip DCE + // detection for these files to avoid mass false positives. + let is_julia_package_extension = file_path.ends_with("Ext.jl") + || file_path.starts_with("ext/") + || file_path.contains("/ext/") + || file_path.contains("\\ext\\"); + // eval / Meta.parse (dynamic code execution) - if content.contains("eval(") || content.contains("Meta.parse(") { + if !is_julia_package_extension + && (content.contains("eval(") || content.contains("Meta.parse(")) + { weak_points.push(WeakPoint { file: None, line: None, @@ -7763,4 +7773,52 @@ pub fn safe_get_x() -> Option { "unsafe fn / unsafe extern must not count toward the unsafe-block tally" ); } + + // --------------------------------------------------------------- + // Julia package-extension DCE exemption + // --------------------------------------------------------------- + + fn count_julia_dce(content: &str, file_path: &str) -> usize { + let analyzer = Analyzer::new(std::path::Path::new(".")).expect("analyzer construction"); + let mut stats = ProgramStatistics::default(); + let mut wp = Vec::new(); + analyzer + .analyze_julia(content, &mut stats, &mut wp, file_path) + .expect("analyze_julia"); + wp.iter() + .filter(|w| matches!(w.category, WeakPointCategory::DynamicCodeExecution)) + .count() + } + + #[test] + fn julia_ext_jl_dce_is_exempt() { + let src = r#"function __init__() Meta.parse("1 + 1") end"#; + assert_eq!( + count_julia_dce(src, "FooExt.jl"), + 0, + "*Ext.jl files use eval/Meta.parse idiomatically — must be exempt" + ); + } + + #[test] + fn julia_ext_dir_dce_is_exempt() { + // Per Julia convention some package extensions live under ext/.jl + // rather than the trailing-Ext.jl filename — both shapes must skip DCE. + let src = r#"eval(:(x = 1))"#; + assert_eq!( + count_julia_dce(src, "ext/MyExtension.jl"), + 0, + "files under ext/ must be exempt" + ); + } + + #[test] + fn julia_regular_file_still_flags_eval() { + // Non-extension Julia files must still report eval/Meta.parse usage. + let src = r#"function dangerous() eval(user_input) end"#; + assert!( + count_julia_dce(src, "src/dangerous.jl") > 0, + "non-extension Julia files must still flag eval()" + ); + } }