Skip to content

Commit ecc96ff

Browse files
committed
feat(bump): working on next release
1 parent 0c181f5 commit ecc96ff

18 files changed

Lines changed: 379 additions & 56 deletions

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,18 @@ pip install semgrep
3636
# 3. Set your LLM API key
3737
export MISTRAL_API_KEY="your-key-here"
3838

39+
# 4. Configure and scan
3940
# 4. Configure and scan
4041
cp config.example.toml my-config.toml
4142
# Edit my-config.toml: set [project] path to your target code
4243
./target/release/baco scan --config my-config.toml
4344

44-
# 5. View the report
45-
open baco-output/report.html
46-
```
45+
# First scan sequence:
46+
# 1. Set your LLM API key (via env or config file)
47+
# 2. Run the scan: ./target/release/baco scan --config my-config.toml
48+
- **24 phases run**: 4 parallel (Indexing, Semgrep, CpgSlice, LlmStaticAnalysis) + 20 sequential — see [Architecture](docs/architecture.md)
49+
- **Output in `baco-output/`**: `findings.json`, `report.html`, `report.sarif`
50+
- **Resume interrupted scans**: the scanner writes a `checkpoint.json` to the output directory after each phase. Use `./target/release/baco resume --checkpoint baco-output/checkpoint.json` to restart from the last successful phase.
4751

4852
### What happens next
4953

config.example.toml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,73 @@ project = "libxml2"
196196
# trusted_paths = ["."] # Filesystem paths the agent is allowed to access
197197
# keep_artifacts = false # Keep agent work artifacts (logs, temp files)
198198

199+
200+
# CPG-guided slicing (P3.1)
201+
[cpg]
202+
# Whether CPG slicing is enabled
203+
# enabled = false
204+
# Path to Joern binary (None = search PATH)
205+
# joern_path = ""
206+
# Maximum lines to include in a slice
207+
# slice_budget_lines = 200
208+
209+
# Exploit synthesis (T3.2)
210+
[exploit]
211+
# Whether exploit synthesis is enabled
212+
# enabled = false
213+
# Docker image for sandboxed exploit execution
214+
# sandbox_image = "python:3.11-slim"
215+
# Timeout for exploit execution in seconds
216+
# timeout_secs = 30
217+
# Maximum number of exploit attempts per finding
218+
# max_exploits_per_finding = 1
219+
220+
# Soundness validation (CORRECT paper)
221+
[validate]
222+
# Whether the Validate phase is enabled
223+
# enabled = false
224+
225+
# Triple-path context augmentation (P1: VulTriage)
226+
[vultriage]
227+
# Whether triple-path context augmentation is enabled
228+
# enabled = false
229+
# Whether to include the control path (AST/CFG/DFG verbalisation)
230+
# control_path = true
231+
# Whether to include the knowledge path (CWE pattern RAG)
232+
# knowledge_path = true
233+
# Whether to include the semantic path (function summary)
234+
# semantic_path = true
235+
236+
# Policy-based generation (P2.2: VulnLLM-R)
237+
[policy_sampling]
238+
# Whether policy-based generation is enabled
239+
# enabled = false
240+
# Number of sampling rounds to build the policy
241+
# samples = 4
242+
243+
# Agent scaffold (P2.5: VulnLLM-R)
244+
[agent_scaffold]
245+
# Whether the agent scaffold is enabled
246+
# enabled = false
247+
# Maximum interaction rounds per target function
248+
# max_rounds = 5
249+
# Number of call-graph paths to sample per target function
250+
# paths_per_target = 3
251+
252+
# Primitive-API abstraction (P4: PacVD)
253+
[pacvd]
254+
# Whether PacVD abstraction is enabled
255+
# enabled = false
256+
# Abstraction level 1-4 (1=fuzzy, 4=concrete)
257+
# level = 2
258+
# Whether to auto-select the level based on the configured LLM model
259+
# auto_level = false
260+
261+
# Multi-agent harness synthesis (P5: AgentFlow)
262+
[agent_flow]
263+
# Whether AgentFlow is enabled
264+
# enabled = false
265+
# Maximum search-loop iterations
266+
# max_iterations = 10
267+
# Whether the target must be built with coverage/sanitizer instrumentation
268+
# requires_instrumented_target = false

src/indexer.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ impl FileIndex {
9090
languages: &[String],
9191
max_size: u64,
9292
excludes: &[String],
93+
pb: Option<&indicatif::ProgressBar>,
9394
) -> Result<(Self, FileHashStore), std::io::Error> {
9495
if !std::path::Path::new(project_path).exists() {
9596
tracing::error!("\u{1B}[31m[INDEXING]\u{1B}[0m ERROR: Path does not exist!");
@@ -143,14 +144,23 @@ impl FileIndex {
143144
total_size / (1024 * 1024)
144145
);
145146

147+
if let Some(pb) = pb {
148+
pb.set_length(all_files.len() as u64);
149+
pb.set_position(0);
150+
pb.set_message("Indexing files...");
151+
}
152+
146153
let mut hasher = crate::file_hash::FileHasher::new();
147154
let mut hash_store = FileHashStore::new();
148155

149-
for file_info in &mut all_files {
156+
for (i, file_info) in all_files.iter_mut().enumerate() {
150157
if let Ok(hash) = hasher.hash_file(&file_info.path) {
151158
file_info.hash = Some(hash.clone());
152159
hash_store.insert_hash(&file_info.path, hash);
153160
}
161+
if let Some(pb) = pb {
162+
pb.set_position((i + 1) as u64);
163+
}
154164
}
155165

156166
hash_store.set_last_scan(chrono::Utc::now().timestamp());

src/llm.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,6 @@ fn get_error_details(e: &reqwest::Error) -> (String, String, &'static str) {
770770
(status, url_e, kind)
771771
}
772772

773-
/// Helper to create LLM client with metrics from phase config
774773
pub fn create_llm_client_with_metrics(
775774
scanner: &crate::scanner::Scanner,
776775
phase_name: &str,
@@ -781,7 +780,15 @@ pub fn create_llm_client_with_metrics(
781780
_ => return None,
782781
};
783782

784-
let api_key = phase_config.api_key.as_ref()?;
783+
let api_key = phase_config.api_key.as_ref();
784+
if api_key.is_none() {
785+
eprintln!(
786+
"\u{1B}[33m[SCANNER] {} skipped: LLM not configured (set LLM_API_KEY or llm.api_key)\u{1B}[0m",
787+
phase_name
788+
);
789+
}
790+
791+
let api_key = api_key?;
785792

786793
let llm_config = LlmConfig {
787794
base_url: phase_config.base_url.clone(),

src/phase/indexing.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ impl ScanPhase for IndexingPhase {
6161
&ctx.scanner.config.project.languages,
6262
ctx.scanner.config.scanner.max_file_size_kb * 1024,
6363
&ctx.scanner.config.scanner.exclude_paths,
64+
Some(ctx.pb),
6465
) {
6566
Ok(result) => result,
6667
Err(e) => {

src/scanner/orchestrator.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -480,10 +480,7 @@ async fn run_sequential_phases(
480480
pub(super) async fn run_scanner(
481481
scanner: &super::Scanner,
482482
) -> Result<Vec<VulnerabilityFinding>, String> {
483-
let (mut findings, completed_phases, mut analyzed_files) = if scanner.force {
484-
tracing::info!("Force flag set - starting fresh, ignoring checkpoint");
485-
(Vec::new(), Vec::new(), Vec::new())
486-
} else if scanner.checkpoint_path.exists() {
483+
let (mut findings, completed_phases, mut analyzed_files) = if !scanner.force && scanner.checkpoint_path.exists() {
487484
use crate::checkpoint::Checkpoint;
488485
match Checkpoint::load(&scanner.checkpoint_path.to_string_lossy()) {
489486
Ok(cp) => {
@@ -495,8 +492,18 @@ pub(super) async fn run_scanner(
495492
);
496493
return Ok(cp.findings_so_far);
497494
}
495+
496+
let resume_phase =
497+
Checkpoint::resume_from(&scanner.checkpoint_path.to_string_lossy())
498+
.unwrap_or(ScanPhase::Indexing);
499+
let phase_idx = scanner.phase_graph.phase_index(&resume_phase);
500+
let total = scanner.phase_graph.total_phases();
501+
498502
eprintln!(
499-
"\u{1B}[33m[SCANNER] Resuming from checkpoint: {} phases already completed, {} findings loaded.\n Use --force to start a fresh scan.\u{1B}[0m",
503+
"\u{1B}[33m[SCANNER] Resuming scan from phase {:?} ({}/{}) - {} phases already completed, {} findings loaded.\n Use --force to start a fresh scan.\u{1B}[0m",
504+
resume_phase,
505+
phase_idx,
506+
total,
500507
cp.completed_phases.len(),
501508
cp.findings_so_far.len()
502509
);

src/scanner/phases/llm_phases/agent_verification.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::findings::VulnerabilityFinding;
66
use crate::scanner::phases::PhaseConfig;
77
use std::sync::Arc;
88

9-
/// Run Security Agent verification phase (Phase 6/20)
9+
/// Run Security Agent verification phase (Phase 10/24)
1010
pub async fn run_security_agent_verification(
1111
scanner: &crate::scanner::Scanner,
1212
cfg: PhaseConfig<'_>,
@@ -28,19 +28,19 @@ pub async fn run_security_agent_verification(
2828

2929
if !config.agent.enabled {
3030
tracing::debug!("Agent mode disabled, skipping Security Agent verification");
31-
pb.set_message("Phase 6/20: Agent mode disabled - skipping");
31+
pb.set_message("Phase 10/24: Agent mode disabled - skipping");
3232
pb.set_position(base + 100);
3333
return Ok((findings, analyzed_files.to_vec()));
3434
}
3535

3636
let Some(_api_key) = &config.llm.phases.discovery.api_key else {
3737
tracing::debug!("No API key for agent, skipping Security Agent verification");
38-
pb.set_message("Phase 6/20: No API key - skipping");
38+
pb.set_message("Phase 10/24: No API key - skipping");
3939
pb.set_position(base + 100);
4040
return Ok((findings, analyzed_files.to_vec()));
4141
};
4242

43-
pb.set_message("Phase 6/20: Security Agent verification (tool-based analysis)...");
43+
pb.set_message("Phase 10/24: Security Agent verification (tool-based analysis)...");
4444

4545
let total_findings = findings.len();
4646

@@ -134,7 +134,7 @@ pub async fn run_security_agent_verification(
134134
};
135135
pb.set_position(base + progress_pct);
136136
pb.set_message(format!(
137-
"Phase 6/20: Security Agent verifying [{}/{}] - {}",
137+
"Phase 10/24: Security Agent verifying [{}/{}] - {}",
138138
i + 1,
139139
total_findings,
140140
finding.title
@@ -276,7 +276,7 @@ pub async fn run_security_agent_verification(
276276
};
277277

278278
if enter_agent_flow {
279-
pb.set_message("Phase 6/20: AgentFlow harness synthesis...");
279+
pb.set_message("Phase 10/24: AgentFlow harness synthesis...");
280280

281281
for finding in findings.iter_mut() {
282282
// Build a minimal harness from the finding

src/scanner/phases/llm_phases/discovery.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::llm;
66
use crate::scanner::phases::PhaseConfig;
77
use std::sync::Arc;
88

9-
/// Run LLM discovery phase (Phase 4/20)
9+
/// Run LLM discovery phase (Phase 7/24)
1010
pub async fn run_llm_discovery(
1111
scanner: &crate::scanner::Scanner,
1212
cfg: PhaseConfig<'_>,
@@ -25,11 +25,11 @@ pub async fn run_llm_discovery(
2525
tracing::info!("Running LLM discovery phase...");
2626
let base = pb.position();
2727
pb.set_message(
28-
"Phase 4/20: LLM discovery (enriching vulnerability descriptions with AI context)...",
28+
"Phase 7/24: LLM discovery (enriching vulnerability descriptions with AI context)...",
2929
);
3030

3131
// Step 1: Detect project stack and fetch CVEs for threat intelligence
32-
pb.set_message("Phase 4/20: Detecting project stack and fetching CVE data...");
32+
pb.set_message("Phase 7/24: Detecting project stack and fetching CVE data...");
3333
let target_path_str = target_path.to_string_lossy().to_string();
3434
let bootstrapper = CveBootstrapper::new(target_path_str.clone());
3535

@@ -133,7 +133,7 @@ pub async fn run_llm_discovery(
133133
};
134134
pb.set_position(base + progress_pct);
135135
pb.set_message(format!(
136-
"Phase 4/20: Enriching findings [{}/{}] - {}",
136+
"Phase 7/24: Enriching findings [{}/{}] - {}",
137137
i + 1,
138138
total_findings,
139139
finding.title
@@ -213,12 +213,12 @@ Respond with ONLY JSON:
213213
}
214214
pb.set_position(base + 100);
215215
pb.set_message(format!(
216-
"Phase 4/20: Discovery complete - enriched {} findings",
216+
"Phase 7/24: Discovery complete - enriched {} findings",
217217
total_findings
218218
));
219219
} else {
220220
tracing::debug!("No API key for discovery, skipping LLM enrichment");
221-
pb.set_message("Phase 4/20: No API key configured - skipping discovery");
221+
pb.set_message("Phase 7/24: No API key configured - skipping discovery");
222222
pb.set_position(base + 100);
223223
}
224224
Ok((findings, analyzed_files.to_vec()))

src/scanner/phases/llm_phases/static_analysis.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::helpers::detect_language;
1+
/// Run LLM static analysis phase (Phase 4/24)
22
use crate::context::callee_walker::extract_call_sites;
33
use crate::context::pacvd_extractor::{self, AbstractionLevel};
44
use crate::context::semantic_path;
@@ -97,7 +97,7 @@ pub async fn run_llm_static_analysis(
9797

9898
// Capture base position for intra-phase progress
9999
let base = pb.position();
100-
pb.set_message("Phase 3/20: LLM static analysis (analyzing files for vulnerabilities)...");
100+
pb.set_message("Phase 4/24: LLM static analysis (analyzing files for vulnerabilities)...");
101101

102102
let index = crate::indexer::FileIndex::index_project(
103103
target_path.to_str().unwrap_or("."),
@@ -165,15 +165,15 @@ pub async fn run_llm_static_analysis(
165165
let progress_pct = ((i as f64 / file_count as f64) * 100.0) as u64;
166166
pb.set_position(base + progress_pct);
167167
pb.set_message(format!(
168-
"Phase 3/20: Skipping already analyzed [{}]: {}",
168+
"Phase 4/24: Skipping already analyzed [{}]: {}",
169169
i + 1,
170170
file_info.path.display()
171171
));
172172
continue;
173173
}
174174
let progress_pct = ((i as f64 / file_count as f64) * 100.0) as u64;
175175
let msg = format!(
176-
"Phase 3/20: LLM analyzing [{}/{}] ({:.0}%): {}",
176+
"Phase 4/24: LLM analyzing [{}/{}] ({:.0}%): {}",
177177
i + 1,
178178
file_count,
179179
progress_pct,
@@ -300,7 +300,7 @@ pub async fn run_llm_static_analysis(
300300
llm_findings.extend(file_findings);
301301
new_analyzed_files.push(file_path_str);
302302
let msg = format!(
303-
"Phase 3/20: LLM analyzing [{}/{}] ({:.0}%): {} - {} findings total",
303+
"Phase 4/24: LLM analyzing [{}/{}] ({:.0}%): {} - {} findings total",
304304
i + 1,
305305
file_count,
306306
progress_pct,
@@ -318,7 +318,7 @@ pub async fn run_llm_static_analysis(
318318
let error_lines: Vec<&str> = e.lines().take(3).collect();
319319
let error_summary = error_lines.join(" | ");
320320
let msg = format!(
321-
"Phase 3/20: {} - {} - FAILED: {}",
321+
"Phase 4/24: {} - {} - FAILED: {}",
322322
file_info.path.display(),
323323
error_summary,
324324
if i + 1 < file_count {
@@ -340,7 +340,7 @@ pub async fn run_llm_static_analysis(
340340

341341
findings.extend(llm_findings.clone());
342342
pb.set_message(format!(
343-
"Phase 3/20: LLM static analysis complete - {} findings discovered",
343+
"Phase 4/24: LLM static analysis complete - {} findings discovered",
344344
llm_findings.len()
345345
));
346346
} else {

0 commit comments

Comments
 (0)