fix: create CVE notifications during scan, fix help chat doc loading
All checks were successful
CI / Check (pull_request) Successful in 10m4s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
All checks were successful
CI / Check (pull_request) Successful in 10m4s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
Bug fixes:
1. CVE notifications now created during scan pipeline (not just hourly)
- Previously, notifications were only created by the scheduled
monitor_cves job. Users with 4 CVE alerts saw 0 notifications.
- Now the scan pipeline (Stage 3) creates notifications immediately
when CVE alerts are discovered, with the same dedup logic.
2. Help chat doc context loading fixed for Docker/production
- Added HELP_DOCS_PATH env var for explicit doc root configuration
- Added fallback chain: env var → binary location → cwd → Docker paths
- Dockerfile.agent now copies README.md and docs/ into /app and sets
HELP_DOCS_PATH=/app so the help chat has doc context in production
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,11 @@ RUN pip3 install --break-system-packages ruff
|
||||
|
||||
COPY --from=builder /app/target/release/compliance-agent /usr/local/bin/compliance-agent
|
||||
|
||||
# Copy documentation for the help chat assistant
|
||||
COPY --from=builder /app/README.md /app/README.md
|
||||
COPY --from=builder /app/docs /app/docs
|
||||
ENV HELP_DOCS_PATH=/app
|
||||
|
||||
# Ensure SSH key directory exists
|
||||
RUN mkdir -p /data/compliance-scanner/ssh
|
||||
|
||||
|
||||
@@ -104,28 +104,58 @@ fn load_docs(root: &Path) -> String {
|
||||
|
||||
/// Returns a reference to the cached doc context string, initialised on
|
||||
/// first call via `OnceLock`.
|
||||
///
|
||||
/// Discovery order:
|
||||
/// 1. `HELP_DOCS_PATH` env var (explicit override)
|
||||
/// 2. Walk up from the binary location
|
||||
/// 3. Current working directory
|
||||
/// 4. Common Docker paths (/app, /opt/compliance-scanner)
|
||||
fn doc_context() -> &'static str {
|
||||
DOC_CONTEXT.get_or_init(|| {
|
||||
// 1. Explicit env var
|
||||
if let Ok(path) = std::env::var("HELP_DOCS_PATH") {
|
||||
let p = PathBuf::from(&path);
|
||||
if p.join("README.md").is_file() || p.join("docs").is_dir() {
|
||||
tracing::info!("help_chat: loading docs from HELP_DOCS_PATH={path}");
|
||||
return load_docs(&p);
|
||||
}
|
||||
tracing::warn!("help_chat: HELP_DOCS_PATH={path} has no README.md or docs/");
|
||||
}
|
||||
|
||||
// 2. Walk up from binary location
|
||||
let start = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(Path::to_path_buf))
|
||||
.unwrap_or_else(|| PathBuf::from("."));
|
||||
|
||||
match find_project_root(&start) {
|
||||
Some(root) => load_docs(&root),
|
||||
None => {
|
||||
// Fallback: try current working directory
|
||||
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
if cwd.join("README.md").is_file() {
|
||||
return load_docs(&cwd);
|
||||
}
|
||||
tracing::error!(
|
||||
"help_chat: could not locate project root from {}; doc context will be empty",
|
||||
start.display()
|
||||
);
|
||||
String::new()
|
||||
if let Some(root) = find_project_root(&start) {
|
||||
return load_docs(&root);
|
||||
}
|
||||
|
||||
// 3. Current working directory
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
if let Some(root) = find_project_root(&cwd) {
|
||||
return load_docs(&root);
|
||||
}
|
||||
if cwd.join("README.md").is_file() {
|
||||
return load_docs(&cwd);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Common Docker/deployment paths
|
||||
for candidate in ["/app", "/opt/compliance-scanner", "/srv/compliance-scanner"] {
|
||||
let p = PathBuf::from(candidate);
|
||||
if p.join("README.md").is_file() || p.join("docs").is_dir() {
|
||||
tracing::info!("help_chat: found docs at {candidate}");
|
||||
return load_docs(&p);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::error!(
|
||||
"help_chat: could not locate project root; doc context will be empty. \
|
||||
Set HELP_DOCS_PATH to the directory containing README.md and docs/"
|
||||
);
|
||||
String::new()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -315,20 +315,67 @@ impl PipelineOrchestrator {
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Persist CVE alerts (upsert by cve_id + repo_id)
|
||||
for alert in &cve_alerts {
|
||||
let filter = doc! {
|
||||
"cve_id": &alert.cve_id,
|
||||
"repo_id": &alert.repo_id,
|
||||
};
|
||||
let update = mongodb::bson::to_document(alert)
|
||||
.map(|d| doc! { "$set": d })
|
||||
.unwrap_or_else(|_| doc! {});
|
||||
self.db
|
||||
.cve_alerts()
|
||||
.update_one(filter, update)
|
||||
.upsert(true)
|
||||
.await?;
|
||||
// Persist CVE alerts and create notifications
|
||||
{
|
||||
use compliance_core::models::notification::{parse_severity, CveNotification};
|
||||
|
||||
let repo_name = repo.name.clone();
|
||||
let mut new_notif_count = 0u32;
|
||||
|
||||
for alert in &cve_alerts {
|
||||
// Upsert the alert
|
||||
let filter = doc! {
|
||||
"cve_id": &alert.cve_id,
|
||||
"repo_id": &alert.repo_id,
|
||||
};
|
||||
let update = mongodb::bson::to_document(alert)
|
||||
.map(|d| doc! { "$set": d })
|
||||
.unwrap_or_else(|_| doc! {});
|
||||
self.db
|
||||
.cve_alerts()
|
||||
.update_one(filter, update)
|
||||
.upsert(true)
|
||||
.await?;
|
||||
|
||||
// Create notification (dedup by cve_id + repo + package + version)
|
||||
let notif_filter = doc! {
|
||||
"cve_id": &alert.cve_id,
|
||||
"repo_id": &alert.repo_id,
|
||||
"package_name": &alert.affected_package,
|
||||
"package_version": &alert.affected_version,
|
||||
};
|
||||
let severity = parse_severity(alert.severity.as_deref(), alert.cvss_score);
|
||||
let mut notification = CveNotification::new(
|
||||
alert.cve_id.clone(),
|
||||
repo_id.clone(),
|
||||
repo_name.clone(),
|
||||
alert.affected_package.clone(),
|
||||
alert.affected_version.clone(),
|
||||
severity,
|
||||
);
|
||||
notification.cvss_score = alert.cvss_score;
|
||||
notification.summary = alert.summary.clone();
|
||||
notification.url = Some(format!("https://osv.dev/vulnerability/{}", alert.cve_id));
|
||||
|
||||
let notif_update = doc! {
|
||||
"$setOnInsert": mongodb::bson::to_bson(¬ification).unwrap_or_default()
|
||||
};
|
||||
if let Ok(result) = self
|
||||
.db
|
||||
.cve_notifications()
|
||||
.update_one(notif_filter, notif_update)
|
||||
.upsert(true)
|
||||
.await
|
||||
{
|
||||
if result.upserted_id.is_some() {
|
||||
new_notif_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_notif_count > 0 {
|
||||
tracing::info!("[{repo_id}] Created {new_notif_count} CVE notification(s)");
|
||||
}
|
||||
}
|
||||
|
||||
// Stage 6: Issue Creation
|
||||
|
||||
Reference in New Issue
Block a user