fix(bridge): distinguish direct vs transitive phantom deps (closes #47)#50
Merged
Merged
Conversation
…action Closes #47. `bridge triage` was telling users to "Remove unused dependency `<pkg>` from Cargo.toml" for any phantom-classified CVE, but the 2026-05-26 estate sweep showed every sampled phantom (28/28 in a 6-repo sample, 157 phantoms total) was a *transitive* dep pulled in by some upstream crate — never declared in any Cargo.toml. The action string was unactionable. Fix: parse the project's Cargo.toml dependency tables to distinguish direct from transitive deps, then emit the right action for each. Changes: - `src/bridge/lockfile.rs`: new `collect_direct_cargo_dependencies()` walks the root Cargo.toml + each `workspace.members` manifest, indexing `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]`, `[workspace.dependencies]`, and target-prefixed variants (`[target.cfg(...).dependencies]`). Crate names normalised to hyphen+lowercase for CVE-feed matching (serde_json ↔ serde-json). - `src/bridge/classify.rs`: `classify()` takes a new `is_direct: bool`. - Phantom + direct → unchanged "Remove unused dependency" action. - Phantom + transitive → new action: "Transitive — run `cargo update -p <pkg>` ... Otherwise informational: code unreachable from this project." - Reachable arms unchanged. - `src/bridge/mod.rs`: `triage()` builds the direct-deps set once (outside the per-CVE loop) and passes the lookup result into classify. Regression coverage: - `direct_deps_skips_transitive_only_crates` — repro of the `lru` / ratatui case from the issue. - `direct_deps_collects_dev_and_build_sections`, `direct_deps_handles_target_sections`, `direct_deps_handles_workspace_members`, `direct_deps_normalises_underscore_to_hyphen`, `direct_deps_ignores_commented_lines_and_strings_with_hash`, `direct_deps_empty_when_no_manifest` — parser edge cases. - `test_phantom_transitive_recommends_cargo_update` — asserts no "Remove unused dependency" string for the transitive phantom case. - `test_phantom_direct_recommends_removal` — direct case unchanged. - `test_reachable_classification_unaffected_by_is_direct` — sanity check that the new flag only affects the Phantom arm. All 236 binary tests pass. cargo clippy clean. cargo fmt clean. Out of scope (per the issue): - "For unmitigable + reachable: actionable warning" — the current rationale already describes the import sites + lack of fix. Whether the action wording needs further sharpening is a separate concern from the wrong-action-on-phantom-transitive bug. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🔍 Hypatia Security ScanFindings: 49 issues detected
View findings[
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
"type": "ncl_missing_spdx",
"file": "/home/runner/work/panic-attack/panic-attack/reports/panic-attack-20260211180017.ncl",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
},
{
"reason": "expect() in hot path (2 occurrences, CWE-754)",
"type": "expect_in_hot_path",
"file": "/home/runner/work/panic-attack/panic-attack/src/attestation/chain.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
},
{
"reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/attestation/evidence.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/ambush/mod.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/kanren/strategy.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/axial/mod.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "expect() in hot path (4 occurrences, CWE-754)",
"type": "expect_in_hot_path",
"file": "/home/runner/work/panic-attack/panic-attack/src/assail/analyzer.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
},
{
"reason": "unwrap() without prior check -- DoS via panic (4 occurrences, CWE-754)",
"type": "unwrap_without_check",
"file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "high"
},
{
"reason": "expect() in hot path (2 occurrences, CWE-754)",
"type": "expect_in_hot_path",
"file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
🔍 Hypatia Security ScanFindings: 49 issues detected
View findings[
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
"type": "ncl_missing_spdx",
"file": "/home/runner/work/panic-attack/panic-attack/reports/panic-attack-20260211180017.ncl",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
},
{
"reason": "expect() in hot path (2 occurrences, CWE-754)",
"type": "expect_in_hot_path",
"file": "/home/runner/work/panic-attack/panic-attack/src/attestation/chain.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
},
{
"reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/attestation/evidence.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/ambush/mod.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/kanren/strategy.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
"type": "unwrap_dangerous_default",
"file": "/home/runner/work/panic-attack/panic-attack/src/axial/mod.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
},
{
"reason": "expect() in hot path (4 occurrences, CWE-754)",
"type": "expect_in_hot_path",
"file": "/home/runner/work/panic-attack/panic-attack/src/assail/analyzer.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
},
{
"reason": "unwrap() without prior check -- DoS via panic (4 occurrences, CWE-754)",
"type": "unwrap_without_check",
"file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "high"
},
{
"reason": "expect() in hot path (2 occurrences, CWE-754)",
"type": "expect_in_hot_path",
"file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #47.
bridge triage's phantom-classified findings were uniformly recommending "Remove unused dependency<pkg>from Cargo.toml", but the 2026-05-26 estate sweep showed every sampled phantom (28/28 in a 6-repo sample, 157 phantoms total) was a transitive dep pulled in by an upstream crate — never declared in anyCargo.toml. The action was unactionable.Fix
Parse the project's
Cargo.tomldependency tables once per triage run, then route phantom findings to one of two action strings based on whether the package appears as a direct dependency.<pkg>from Cargo.toml"<pkg>from Cargo.toml" ❌cargo update -p <pkg>to pull a non-vulnerable version if one is published, or upgrade the upstream crate that pulls it in. Otherwise informational: code unreachable from this project."Implementation
src/bridge/lockfile.rscollect_direct_cargo_dependencies()walks the rootCargo.toml+ eachworkspace.membersmanifest. Indexes[dependencies],[dev-dependencies],[build-dependencies],[workspace.dependencies], and target-prefixed variants ([target.cfg(...).dependencies]etc.). Crate names normalised to hyphen + lowercase so a CVE feed reportingserde_jsonmatches a manifest lineserde-json.src/bridge/classify.rsclassify()signature gainsis_direct: bool. Phantom arm splits on the flag; reachable arms unchanged.src/bridge/mod.rstriage()builds the direct-deps set once (outside the per-CVE loop) and passes the lookup result into classify.Regression coverage
New tests:
direct_deps_skips_transitive_only_crates— direct repro of thelru/ratatuicase from the issue.direct_deps_collects_dev_and_build_sections— all three direct sections.direct_deps_handles_target_sections—[target.cfg(unix).dependencies]etc.direct_deps_handles_workspace_members— root manifest declares members; member deps are reachable.direct_deps_normalises_underscore_to_hyphen—serde_json↔serde-json.direct_deps_ignores_commented_lines_and_strings_with_hash—# foo = \"1.0\"does not count.direct_deps_empty_when_no_manifest— graceful degradation (returns empty set, all phantoms treated as transitive).test_phantom_transitive_recommends_cargo_update— the bug behaviour from bridge triage: 'Remove unused dependency' action assumes direct dep, fires on transitive deps #47 is gone.test_phantom_direct_recommends_removal— direct case unchanged.test_reachable_classification_unaffected_by_is_direct— invariant.Test plan
cargo test --bin panic-attack --features signing,http bridge::— 28 passed (10 new + 18 existing)cargo clippy --all-targets --features signing,http -- -D warnings— cleancargo fmt --check— cleanOut of scope (per the issue)
The current unmitigable rationale already describes import sites + lack-of-fix. Whether the action wording for that category needs further sharpening is a separate concern from the wrong-action-on-phantom-transitive bug.
🤖 Generated with Claude Code