From 1a34767054c1f9641742993a0029ab62d5a7b753 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Mon, 6 Apr 2026 20:44:10 -0400 Subject: [PATCH 01/10] =?UTF-8?q?fix:=20P0+P1=20hotfix=20batch=20=E2=80=94?= =?UTF-8?q?=20KV-cache=20crash,=20default=20features,=20allowlist,=20syslo?= =?UTF-8?q?g,=20synthesis,=20tiering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #147: KV-cache attention mask off-by-one crashes all live inference - Remove erroneous +1 from decode-step attention_len in both run_prompt and run_prompt_cached; hoist initial_cache_len so decode loop can account for prior cache padding. Fixes #149: Default build has no inference features - Change cli/Cargo.toml default features from [] to [onnx] so cargo install produces a working --live binary out of the box. - Add compile-time bail when --live is used on a no-inference build. Fixes #151: sc/wmic missing from Windows command allowlist - Add sc, wmic, schtasks to WINDOWS_COMMAND_ALLOWLIST so priv-esc-review and scheduled-task enumeration work on Windows. Fixes #153: read_syslog dry-run defaults to README.md - Replace ./README.md fallback with platform-appropriate log path (/var/log/syslog on Linux, System.evtx on Windows). - Fix check_tool_precondition to use the same platform path. Fixes #155: Basic tier final_answer is mechanical concatenation - Rewrite basic_tier_summary_for_task to group findings by severity, add cross-reference hints, deduplicate actions, and present prioritized recommendations. Fixes #157: PARAM_BASIC_CEILING_B=2.0 too high - Lower threshold from 2.0 to 1.0 so 1B+ models (Qwen2.5-0.5B, Llama-3.2-1B) reach Moderate tier and actually use inference. --- cli/Cargo.toml | 2 +- cli/src/main.rs | 7 ++ core_engine/src/agent.rs | 9 +- core_engine/src/lib.rs | 149 +++++++++++++++++++++++------ cyber_tools/src/lib.rs | 9 +- inference_bridge/src/lib.rs | 10 +- inference_bridge/src/onnx_vitis.rs | 26 +++-- 7 files changed, 166 insertions(+), 46 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6835b47..24aeb07 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true license.workspace = true [features] -default = [] +default = ["onnx"] onnx = ["inference_bridge/onnx"] vitis = ["inference_bridge/vitis"] directml = ["inference_bridge/directml"] diff --git a/cli/src/main.rs b/cli/src/main.rs index b43afd7..e7c37b0 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -294,6 +294,13 @@ fn validate_live_runtime_preflight(runtime: &RuntimeConfig) -> Result<()> { return Ok(()); } + // Fail fast when the binary was compiled without inference support (#149). + #[cfg(not(feature = "onnx"))] + bail!( + "Live inference requested but this binary was built without inference support. \ + Rebuild with `--features onnx` (or `--features vitis`/`--features directml`)." + ); + if !runtime.model.is_file() { bail!( "Live mode model file not found: {}. Run '--doctor --live --introspection-format json' (or '--doctor --live --fix') and provide a readable --model path.", diff --git a/core_engine/src/agent.rs b/core_engine/src/agent.rs index 4f117c1..cc75b9c 100644 --- a/core_engine/src/agent.rs +++ b/core_engine/src/agent.rs @@ -351,9 +351,12 @@ impl Agent { fn check_tool_precondition(&self, tool_name: &str) -> bool { match tool_name { "read_syslog" => { - // Default path is ./agent.log — skip if it doesn't exist and - // the sandbox policy would deny access anyway. - let default_path = std::path::Path::new("./agent.log"); + // Use a platform-appropriate default log path (#153). + let default_path = if cfg!(target_os = "windows") { + std::path::Path::new("C:\\Windows\\System32\\winevt\\Logs\\System.evtx") + } else { + std::path::Path::new("/var/log/syslog") + }; if !default_path.exists() { return false; } diff --git a/core_engine/src/lib.rs b/core_engine/src/lib.rs index 262569e..1b48807 100644 --- a/core_engine/src/lib.rs +++ b/core_engine/src/lib.rs @@ -1,6 +1,6 @@ pub mod agent; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use inference_bridge::ModelCapabilityProbe; use serde::{Deserialize, Serialize, Serializer}; @@ -101,7 +101,9 @@ static BUILTIN_TEMPLATES: [InvestigationTemplate; 7] = [ // ── Capability tiering thresholds (const, easy to tune) ── /// Models below this parameter count (billions) are classified as Basic. -const PARAM_BASIC_CEILING_B: f32 = 2.0; +/// Lowered from 2.0 → 1.0 so common 1B+ models (Qwen2.5-0.5B overestimates +/// to ~1.4B, Llama-3.2-1B at ~1.12B) reach Moderate tier and use inference (#157). +const PARAM_BASIC_CEILING_B: f32 = 1.0; /// Models above this parameter count (billions) are classified as Strong. const PARAM_STRONG_FLOOR_B: f32 = 10.0; /// Latency above this (ms/tok) demotes to Basic. @@ -1179,10 +1181,17 @@ pub fn basic_tier_summary_for_task(findings: &[Finding], task: Option<&str>) -> return format!("{prefix}\nFINDINGS:\n(none)\nRISK: info\nACTIONS:\n(none)"); } - let max_sev = findings - .iter() + // Sort findings: highest severity first, then by confidence descending (#155). + let mut sorted: Vec<&Finding> = findings.iter().collect(); + sorted.sort_by(|a, b| { + b.severity + .cmp(&a.severity) + .then_with(|| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal)) + }); + + let max_sev = sorted + .first() .map(|f| f.severity) - .max() .unwrap_or(FindingSeverity::Info); let distinct_tools: HashSet<&str> = findings @@ -1190,34 +1199,94 @@ pub fn basic_tier_summary_for_task(findings: &[Finding], task: Option<&str>) -> .filter_map(|f| f.evidence_pointer.tool.as_deref()) .collect(); + // -- Header -- let mut out = match task { Some(t) => format!( - "SUMMARY: Task \"{t}\" produced {} findings across {} tool(s). Maximum severity: {}.\nFINDINGS:\n", - findings.len(), - distinct_tools.len(), - max_sev.token() + "INVESTIGATION SUMMARY — \"{t}\"\n\ + {total} findings across {tools} tool(s). Maximum severity: {sev}.\n\n", + total = findings.len(), + tools = distinct_tools.len(), + sev = max_sev.token().to_uppercase() ), None => format!( - "SUMMARY: {} findings detected. Maximum severity: {}.\nFINDINGS:\n", - findings.len(), - max_sev.token() + "INVESTIGATION SUMMARY\n\ + {total} findings detected. Maximum severity: {sev}.\n\n", + total = findings.len(), + sev = max_sev.token().to_uppercase() ), }; - for (i, f) in findings.iter().enumerate() { - out.push_str(&format!( - "{}. {} [{}] — {}\n", - i + 1, - f.title, - f.severity.token(), - f.recommended_action - )); + // -- Group by severity (highest first) -- + let severity_order = [ + FindingSeverity::Critical, + FindingSeverity::High, + FindingSeverity::Medium, + FindingSeverity::Low, + FindingSeverity::Info, + ]; + + for &sev in &severity_order { + let group: Vec<&&Finding> = sorted.iter().filter(|f| f.severity == sev).collect(); + if group.is_empty() { + continue; + } + let header = match sev { + FindingSeverity::Critical => "🔴 CRITICAL", + FindingSeverity::High => "🟠 HIGH", + FindingSeverity::Medium => "🟡 MEDIUM", + FindingSeverity::Low => "🔵 LOW", + FindingSeverity::Info => "ℹ️ INFO", + }; + out.push_str(&format!("── {} ({}) ──\n", header, group.len())); + for f in &group { + let tool_tag = f + .evidence_pointer + .tool + .as_deref() + .map(|t| format!(" [{}]", t)) + .unwrap_or_default(); + out.push_str(&format!( + " • {}{} — {}\n", + f.title, tool_tag, f.recommended_action + )); + } + out.push('\n'); } - out.push_str(&format!("RISK: {}\nACTIONS:\n", max_sev.token())); + // -- Cross-references: find tools whose findings overlap -- + if distinct_tools.len() > 1 { + // Collect tool→titles mapping for cross-reference hints. + let mut tool_titles: HashMap<&str, Vec<&str>> = HashMap::new(); + for f in &sorted { + if let Some(tool) = f.evidence_pointer.tool.as_deref() { + tool_titles.entry(tool).or_default().push(&f.title); + } + } + if tool_titles.len() > 1 { + out.push_str("CROSS-REFERENCES:\n"); + let tools_vec: Vec<&&str> = tool_titles.keys().collect(); + out.push_str(&format!( + " Data was collected from {} sources ({}). ", + tools_vec.len(), + tools_vec.iter().map(|t| **t).collect::>().join(", ") + )); + out.push_str("Correlate findings across tools for a complete picture.\n\n"); + } + } - for (i, f) in findings.iter().enumerate() { - out.push_str(&format!("{}. {}\n", i + 1, f.recommended_action)); + // -- Risk assessment -- + out.push_str(&format!("OVERALL RISK: {}\n\n", max_sev.token().to_uppercase())); + + // -- Prioritized actions (deduplicated, urgent first) -- + out.push_str("RECOMMENDED ACTIONS (priority order):\n"); + let mut seen_actions: HashSet<&str> = HashSet::new(); + let mut action_idx = 0usize; + for f in &sorted { + let action = f.recommended_action.as_str(); + if seen_actions.insert(action) { + action_idx += 1; + out.push_str(&format!(" {}. {}\n", action_idx, action)); + } } // Remove trailing newline for clean output. @@ -1733,8 +1802,9 @@ mod tests { #[test] fn classify_small_model_as_basic() { + // With PARAM_BASIC_CEILING_B = 1.0 (#157), a 0.5B model is Basic. let probe = ModelCapabilityProbe { - estimated_param_billions: 1.2, + estimated_param_billions: 0.5, execution_provider: "CPUExecutionProvider".to_string(), smoke_latency_ms: 80, vocab_size: 32000, @@ -1742,6 +1812,18 @@ mod tests { assert_eq!(classify_capability(&probe), ModelCapabilityTier::Basic); } + #[test] + fn classify_1b_model_as_moderate() { + // With PARAM_BASIC_CEILING_B = 1.0 (#157), a 1.2B model reaches Moderate. + let probe = ModelCapabilityProbe { + estimated_param_billions: 1.2, + execution_provider: "CPUExecutionProvider".to_string(), + smoke_latency_ms: 80, + vocab_size: 32000, + }; + assert_eq!(classify_capability(&probe), ModelCapabilityTier::Moderate); + } + #[test] fn classify_medium_model_moderate_latency_as_moderate() { let probe = ModelCapabilityProbe { @@ -1859,12 +1941,15 @@ mod tests { ]; let summary = basic_tier_summary(&findings); - assert!(summary.starts_with("SUMMARY: 2 findings detected. Maximum severity: high.")); - assert!(summary.contains("FINDINGS:")); - assert!(summary.contains("1. Active listeners [high]")); - assert!(summary.contains("2. Suspicious persistence [medium]")); - assert!(summary.contains("RISK: high")); - assert!(summary.contains("ACTIONS:")); + // Updated format groups by severity and provides cross-references (#155). + assert!(summary.contains("INVESTIGATION SUMMARY")); + assert!(summary.contains("2 findings")); + assert!(summary.contains("HIGH")); + assert!(summary.contains("Active listeners")); + assert!(summary.contains("Suspicious persistence")); + assert!(summary.contains("OVERALL RISK: HIGH")); + assert!(summary.contains("RECOMMENDED ACTIONS")); + assert!(summary.contains("CROSS-REFERENCES")); } #[test] @@ -1891,8 +1976,10 @@ mod tests { "observation.indicator_count", )]; let summary = basic_tier_summary_for_task(&findings, Some("windows-triage")); - assert!(summary.contains("Task \"windows-triage\"")); + // Updated format includes task name in header (#155). + assert!(summary.contains("windows-triage")); assert!(summary.contains("1 tool(s)")); + assert!(summary.contains("RECOMMENDED ACTIONS")); } // ── Discrete confidence label tests (#85) ── diff --git a/cyber_tools/src/lib.rs b/cyber_tools/src/lib.rs index 3bd5862..bcd6df3 100644 --- a/cyber_tools/src/lib.rs +++ b/cyber_tools/src/lib.rs @@ -81,10 +81,11 @@ impl SandboxPolicy { } #[cfg(target_os = "windows")] - let command_allowlist: HashSet = ["whoami", "netstat", "net", "tasklist", "reg"] - .into_iter() - .map(|c| c.to_string()) - .collect(); + let command_allowlist: HashSet = + ["whoami", "netstat", "net", "tasklist", "reg", "sc", "wmic", "schtasks"] + .into_iter() + .map(|c| c.to_string()) + .collect(); #[cfg(not(target_os = "windows"))] let command_allowlist: HashSet = ["id", "ss", "sudo"] diff --git a/inference_bridge/src/lib.rs b/inference_bridge/src/lib.rs index 65414b4..4950c88 100644 --- a/inference_bridge/src/lib.rs +++ b/inference_bridge/src/lib.rs @@ -495,8 +495,16 @@ impl OnnxVitisEngine { format!(r#"{{"tool":"hash_binary","args":{{"path":"{path}"}}}}"#) } "read_syslog" => { + // Use a platform-appropriate default log path instead of a + // project file like README.md, which would produce bogus + // findings (#153). + let default_path = if cfg!(target_os = "windows") { + "C:\\Windows\\System32\\winevt\\Logs\\System.evtx".to_string() + } else { + "/var/log/syslog".to_string() + }; let path = Self::guess_path_from_task(task) - .unwrap_or_else(|| "./README.md".to_string()); + .unwrap_or(default_path); let path = Self::escape_json_string(&path); let max_lines = Self::guess_line_count_from_task(task).unwrap_or(200); format!( diff --git a/inference_bridge/src/onnx_vitis.rs b/inference_bridge/src/onnx_vitis.rs index b3a6079..9ac881e 100644 --- a/inference_bridge/src/onnx_vitis.rs +++ b/inference_bridge/src/onnx_vitis.rs @@ -2712,6 +2712,10 @@ fn run_prompt_on_session( bail!("prompt encoding produced no token IDs"); } + // Track initial KV-cache padding length so the decode loop can account + // for it in the attention mask (#147). + let mut initial_cache_len: usize = 0; + if cache_enabled { // --- Prefix cache reuse (#65) --- // Find how many leading tokens match the previous prompt. @@ -2782,7 +2786,7 @@ fn run_prompt_on_session( // Same fix as run_prompt: account for forced cache padding when // cache tensors will be included during prefill (#136). - let initial_cache_len: usize = + initial_cache_len = if cache.layout.use_cache.is_none() && !cache_state.is_empty() { cache_state .values() @@ -2847,13 +2851,16 @@ fn run_prompt_on_session( for step in 0..config.max_new_tokens.max(1) { let step_started = Instant::now(); let (decode_with_cache, step_input_ids, attention_len) = if cache_enabled { - // attention_len = past KV-cache entries + 1 current decode token (#114). + // attention_len = past KV-cache entries (context_ids tracks total + // processed tokens; initial_cache_len accounts for prior cache + // padding). The model internally handles the current decode + // token, so we must NOT add +1 here (#147). ( true, vec![*context_ids .last() .ok_or_else(|| anyhow!("empty context ids"))?], - (context_ids.len() + 1).max(1), + (context_ids.len() + initial_cache_len).max(1), ) } else { let step_input_ids = context_ids.clone(); @@ -3047,6 +3054,10 @@ pub fn run_prompt(config: &ModelConfig, prompt: &str) -> Result { bail!("prompt encoding produced no token IDs"); } + // Track initial KV-cache padding length so the decode loop can account + // for it in the attention mask (#147). + let mut initial_cache_len: usize = 0; + if cache_enabled { // Batch prefill: ingest the entire prompt in a single forward pass. let prefill_started = Instant::now(); @@ -3057,7 +3068,7 @@ pub fn run_prompt(config: &ModelConfig, prompt: &str) -> Result { // attention will concatenate past (length 1) + current (length N), // producing a key sequence of N+1. The attention mask must match that // total, otherwise we get a broadcast shape error (#136). - let initial_cache_len: usize = if layout.use_cache.is_none() && !cache_state.is_empty() { + initial_cache_len = if layout.use_cache.is_none() && !cache_state.is_empty() { // Cache IS included during prefill — get its actual past-axis size. cache_state .values() @@ -3110,13 +3121,16 @@ pub fn run_prompt(config: &ModelConfig, prompt: &str) -> Result { for step in 0..config.max_new_tokens.max(1) { let step_started = Instant::now(); let (decode_with_cache, step_input_ids, attention_len) = if cache_enabled { - // attention_len = past KV-cache entries + 1 current decode token (#114). + // attention_len = past KV-cache entries (context_ids tracks total + // processed tokens; initial_cache_len accounts for prior cache + // padding). The model internally handles the current decode + // token, so we must NOT add +1 here (#147). ( true, vec![*context_ids .last() .ok_or_else(|| anyhow!("empty context ids"))?], - (context_ids.len() + 1).max(1), + (context_ids.len() + initial_cache_len).max(1), ) } else { let step_input_ids = context_ids.clone(); From 5102025d7a2e533fa6549a40d0475f69aa0bb858 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 16:06:30 -0400 Subject: [PATCH 02/10] =?UTF-8?q?style:=20cargo=20fmt=20=E2=80=94=20fix=20?= =?UTF-8?q?formatting=20for=20CI=20quality=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core_engine/src/lib.rs | 13 +++++++++---- cyber_tools/src/lib.rs | 11 ++++++----- inference_bridge/src/onnx_vitis.rs | 25 ++++++++++++------------- test_outputs/test_server.db | Bin 0 -> 4096 bytes test_outputs/test_server.db-shm | Bin 0 -> 32768 bytes test_outputs/test_server.db-wal | Bin 0 -> 337872 bytes 6 files changed, 27 insertions(+), 22 deletions(-) create mode 100644 test_outputs/test_server.db create mode 100644 test_outputs/test_server.db-shm create mode 100644 test_outputs/test_server.db-wal diff --git a/core_engine/src/lib.rs b/core_engine/src/lib.rs index 1b48807..4b6e501 100644 --- a/core_engine/src/lib.rs +++ b/core_engine/src/lib.rs @@ -1184,9 +1184,11 @@ pub fn basic_tier_summary_for_task(findings: &[Finding], task: Option<&str>) -> // Sort findings: highest severity first, then by confidence descending (#155). let mut sorted: Vec<&Finding> = findings.iter().collect(); sorted.sort_by(|a, b| { - b.severity - .cmp(&a.severity) - .then_with(|| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal)) + b.severity.cmp(&a.severity).then_with(|| { + b.confidence + .partial_cmp(&a.confidence) + .unwrap_or(std::cmp::Ordering::Equal) + }) }); let max_sev = sorted @@ -1275,7 +1277,10 @@ pub fn basic_tier_summary_for_task(findings: &[Finding], task: Option<&str>) -> } // -- Risk assessment -- - out.push_str(&format!("OVERALL RISK: {}\n\n", max_sev.token().to_uppercase())); + out.push_str(&format!( + "OVERALL RISK: {}\n\n", + max_sev.token().to_uppercase() + )); // -- Prioritized actions (deduplicated, urgent first) -- out.push_str("RECOMMENDED ACTIONS (priority order):\n"); diff --git a/cyber_tools/src/lib.rs b/cyber_tools/src/lib.rs index bcd6df3..ea71474 100644 --- a/cyber_tools/src/lib.rs +++ b/cyber_tools/src/lib.rs @@ -81,11 +81,12 @@ impl SandboxPolicy { } #[cfg(target_os = "windows")] - let command_allowlist: HashSet = - ["whoami", "netstat", "net", "tasklist", "reg", "sc", "wmic", "schtasks"] - .into_iter() - .map(|c| c.to_string()) - .collect(); + let command_allowlist: HashSet = [ + "whoami", "netstat", "net", "tasklist", "reg", "sc", "wmic", "schtasks", + ] + .into_iter() + .map(|c| c.to_string()) + .collect(); #[cfg(not(target_os = "windows"))] let command_allowlist: HashSet = ["id", "ss", "sudo"] diff --git a/inference_bridge/src/onnx_vitis.rs b/inference_bridge/src/onnx_vitis.rs index 9ac881e..47e0e50 100644 --- a/inference_bridge/src/onnx_vitis.rs +++ b/inference_bridge/src/onnx_vitis.rs @@ -2786,19 +2786,18 @@ fn run_prompt_on_session( // Same fix as run_prompt: account for forced cache padding when // cache tensors will be included during prefill (#136). - initial_cache_len = - if cache.layout.use_cache.is_none() && !cache_state.is_empty() { - cache_state - .values() - .next() - .and_then(|v| { - let spec = cache.layout.cache_specs.first()?; - v.shape().get(spec.past_axis).copied().map(|d| d as usize) - }) - .unwrap_or(0) - } else { - 0 - }; + initial_cache_len = if cache.layout.use_cache.is_none() && !cache_state.is_empty() { + cache_state + .values() + .next() + .and_then(|v| { + let spec = cache.layout.cache_specs.first()?; + v.shape().get(spec.past_axis).copied().map(|d| d as usize) + }) + .unwrap_or(0) + } else { + 0 + }; let attention_len = context_ids.len() + initial_cache_len; debug!( diff --git a/test_outputs/test_server.db b/test_outputs/test_server.db new file mode 100644 index 0000000000000000000000000000000000000000..4e86411b5803e34b1e4767ce981907694f15c1ed GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYBBVUzd(fE{gwozio9p!oD{ZBWnMf?A=OOh53P1GFqo>ZjRP1(flx)TWyY? zN!z+D>W4it(YoE{a8JBvcs4v2{~r3G7p^<3I<2~_vR3iC!+RT9H}a00?bU77W0kim zSOxD!_OHAr&KvSouI-Y600IagfB*srAbmqy8e37=cJ&3<(6>3XHo=PH+UCK!AW-fj4fG Y6C8oJ5Fp@IV8v~6f+O$_f(8Zt0-^IZi2wiq literal 0 HcmV?d00001 diff --git a/test_outputs/test_server.db-wal b/test_outputs/test_server.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..d8e7e8b47db46b9e4f4b94bd1576c19f85b4263b GIT binary patch literal 337872 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 literal 0 HcmV?d00001 From 92b3f6c19e6ab322f187bca7bd5a1cdfa6d138a8 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 16:07:08 -0400 Subject: [PATCH 03/10] chore: remove test DB artifacts and add to .gitignore --- .gitignore | 3 +++ test_outputs/test_server.db | Bin 4096 -> 0 bytes test_outputs/test_server.db-shm | Bin 32768 -> 0 bytes test_outputs/test_server.db-wal | Bin 337872 -> 0 bytes 4 files changed, 3 insertions(+) delete mode 100644 test_outputs/test_server.db delete mode 100644 test_outputs/test_server.db-shm delete mode 100644 test_outputs/test_server.db-wal diff --git a/.gitignore b/.gitignore index 311c0e0..42ae881 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ Thumbs.db /launch-assets/generated/ /launch-assets/reports/ /launch-assets/*.json +/test_outputs/*.db +/test_outputs/*.db-shm +/test_outputs/*.db-wal # Local GitHub Actions runner /actions-runner/ diff --git a/test_outputs/test_server.db b/test_outputs/test_server.db deleted file mode 100644 index 4e86411b5803e34b1e4767ce981907694f15c1ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYBBVUzd(fE{gwozio9p!oD{ZBWnMf?A=OOh53P1GFqo>ZjRP1(flx)TWyY? zN!z+D>W4it(YoE{a8JBvcs4v2{~r3G7p^<3I<2~_vR3iC!+RT9H}a00?bU77W0kim zSOxD!_OHAr&KvSouI-Y600IagfB*srAbmqy8e37=cJ&3<(6>3XHo=PH+UCK!AW-fj4fG Y6C8oJ5Fp@IV8v~6f+O$_f(8Zt0-^IZi2wiq diff --git a/test_outputs/test_server.db-wal b/test_outputs/test_server.db-wal deleted file mode 100644 index d8e7e8b47db46b9e4f4b94bd1576c19f85b4263b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 337872 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 From 67182f4ec5c7cd506b20ecf94b1ae36eb0629aa1 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 16:08:24 -0400 Subject: [PATCH 04/10] ci: add cargo cache and bump timeout for stdin-integration jobs The stdin-integration job compiles the full CLI binary from scratch without any cargo caching, causing it to routinely exceed the 20-minute timeout on GitHub Actions runners. Add Swatinem/rust-cache@v2 (matching the cross-platform job) and bump timeout to 30 minutes. --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aca79d8..514b276 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: stdin-integration: name: CLI stdin integration (${{ matrix.os }}) runs-on: ${{ matrix.os }} - timeout-minutes: 20 + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -70,6 +70,9 @@ jobs: rustup default 1.92.0 rustup show active-toolchain + - name: Cache cargo artifacts + uses: Swatinem/rust-cache@v2 + - name: Run stdin integration tests run: cargo test -p wraithrun --test stdin_integration From 6da05b6781a2564c39e3ce4ae7fd757265b54c34 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 16:57:50 -0400 Subject: [PATCH 05/10] fix: prevent ONNX session hang with pre-flight DLL and model preamble checks When the onnx feature is enabled (now the default), build_session() could hang indefinitely in two scenarios: 1. onnxruntime DLL not found: Session::builder() blocks on dynamic loader 2. Corrupt/stub model file: commit_from_file() never returns Added ensure_ort_dylib_available() to bail early when the runtime library cannot be located on PATH or via ORT_DYLIB_PATH, and validate_model_preamble() to reject files that don't start with a valid protobuf field tag before reaching the ONNX Runtime. Also fixes three clippy warnings newly exposed by the onnx default feature: - dead_code on push_warn (cfg gate narrowed) - too_many_arguments on run_prompt_shared_buffer (allow attribute) - unnecessary_to_owned on suffix.to_vec() (removed) --- inference_bridge/src/onnx_vitis.rs | 97 +++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/inference_bridge/src/onnx_vitis.rs b/inference_bridge/src/onnx_vitis.rs index 47e0e50..796c3e4 100644 --- a/inference_bridge/src/onnx_vitis.rs +++ b/inference_bridge/src/onnx_vitis.rs @@ -14,6 +14,7 @@ use std::{ collections::{HashMap, HashSet}, ffi::CString, fs, + io::Read, path::PathBuf, time::Instant, }; @@ -75,7 +76,7 @@ impl RuntimeCompatibilityReport { .any(|issue| issue.severity == RuntimeCompatibilitySeverity::Fail) } - #[cfg(any(not(feature = "onnx"), test))] + #[cfg(not(feature = "onnx"))] fn push_warn(&mut self, reason_code: &'static str, detail: impl Into) { self.issues.push(RuntimeCompatibilityIssue { severity: RuntimeCompatibilitySeverity::Warn, @@ -255,7 +256,12 @@ fn classify_session_init_reason_code(error_text: &str) -> &'static str { return "runtime_vitis_provider_missing"; } - if normalized.contains("onnxruntime.dll") && normalized.contains("not found") { + if (normalized.contains("onnxruntime.dll") + || normalized.contains("libonnxruntime.so") + || normalized.contains("libonnxruntime.dylib") + || normalized.contains("onnx runtime library")) + && normalized.contains("not found") + { return "runtime_ort_dylib_missing"; } @@ -1370,9 +1376,86 @@ fn build_session_with_vitis_cascade(config: &ModelConfig) -> Result { } } +#[cfg(feature = "onnx")] +fn ensure_ort_dylib_available() -> Result<()> { + if let Some(path) = std::env::var_os("ORT_DYLIB_PATH") { + let p = PathBuf::from(&path); + if p.is_file() { + return Ok(()); + } + bail!( + "ORT_DYLIB_PATH is set to '{}' but the file does not exist; \ + install ONNX Runtime or set ORT_DYLIB_PATH / WRAITHRUN_ORT_DYLIB_PATH \ + to a valid onnxruntime library path", + p.display() + ); + } + + // No explicit path — probe system search locations. + let lib_names: &[&str] = if cfg!(windows) { + &["onnxruntime.dll"] + } else if cfg!(target_os = "macos") { + &["libonnxruntime.dylib"] + } else { + &["libonnxruntime.so"] + }; + + let search_dirs: Vec = std::env::var_os("PATH") + .map(|val| std::env::split_paths(&val).collect()) + .unwrap_or_default(); + + for dir in &search_dirs { + for name in lib_names { + if dir.join(name).is_file() { + return Ok(()); + } + } + } + + bail!( + "ONNX Runtime library ({}) not found on PATH or via ORT_DYLIB_PATH; \ + install ONNX Runtime or set ORT_DYLIB_PATH / WRAITHRUN_ORT_DYLIB_PATH", + lib_names.join(" / ") + ); +} + +/// Fast pre-validation: confirms the model file starts with a plausible +/// protobuf field tag (ONNX models are serialized `ModelProto` messages). +/// This prevents feeding garbage bytes to `commit_from_file`, which can +/// block indefinitely in the ONNX Runtime graph optimiser. +#[cfg(feature = "onnx")] +fn validate_model_preamble(path: &std::path::Path) -> Result<()> { + let mut file = fs::File::open(path)?; + let mut buf = [0u8; 4]; + let n = file.read(&mut buf)?; + if n < 2 { + bail!( + "invalid model file '{}': too small ({n} bytes)", + path.display() + ); + } + // Protobuf field tags: low 3 bits = wire type, remaining bits = field number. + // Valid wire types for ModelProto fields: 0 (Varint), 1 (64-bit), + // 2 (Length-delimited), 5 (32-bit). Wire types 3, 4 are deprecated; + // 6, 7 are invalid. + let wire_type = buf[0] & 0x07; + let field_number = buf[0] >> 3; + if field_number == 0 || wire_type == 3 || wire_type == 4 || wire_type > 5 { + bail!( + "invalid model file '{}': does not start with a valid ONNX protobuf header \ + (first byte 0x{:02X} is not a valid protobuf field tag)", + path.display(), + buf[0] + ); + } + Ok(()) +} + #[cfg(feature = "onnx")] fn build_session(config: &ModelConfig) -> Result { configure_ort_dylib_path(config); + ensure_ort_dylib_available()?; + validate_model_preamble(&config.model_path)?; // When the vitis feature is available, delegate to the Vitis EP cascade // unless the user explicitly forces CPU-only mode. @@ -2452,6 +2535,7 @@ pub fn inspect_runtime_compatibility( } #[cfg(feature = "onnx")] +#[allow(clippy::too_many_arguments)] fn run_prompt_shared_buffer( session: &mut Session, layout: &SessionLayout, @@ -2742,13 +2826,8 @@ fn run_prompt_on_session( if !suffix.is_empty() { let prefill_started = Instant::now(); let attention_len = context_ids.len(); - let model_inputs = build_model_inputs( - &cache.layout, - &suffix.to_vec(), - attention_len, - true, - &cache_state, - )?; + let model_inputs = + build_model_inputs(&cache.layout, suffix, attention_len, true, &cache_state)?; let mut outputs = ort_result(cache.session.run(model_inputs))?; debug!( suffix_tokens = suffix.len(), From 1e670a9fd7975c70e96684667b0f6919f2bb9d43 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 17:05:12 -0400 Subject: [PATCH 06/10] ci: fix cargo PATH propagation on self-hosted runner The live-success-e2e job used PowerShell's echo/>> operator to append the cargo bin directory to GITHUB_PATH. On PowerShell 5.1 the >> operator writes UTF-16 LE. GitHub Actions expects UTF-8 in the GITHUB_PATH file, so subsequent steps (rust-cache, cargo test) could not find cargo/rustc. Fix: use Add-Content with -Encoding UTF8 and write unconditionally so every run guarantees PATH propagation regardless of prior runner state. --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 514b276..eab5029 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -221,10 +221,8 @@ jobs: shell: powershell run: | $cargobin = "$env:USERPROFILE\.cargo\bin" - if (-not ($env:Path -split ';' | Where-Object { $_ -eq $cargobin })) { - $env:Path = "$cargobin;$env:Path" - echo "$cargobin" >> $env:GITHUB_PATH - } + $env:Path = "$cargobin;$env:Path" + Add-Content -Path $env:GITHUB_PATH -Value $cargobin -Encoding UTF8 if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) { Write-Host "rustup not found, installing..." Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe From cd28e8a244fa3cdbcc3a1cf8c218e36bc6dfbb35 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 17:07:16 -0400 Subject: [PATCH 07/10] ci: use BOM-free UTF-8 for GITHUB_PATH and add PATH fallback in test step PowerShell 5.1's Add-Content -Encoding UTF8 writes a BOM prefix that corrupts the GITHUB_PATH file. Switch to [IO.File]::AppendAllText which writes BOM-free UTF-8. Also add an explicit cargo bin PATH fallback in the test step itself so it works even if GITHUB_PATH propagation fails on the self-hosted runner. --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eab5029..4d1343c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -222,7 +222,7 @@ jobs: run: | $cargobin = "$env:USERPROFILE\.cargo\bin" $env:Path = "$cargobin;$env:Path" - Add-Content -Path $env:GITHUB_PATH -Value $cargobin -Encoding UTF8 + [IO.File]::AppendAllText($env:GITHUB_PATH, "$cargobin`n") if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) { Write-Host "rustup not found, installing..." Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe @@ -248,6 +248,10 @@ jobs: - name: Run live success e2e test (no fallback) shell: powershell run: | + $cargobin = "$env:USERPROFILE\.cargo\bin" + if (-not ($env:Path -split ';' | Where-Object { $_ -eq $cargobin })) { + $env:Path = "$cargobin;$env:Path" + } cargo test -p wraithrun --features inference_bridge/onnx --test stdin_integration live_mode_e2e_success_without_fallback_when_fixture_is_configured -- --exact --nocapture - name: Upload live success e2e artifacts From f0f5552d8b72c145612e92d5a65a306faa6a07a8 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 17:12:57 -0400 Subject: [PATCH 08/10] ci: use dtolnay/rust-toolchain for reliable PATH setup on self-hosted runner --- .github/workflows/ci.yml | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d1343c..3d565be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,22 +217,10 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Install Rust toolchain (powershell) - shell: powershell - run: | - $cargobin = "$env:USERPROFILE\.cargo\bin" - $env:Path = "$cargobin;$env:Path" - [IO.File]::AppendAllText($env:GITHUB_PATH, "$cargobin`n") - if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) { - Write-Host "rustup not found, installing..." - Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe - .\rustup-init.exe -y --default-toolchain 1.92.0 --profile minimal - Remove-Item .\rustup-init.exe - $env:Path = "$cargobin;$env:Path" - } - rustup toolchain install 1.92.0 --profile minimal - rustup default 1.92.0 - rustup show active-toolchain + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.92.0" - name: Cache cargo artifacts uses: Swatinem/rust-cache@v2 @@ -248,10 +236,6 @@ jobs: - name: Run live success e2e test (no fallback) shell: powershell run: | - $cargobin = "$env:USERPROFILE\.cargo\bin" - if (-not ($env:Path -split ';' | Where-Object { $_ -eq $cargobin })) { - $env:Path = "$cargobin;$env:Path" - } cargo test -p wraithrun --features inference_bridge/onnx --test stdin_integration live_mode_e2e_success_without_fallback_when_fixture_is_configured -- --exact --nocapture - name: Upload live success e2e artifacts From a30f385c356489f1941d1900940cbba90c35dfb8 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 17:16:26 -0400 Subject: [PATCH 09/10] ci: resolve cargo path via GITHUB_ENV fallback and unconditional PATH in test step --- .github/workflows/ci.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d565be..3ad48bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,12 +218,28 @@ jobs: uses: actions/checkout@v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.92.0" + shell: powershell + run: | + $cargobin = "$env:USERPROFILE\.cargo\bin" + $env:Path = "$cargobin;$env:Path" + if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) { + Write-Host "rustup not found, installing..." + Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe + .\rustup-init.exe -y --default-toolchain 1.92.0 --profile minimal + Remove-Item .\rustup-init.exe + } + rustup toolchain install 1.92.0 --profile minimal + rustup default 1.92.0 + # Resolve actual cargo binary directory and share with later steps + $resolved = Split-Path (Get-Command cargo).Source + Write-Host "Resolved cargo bin: $resolved" + $utf8 = New-Object System.Text.UTF8Encoding($false) + [IO.File]::AppendAllText($env:GITHUB_ENV, "CARGO_BIN=$resolved`n", $utf8) + [IO.File]::AppendAllText($env:GITHUB_PATH, "$resolved`n", $utf8) - name: Cache cargo artifacts uses: Swatinem/rust-cache@v2 + continue-on-error: true - name: Validate live e2e fixture configuration shell: powershell @@ -236,6 +252,9 @@ jobs: - name: Run live success e2e test (no fallback) shell: powershell run: | + $bin = if ($env:CARGO_BIN) { $env:CARGO_BIN } else { "$env:USERPROFILE\.cargo\bin" } + $env:Path = "$bin;$env:Path" + Write-Host "cargo at: $(Get-Command cargo -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source)" cargo test -p wraithrun --features inference_bridge/onnx --test stdin_integration live_mode_e2e_success_without_fallback_when_fixture_is_configured -- --exact --nocapture - name: Upload live success e2e artifacts From af82ca81c6887a29675d961c1a5652101c6876c0 Mon Sep 17 00:00:00 2001 From: Shreyas Sankpal Date: Tue, 7 Apr 2026 17:20:12 -0400 Subject: [PATCH 10/10] ci: use 'rustup which cargo' to find toolchain bin when proxy is absent --- .github/workflows/ci.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ad48bc..0c8d9e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -230,12 +230,14 @@ jobs: } rustup toolchain install 1.92.0 --profile minimal rustup default 1.92.0 - # Resolve actual cargo binary directory and share with later steps - $resolved = Split-Path (Get-Command cargo).Source - Write-Host "Resolved cargo bin: $resolved" + # Resolve the real toolchain bin directory (cargo proxy may be absent) + $realBin = Split-Path (& rustup which cargo) + Write-Host "Toolchain bin: $realBin" + $env:Path = "$realBin;$env:Path" + cargo --version $utf8 = New-Object System.Text.UTF8Encoding($false) - [IO.File]::AppendAllText($env:GITHUB_ENV, "CARGO_BIN=$resolved`n", $utf8) - [IO.File]::AppendAllText($env:GITHUB_PATH, "$resolved`n", $utf8) + [IO.File]::AppendAllText($env:GITHUB_ENV, "CARGO_BIN=$realBin`n", $utf8) + [IO.File]::AppendAllText($env:GITHUB_PATH, "$realBin`n", $utf8) - name: Cache cargo artifacts uses: Swatinem/rust-cache@v2