Known-answer oracle scoring for measuring discovery/verification quality.
The eval harness uses labeled vulnerable/secure aligned pairs:
- Oracle files contain the ground truth: expected findings (vulnerable) and expected suppressed (secure twins)
- Blind validation only: oracle data is NEVER loaded into LLM prompts
- Scoring: Findings from the scanner are compared against the oracle to compute:
- Recall: matched_expected / total_expected
- Precision: matched / (matched + false_flags)
- False flags: Findings on secure twin files (should be zero)
# Evaluate precision/recall/F1 vs ground truth
baco eval --target /path/to/fixtures --ground-truth eval/oracles/target.jsonThe eval command:
- Loads the oracle file from
--ground-truthpath - Scans the fixture files in
--targetpath (or uses existing findings via--findings) - Scores findings against expected/expected_suppressed
- Outputs precision, recall, and F1 score metrics
# Run every bundled target offline: no scanner, no LLM keys, no network
baco eval # no arguments = suite mode
baco eval --all # explicit
cargo run --bin baco -- eval # from a repository checkoutSuite mode iterates every eval/oracles/*.json target, scores its bundled
findings fixture (eval/findings/<target>.json) against its oracle, prints a
per-target pass-rate table plus the aggregate, and exits non-zero when the
aggregate does not exceed the floor.
[eval] floor in the config is the knob. Set a fraction in 0.0..=1.0
(default 0.70) in your baco.toml; the suite passes only when the aggregate
pass-rate (total matched / total expected across all targets) is strictly
greater than the floor. The BACO_EVAL_FLOOR environment variable overrides
the config value (unset or empty falls back to config).
[eval]
# Fail unless the suite exceeds a stricter floor
floor = 0.9# Or override per invocation via the environment
BACO_EVAL_FLOOR=0.9 baco eval# Set the environment variable to enable eval mode
export BACO_EVAL=1
# Provide your LLM key
export LLM_API_KEY=your-key-here
# Run baco with eval mode
cargo run -- --eval-target py-sqliThe scanner will:
- Load the oracle file from
eval/oracles/<target>.json - Scan the fixture files in
eval/fixtures/<target>/ - Score findings against expected/expected_suppressed
- Output a ScoreReport with recall/precision/F1 metrics
eval/fixtures/<target-name>/
├── vulnerable.<ext> # Contains the vulnerability at a known line
├── safe_twin.<ext> # Identical logic, secure implementation
└── innocent.<ext> # Additional non-vulnerable files (optional)
eval/oracles/<target-name>.json:
{
"target": "<target-name>",
"description": "Brief description of the vulnerability type",
"expected_findings": [
{
"file_path": "vulnerable.<ext>",
"line": <exact-line-number>,
"cwe_id": "CWE-XXX",
"class": "Vulnerability Class Name"
}
],
"expected_suppressed": [
{
"file_path": "safe_twin.<ext>",
"reason": "Why this is secure (e.g., 'Parameterized query twin')"
}
]
}In tests/integration/eval_oracle.rs:
#[test]
fn test_<target-name>_oracle_parse() {
// Verify oracle parses correctly
}
#[test]
fn test_<target-name>_fixtures_exist() {
// Verify fixture files exist
}
#[test]
fn test_<target-name>_scoring() {
// Unit test score_findings with synthetic findings
}
#[tokio::test]
#[ignore] // Requires BACO_EVAL=1 + LLM key
async fn test_<target-name>_e2e() {
// End-to-end eval with real scanner output
}| Target | Language | Vulnerability class | CWE |
|---|---|---|---|
| py-sqli | Python | SQL injection via f-string | CWE-89 |
| c-overflow | C | Buffer overflow via unbounded memcpy | CWE-120 |
| php-sqli | PHP | SQL injection via $_GET concatenation |
CWE-89 |
| php-xss | PHP | Reflected XSS via unescaped echo |
CWE-79 |
| js-eval | JavaScript | Code injection via eval() of user input |
CWE-95 |
| js-path-traversal | JavaScript | Path traversal via path.join of user input |
CWE-22 |
| py-weak-hash | Python | Weak hash (MD5) for password storage | CWE-327 |
| py-cmdi | Python | OS command injection via os.system |
CWE-78 |
| c-sprintf | C | Out-of-bounds write via unbounded sprintf |
CWE-787 |
| c-uaf | C | Use-after-free via dangling pointer read | CWE-416 |
- Vulnerable file: Single, clear vulnerability at a known line number
- Safe twin: Same structure, secure implementation (parameterized, bounded, etc.)
- Line numbers: Count carefully; use 1-indexed line numbers
- CWE IDs: Use official CWE identifiers (e.g., "CWE-89", "CWE-120")
- Innocent files: Add 1-2 files with no vulnerabilities to test false positive rate