CAMEL-24222: Add camel_dependency_security_audit MCP tool#25051
Conversation
New MCP tool that audits a Camel project's dependencies against the Camel security advisory database. Cross-references declared POM dependencies with known CVEs at the project's Camel version. Features: per-artifact CVE matching, severity classification, reachability analysis (used-in-routes vs transitive), actionable upgrade recommendations, automatic POM sanitization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
davsclaus
left a comment
There was a problem hiding this comment.
Thanks for this new MCP tool — the overall structure follows existing patterns well (PomSanitizer, CatalogService injection, result records). A couple of correctness issues to address before merging.
This review does not replace specialized AI review tools (CodeRabbit, Sourcery) or static analyzers (SonarCloud).
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
| if (hasCritical) { | ||
| recs.add("URGENT: Critical vulnerabilities found. Upgrade Camel version immediately. " | ||
| + "Use camel_migration_compatibility to check upgrade path."); | ||
| } |
There was a problem hiding this comment.
mapSeverity() ignores the actual advisory severity from AdvisoryView.severity() (populated from SecurityAdvisoryModel.getSeverity() — values like "Low", "Medium", "High", "Critical") and instead guesses from the title text using keyword heuristics.
This will misclassify real advisory severities. For example, a "Medium" CVE about "header injection" would get bumped to "high" by the heuristic.
Suggestion: use adv.severity() directly, falling back to "unknown" when null:
| } | |
| private String mapSeverity(String severity) { | |
| if (severity == null || severity.isBlank()) { | |
| return "unknown"; | |
| } | |
| return severity.toLowerCase(); | |
| } |
Then update the call sites from mapSeverity(adv.summary()) to mapSeverity(adv.severity()).
| } | ||
| } | ||
|
|
||
| for (String compName : catalog.findComponentNames()) { |
There was a problem hiding this comment.
Two issues here:
adv.cve().toLowerCase().replace("-", "-")replaces hyphen with hyphen — it's a no-op.AdvisoryViewalready has aurl()field that provides the correct advisory URL.
| for (String compName : catalog.findComponentNames()) { | |
| adv.url())); |
| boolean reachable = usedSchemes.contains(compName); | ||
| boolean declaredDep = pom.dependencies().contains(artifactId); | ||
| List<VulnerabilityFinding> findings = new ArrayList<>(); | ||
| for (AdvisoryService.AdvisoryView adv : matched) { |
There was a problem hiding this comment.
Same as above — use adv.url() instead of constructing the URL.
| for (AdvisoryService.AdvisoryView adv : matched) { | |
| adv.url())); |
| DependencySecurityAuditTools.AuditResult result | ||
| = tools.camel_dependency_security_audit(CURRENT_VERSION_POM, null, null, null, null, null); | ||
|
|
||
| assertThat(result).isNotNull(); |
There was a problem hiding this comment.
This test name suggests it should verify a clean result for the current version, but it doesn't assert summary().clean(). Consider adding:
assertThat(result.summary().clean()).isTrue();or at least assertThat(result.summary().totalCves()).isZero();.
| void shouldIncludeReachabilityWhenRoutesProvided() { | ||
| String routes = "from: \"http:example.com\"\nsteps:\n - to: \"log:out\""; | ||
| DependencySecurityAuditTools.AuditResult result | ||
| = tools.camel_dependency_security_audit(SIMPLE_POM, routes, null, null, null, null); |
There was a problem hiding this comment.
This test verifies reachability analysis when routes are provided, but doesn't assert anything about the reachability result. Consider checking reachableVulnerableArtifacts in the summary or verifying that a specific artifact's reachable flag is set correctly.
gnodet
left a comment
There was a problem hiding this comment.
The new security audit MCP tool combines dependency checking with advisory data — useful functionality. Found several issues, the most impactful being two bugs where existing AdvisoryView fields are ignored in favor of broken manual implementations.
High Severity
1. Severity derived from title keywords instead of adv.severity() (DependencySecurityAuditTools.java line 91)
mapSeverity(adv.summary()) heuristically guesses severity from title keywords, but AdvisoryView already has a severity() field populated from the advisory's actual published severity. This produces wrong results — e.g., CVE-2013-4330 has severity "CRITICAL" but its summary contains none of the critical keywords, so mapSeverity returns "medium". The criticalAndHighCves count and URGENT recommendation are based on this wrong severity. Fix: use adv.severity() directly and remove mapSeverity().
2. Broken URL construction when adv.url() is available (DependencySecurityAuditTools.java line 94)
Three bugs in one line: (1) .replace("-", "-") is a no-op (replaces dash with dash); (2) .toLowerCase() produces cve-2025-27636 but actual URLs use uppercase CVE-2025-27636; (3) missing .html suffix. AdvisoryView already has a url() field with the correct URL. Fix: use adv.url() directly.
Medium Severity
3. False-positive scheme detection on YAML keywords (DependencySecurityAuditTools.java line 135)
extractUsedSchemes() splits on [^a-z0-9-]+ and checks if token + ":" appears in text. For YAML routes, structural keywords like from, steps, to will match as component schemes. While practical impact is mitigated (these aren't registered Camel component names), the approach differs from DependencyCheckTools.containsComponent() and HardenTools.containsComponent() which iterate known component names instead.
4. Dead declaredDep code path (DependencySecurityAuditTools.java line 100)
The second loop's declaredDep variable is effectively dead code. If an artifact is a declared dependency, the first loop already processed it. The condition should simplify to just reachable && !declaredDep.
5. Package-private result records (DependencySecurityAuditTools.java line 226)
All four result records (AuditResult, ArtifactAudit, VulnerabilityFinding, AuditSummary) use package-private visibility. Every other MCP tool class uses public record for result types.
Low Severity
6. Test createTools() missing catalogRepos setup (DependencySecurityAuditToolsTest.java line 65)
Unlike DependencyCheckToolsTest, the test helper doesn't set catalogService.catalogRepos = Optional.empty(), which could cause NPE if a test uses a non-null camelVersion.
7. Shallow test assertions (DependencySecurityAuditToolsTest.java line 91)
Tests like shouldReportCleanForCurrentVersion and shouldIncludeReachabilityWhenRoutesProvided only assert result != null without verifying the behavior their names claim to test.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 1 tested, 0 compile-only — current: 1 all testedMaveniverse Scalpel detected 1 affected modules (current approach: 1). Skip-tests mode would test 1 modules (1 direct + 0 downstream), skip tests for 0 (generated code, meta-modules) Modules Scalpel would test (1)
|
- Use adv.severity() instead of mapSeverity() heuristic — removed mapSeverity() entirely - Use adv.url() instead of broken manual URL construction - Simplify second loop: remove dead declaredDep variable, only add reachable components - Make all result records public (convention across all tool classes) - Strengthen test assertions for version and dependency count Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
Thank you for the review — all findings addressed: High Severity (fixed):
Medium Severity (fixed): Low Severity (fixed): Claude Code on behalf of oscerd |
gnodet
left a comment
There was a problem hiding this comment.
Re-review: good progress — 5 of 7 previous findings addressed (heuristic severity removed, URL fixed, dead code removed, records made public, tests strengthened). However, the severity fix introduced a new case-sensitivity bug.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
| .flatMap(a -> a.findings().stream()) | ||
| .filter(f -> "critical".equals(f.severity()) || "high".equals(f.severity())) | ||
| .count(); |
There was a problem hiding this comment.
Case-sensitive severity comparison will silently produce wrong results. The advisory data stores severity values as uppercase ("CRITICAL", "HIGH", "MEDIUM", "LOW"), but this filter uses lowercase. Since String.equals() is case-sensitive, criticalAndHighCves will always be 0.
Note: AdvisoryService.matchesSeverity() already handles this correctly with equalsIgnoreCase() — same pattern needed here.
| .flatMap(a -> a.findings().stream()) | |
| .filter(f -> "critical".equals(f.severity()) || "high".equals(f.severity())) | |
| .count(); | |
| long criticalCount = vulnerableArtifacts.stream() | |
| .flatMap(a -> a.findings().stream()) | |
| .filter(f -> "critical".equalsIgnoreCase(f.severity()) || "high".equalsIgnoreCase(f.severity())) |
|
|
||
| boolean hasCritical = vulnerableArtifacts.stream() | ||
| .flatMap(a -> a.findings().stream()) | ||
| .anyMatch(f -> "critical".equals(f.severity())); |
There was a problem hiding this comment.
Same case-sensitivity issue here — "critical".equals(f.severity()) will never match the uppercase "CRITICAL" from the advisory data, so the "URGENT" recommendation will never trigger.
| .anyMatch(f -> "critical".equals(f.severity())); | |
| .anyMatch(f -> "critical".equalsIgnoreCase(f.severity())); |
| DependencySecurityAuditTools.AuditResult result | ||
| = tools.camel_dependency_security_audit(SIMPLE_POM, null, null, null, null, null); | ||
|
|
||
| assertThat(result).isNotNull(); |
There was a problem hiding this comment.
Minor: shouldAuditOlderVersionWithKnownCves doesn't assert that any CVEs were actually found (e.g., totalCves > 0). The test would pass even if the matching logic returned zero findings, weakening it as a regression guard.
- Fix case-sensitive severity comparison: use equalsIgnoreCase() for both criticalCount filter and URGENT recommendation trigger - Strengthen test assertion for older version audit Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
Re-review findings addressed:
Claude Code on behalf of oscerd |
gnodet
left a comment
There was a problem hiding this comment.
Re-review: all previous findings are now resolved. The case-sensitivity bug in severity comparisons is fixed (equalsIgnoreCase() on lines 138 and 200), mapSeverity() removed, adv.url() used directly, dead code cleaned up, records made public, and test assertions strengthened. The code is in good shape.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
davsclaus
left a comment
There was a problem hiding this comment.
Good work combining dependency checking with the advisory database — the reachability analysis is a nice touch. The code issues from prior rounds are all resolved; a few test and consistency observations below.
Confirmed Issues (still outstanding from prior review)
1. Test shouldReportCleanForCurrentVersion doesn't assert cleanness
The test name claims to verify a clean result for the current version, but only asserts camelVersion equals "4.22.0". davsclaus suggested adding assertThat(result.summary().clean()).isTrue() — this was not applied.
2. Test shouldIncludeReachabilityWhenRoutesProvided doesn't assert reachability
The test name claims to verify reachability analysis, but only asserts totalDependencies > 0. davsclaus suggested checking reachableVulnerableArtifacts in the summary — this was not applied.
New Findings
3. shouldAuditOlderVersionWithKnownCves assertion is effectively no-op
isGreaterThanOrEqualTo(0) passes even when zero CVEs are found. For Camel 4.10.0 with known CVEs (CVE-2025-27636, CVE-2025-29891, etc.), this should assert isGreaterThan(0) to guard the matching logic. gnodet also flagged this; it appears unresolved.
4. extractUsedSchemes diverges from established codebase pattern
Both DependencyCheckTools.containsComponent() and HardenTools.containsComponent() iterate known catalog component names and check content.contains(comp + ":"). This tool instead tokenizes route text and checks for token + ":", producing false positives for YAML structural keywords (from, to, steps, uri). Practical impact is low (these won't match catalog component names), but aligning with the established pattern would be cleaner.
Questions
5. Second catalog loop intent (lines 100-120)
After scanning POM dependencies, the code iterates all catalog components for vulnerabilities in components used in routes but NOT declared as POM dependencies. What scenario does this cover — transitively available components? A brief code comment would help future readers.
This review covers convention compliance and rules-based review. It does not replace specialized AI review tools (CodeRabbit, Sourcery) or static analyzers (SonarCloud).
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of davsclaus
| DependencySecurityAuditTools.AuditResult result | ||
| = tools.camel_dependency_security_audit(CURRENT_VERSION_POM, null, null, null, null, null); | ||
|
|
||
| assertThat(result).isNotNull(); |
There was a problem hiding this comment.
This test name says "should report clean" but doesn't assert clean(). davsclaus's earlier suggestion to add assertThat(result.summary().clean()).isTrue() was not applied.
| assertThat(result).isNotNull(); | |
| assertThat(result).isNotNull(); | |
| assertThat(result.summary()).isNotNull(); | |
| assertThat(result.summary().camelVersion()).isEqualTo("4.22.0"); | |
| assertThat(result.summary().clean()).isTrue(); |
| = tools.camel_dependency_security_audit(SIMPLE_POM, routes, null, null, null, null); | ||
|
|
||
| assertThat(result).isNotNull(); | ||
| assertThat(result.summary()).isNotNull(); |
There was a problem hiding this comment.
This test name says "should include reachability" but doesn't assert anything about reachability. Consider checking result.summary().reachableVulnerableArtifacts() or verifying a specific artifact's reachable flag.
| DependencySecurityAuditTools.AuditResult result | ||
| = tools.camel_dependency_security_audit(SIMPLE_POM, null, null, null, null, null); | ||
|
|
||
| assertThat(result).isNotNull(); |
There was a problem hiding this comment.
isGreaterThanOrEqualTo(0) is always true — this assertion can never fail. Camel 4.10.0 has known CVEs, so this should be:
| assertThat(result).isNotNull(); | |
| assertThat(result.summary().totalCves()).isGreaterThan(0); |
| (int) reachableCount, | ||
| totalCves == 0); | ||
|
|
||
| return new AuditResult( |
There was a problem hiding this comment.
This tokenizing approach produces false positives for YAML structural keywords (from:, to:, steps:, uri:, route:, id:). Both DependencyCheckTools and HardenTools use a different pattern — iterating known catalog component names and checking content.contains(comp + ":") — which avoids false positives entirely. Consider aligning with the established approach for consistency.
- shouldAuditOlderVersionWithKnownCves: assert totalCves > 0 - shouldReportCleanForCurrentVersion: assert summary.clean() is true - shouldIncludeReachabilityWhenRoutesProvided: assert reachableVulnerableArtifacts - Add comment explaining second catalog loop (transitive components) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
Thank you for the follow-up review. All findings addressed:
Claude Code on behalf of oscerd |
|
All davsclaus findings addressed: tests now assert clean(), reachableVulnerableArtifacts, and totalCves > 0. Added comment for second catalog loop. Claude Code on behalf of oscerd |
gnodet
left a comment
There was a problem hiding this comment.
Re-review (1 new commit since last approval)
The new commit (c2179f9) correctly addresses davsclaus's feedback: adds an explanatory code comment and strengthens 3 test assertions (totalCves > 0, clean() assertion, reachableVulnerableArtifacts assertion). No production logic changed. All previously identified issues from 3 review rounds remain resolved. CI passes on all 4 checks.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
Summary
Adds a new MCP tool
camel_dependency_security_auditthat performs security vulnerability analysis on a Camel project's dependencies by cross-referencing them with the Camel security advisory database.PomSanitizer)Distinct from
camel_dependency_check(dependency hygiene) andcamel_security_advisories(raw CVE listing) — this tool combines both into a project-specific vulnerability audit.Test plan
Claude Code on behalf of oscerd
🤖 Generated with Claude Code