feat: Add security vulnerability management module#688
feat: Add security vulnerability management module#688mikejmorgan-ai wants to merge 2 commits intomainfrom
Conversation
Implements autonomous security vulnerability scanning and patching for CX Linux. Features: - Vulnerability scanner with OSV/NVD API integration - Autonomous patcher with safety controls (dry-run by default) - Scheduled security maintenance via systemd timers - SQLite-backed persistence for scan history and rollback support - Configurable whitelist/blacklist for packages - Severity-based filtering (critical_only, high_and_above, all) CLI Commands: - cx security scan --all # Scan all packages - cx security scan --package openssl # Scan specific package - cx security scan --critical # Show only critical CVEs - cx security patch --scan-and-patch # Create patch plan (dry-run) - cx security patch --apply # Apply patches - cx security schedule create monthly-patch --frequency monthly - cx security schedule install-timer monthly-patch - cx security status # Show security overview Closes #422 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a new Security Vulnerability Management subsystem: CLI commands for scanning, patching, scheduling, and status; a SQLite-backed database; OSV vulnerability querying with caching; autonomous patcher with snapshot/rollback support; and systemd timer integration for scheduled runs. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI
participant Scanner
participant OSV as OSV API
participant Cache
participant DB
User->>CLI: wezterm security scan --all
CLI->>Scanner: run_scan()
Scanner->>Scanner: get_installed_packages()
Scanner->>Cache: load_vulnerability_cache()
loop for each package
alt cache hit
Cache-->>Scanner: cached vulnerabilities
else cache miss
Scanner->>OSV: POST query_osv(package)
OSV-->>Scanner: vuln JSON / 404
Scanner->>Cache: update cache
end
Scanner->>Scanner: filter by severity, aggregate
end
Scanner->>DB: save_scan_result()
DB-->>DB: insert records
Scanner->>User: print formatted output
sequenceDiagram
participant User
participant CLI
participant Patcher
participant Scanner
participant APT as SystemAPT
participant Snapshot
participant DB
User->>CLI: wezterm security patch --scan-and-patch --apply
CLI->>Patcher: run_patch()
Patcher->>Scanner: get last scan / run_scan()
Scanner-->>Patcher: vulnerable packages
Patcher->>Patcher: build patch plan (strategy, whitelist/blacklist)
loop for each package in plan
Patcher->>APT: get_available_update(package)
APT-->>Patcher: candidate version
end
Patcher->>User: display plan
alt apply
Patcher->>Snapshot: create_snapshot() (optional)
Snapshot-->>Patcher: success/fail
loop apply patches
Patcher->>APT: apply_patch(package, version)
APT-->>Patcher: success/failure
Patcher->>DB: record_patch()
end
else dry-run
Patcher->>User: show dry-run results
end
Patcher->>User: print summary
sequenceDiagram
participant User
participant CLI
participant Scheduler
participant DB
participant Systemd
User->>CLI: wezterm security schedule create name --frequency monthly
CLI->>Scheduler: run_schedule(Create)
Scheduler->>DB: save_schedule()
DB-->>Scheduler: schedule id
User->>CLI: wezterm security schedule install-timer name
CLI->>Scheduler: run_schedule(InstallTimer)
Scheduler->>Systemd: write unit files + daemon-reload + enable timer
Systemd-->>Scheduler: success
Note over Systemd, Scheduler: Timer triggers -> CLI run schedule run
Systemd->>CLI: invoke schedule run
CLI->>Scheduler: run_schedule(Run)
Scheduler->>Scanner: run_scan() and optionally -> Patcher
Scheduler->>DB: update_schedule_last_run()
Scheduler->>User: send notification
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @mikejmorgan-ai, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request integrates a new, autonomous security vulnerability management module into CX Linux. It provides a complete solution for continuously monitoring system packages for known vulnerabilities, applying necessary patches with safety precautions, and automating these processes through scheduled tasks. The module aims to significantly enhance the system's security posture by streamlining vulnerability detection and remediation. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive security vulnerability management system for CX Linux, enabling autonomous scanning and patching of system packages. The feature addresses issue #422 by adding CLI commands for vulnerability detection via OSV/NVD APIs, automated patch application with safety controls, and systemd timer-based scheduling.
Changes:
- Added security module with four submodules: scanner, patcher, scheduler, and database
- Integrated new CLI commands under
cx securitywith scan, patch, schedule, and status subcommands - Added SQLite persistence for vulnerability cache, scan history, and patch tracking
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| wezterm/src/cli/security/mod.rs | Defines CLI command structure and enums for security operations |
| wezterm/src/cli/security/scanner.rs | Implements OSV API integration and package vulnerability scanning |
| wezterm/src/cli/security/patcher.rs | Autonomous patch application with whitelist/blacklist and rollback support |
| wezterm/src/cli/security/scheduler.rs | Systemd timer management for automated security maintenance |
| wezterm/src/cli/security/database.rs | SQLite persistence layer for scan results and patch history |
| wezterm/src/cli/mod.rs | Integrates security commands into main CLI dispatcher |
| wezterm/Cargo.toml | Adds dependencies for SQLite, HTTP client, UUID generation |
|
|
||
| /// Check if running as root | ||
| fn is_root() -> bool { | ||
| unsafe { libc::geteuid() == 0 } |
There was a problem hiding this comment.
Using unsafe FFI call to check root privileges. Consider using a safe wrapper like the nix crate's nix::unistd::geteuid() function which provides the same functionality without requiring unsafe blocks.
| let _ = Command::new("apt-get") | ||
| .args(["update", "-qq"]) | ||
| .output(); |
There was a problem hiding this comment.
Silent failure of apt-get update could lead to stale package cache and incorrect patch decisions. Consider logging warnings when the update fails, or checking the status to ensure the cache is current before proceeding with patch operations.
|
@github-copilot Review this PR using the CX Linux Sovereign Reviewer instructions. Specifically, diagnose the 19 failing checks and verify if the 'Sovereign Purple' branding or BSL 1.1 headers are causing the CodeQL failure. |
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and well-designed security vulnerability management module. The features for scanning, autonomous patching with safety controls like dry-runs and snapshots, and scheduling are comprehensive. The code is generally well-structured. My review focuses on improving robustness in a few areas, particularly around file path handling and data serialization to ensure the module is reliable for all users.
| dirs::data_dir() | ||
| .unwrap_or_else(|| PathBuf::from("/var/lib")) | ||
| .join("cx-linux") | ||
| } |
There was a problem hiding this comment.
Using /var/lib as a fallback for the database directory will likely cause permission denied errors for non-root users if dirs::data_dir() returns None. A more robust approach would be to fall back to a user-writable path, such as a directory within the user's home directory, or to return an error if a suitable directory cannot be found.
| /// Load patcher configuration | ||
| fn load_patcher_config() -> Result<PatcherConfig> { | ||
| let config_path = dirs::config_dir() | ||
| .unwrap_or_else(|| std::path::PathBuf::from("~/.config")) |
There was a problem hiding this comment.
PathBuf::from("~/.config") does not expand the tilde ~ to the user's home directory. This will result in looking for the config file in a subdirectory of the current working directory named ~, which is incorrect. Please use dirs::home_dir() to correctly resolve the home directory.
| .unwrap_or_else(|| std::path::PathBuf::from("~/.config")) | |
| .unwrap_or_else(|| dirs::home_dir().expect("could not determine home directory").join(".config")) |
| /// Systemd unit file paths | ||
| fn get_systemd_user_dir() -> PathBuf { | ||
| dirs::config_dir() | ||
| .unwrap_or_else(|| PathBuf::from("~/.config")) |
There was a problem hiding this comment.
PathBuf::from("~/.config") does not expand the tilde ~ to the user's home directory. This will result in looking for the systemd user directory in a wrong location (<current_dir>/~/.config/...). Please use dirs::home_dir() to correctly resolve the home directory.
| .unwrap_or_else(|| PathBuf::from("~/.config")) | |
| .unwrap_or_else(|| dirs::home_dir().expect("could not determine home directory").join(".config")) |
| vuln.id, | ||
| aliases, | ||
| vuln.summary, | ||
| format!("{:?}", vuln.severity), |
There was a problem hiding this comment.
Serializing enums like Severity using their Debug format (format!("{:?}", ...) can be brittle, as the Debug output is not guaranteed to be stable. This could lead to issues with data persistence and retrieval if the enum definition or compiler behavior changes. A more robust approach is to define a stable string representation, for example by implementing as_str() and FromStr, or by using a serialization library like serde with a string representation.
There was a problem hiding this comment.
Actionable comments posted: 18
🤖 Fix all issues with AI agents
In `@wezterm/src/cli/security/database.rs`:
- Around line 1-31: Update the file header and the data directory branding:
replace the BSL header copyright and owner text that currently references "CX
Linux" with the required "AI Venture Holdings LLC" BSL 1.1 header, and change
the data directory string in the get_db_dir() function (currently joining
"cx-linux") to "cxlinux-ai"; locate these changes in the top-of-file comment
block and the get_db_dir() function to ensure all occurrences of "CX Linux" and
"cx-linux" are replaced with the mandated "AI Venture Holdings LLC" and
"cxlinux-ai" respectively.
- Around line 265-268: The query currently collects rows with silent error
suppression using stmt.query_map(...).filter_map(|r| r.ok()).collect(), which
hides row-level DB errors; change the collection to propagate errors by using
collect::<Result<Vec<_>, _>>()? instead of filter_map so failures bubble up
(apply this change for the packages query where packages: Vec<(String, String,
String)> is built from stmt.query_map, and make the same replacement for the
vulnerabilities query and the patches query to ensure any row mapping errors are
returned rather than discarded).
- Around line 147-222: The save_scan_result function currently performs multiple
deletes and inserts directly on self.conn, risking partial writes; wrap the
whole persistence block in a rusqlite transaction by calling let tx =
self.conn.transaction()? at the start, replace calls to self.conn.execute(...)
with tx.execute(...) (and use tx.prepare/tx.execute_named if needed for other
operations), perform the DELETE FROM pending_patches and all INSERTs using tx,
and call tx.commit()? at the end to persist; this ensures the transaction will
roll back automatically on error and prevents partial state if any insert fails.
In `@wezterm/src/cli/security/mod.rs`:
- Around line 1-37: Replace the leading doc-style block comment with a plain
block comment and update the copyright owner: change the opening "/** ... */"
header to a non-doc "/* ... */" block so it isn't treated as documentation, and
replace "Copyright (c) 2026 CX Linux" with "Copyright (c) 2026 AI Venture
Holdings LLC"; leave the subsequent //! module-level documentation intact so
rustfmt/rustc no longer errors.
In `@wezterm/src/cli/security/patcher.rs`:
- Around line 141-167: The current has_fix calculation in the patcher (inside
the block after get_available_update(&vp.package.name)) uses lexicographic
comparison (target >= fixed) which is wrong for Debian versions; change this to
use a proper Debian version comparison (e.g., use a Debian version parser crate
like debian-archive/version or call out to dpkg --compare-versions) when
evaluating vp.vulnerabilities.fixed_version against target. Update the has_fix
computation (the closure iterating vp.vulnerabilities) to parse/compare using
the chosen Debian-aware comparator and ensure any parsing errors are handled
(treat as not fixed or log), leaving PatchPlan construction and fields
(package_name, current_version, target_version, vulnerabilities_fixed, severity,
safe_to_apply, reason) unchanged.
- Around line 323-380: The calls that spawn external processes in apply_patch
and create_snapshot (specifically Command::new invocations for "apt-get",
"timeshift", "snapper", and "dpkg") must be replaced to use the existing
CommandValidator/SafeCommand wrapper: validate the package_name with
CommandValidator (or appropriate validator) before use, build a SafeCommand for
apt-get and run it for both the versioned install and the fallback install, and
likewise construct and run SafeCommand instances for timeshift, snapper and dpkg
(including the dpkg --get-selections output path). Update apply_patch,
create_snapshot, and the similar block around the other apt-get invocation noted
in the review to use SafeCommand APIs so no unvalidated user/DB input is passed
directly into Command::new.
- Around line 57-65: The loaded config's min_severity and auto_snapshot values
are read but never applied; update the code that builds the patching strategy
and snapshot behavior to honor these settings by merging cmd and config (like
whitelist/blacklist already do) and passing the resulting min_severity and
auto_snapshot into the strategy/snapshot logic (i.e., where you construct the
patching strategy and where snapshots are triggered/created around lines where
you handle strategy/snapshot behavior). Ensure cmd values override config (use
cmd.min_severity if set else config.min_severity, same for auto_snapshot) and
propagate those final values into the functions/classes that control severity
filtering and automatic snapshot creation so user config takes effect.
- Around line 408-412: The load_patcher_config function builds config_path using
dirs::config_dir() but falls back to a literal "~/.config" which is not
expanded; change the fallback to use dirs::home_dir() (e.g. map
home_dir().join(".config")) or return an Err when dirs::config_dir() is None, so
config_path is computed from a real filesystem path; update the config_path
construction in load_patcher_config to use dirs::config_dir().or_else(||
dirs::home_dir().map(|h| h.join(".config"))) (or propagate an error) before
joining "cx-linux" and "patcher_config.json".
- Around line 1-14: The file header is currently a doc comment (/** ... */)
which combined with the //! module docs causes rustfmt/rustc issues; change that
block to a plain block comment (/* ... */) and update the copyright owner to "AI
Venture Holdings LLC" while preserving the Business Source License 1.1 text.
Locate the top-of-file header in patcher.rs and replace the /** ... */ doc-style
header with a non-doc /* ... */ BSL 1.1 header containing the required copyright
string, leaving the following //! module documentation intact.
In `@wezterm/src/cli/security/scanner.rs`:
- Around line 181-190: The code in get_packages_from_apt_cache is calling
Command::new("dpkg-query") directly; replace this raw invocation with the
project's CommandValidator/SafeCommand flow: create a CommandValidator for
"dpkg-query", validate the fixed args ["-W",
"-f=${Package}\t${Version}\t${Architecture}\t${Status}\n"], obtain a SafeCommand
from the validator and execute it (calling the SafeCommand method that returns
output), then preserve the existing error handling (context("Failed to run
dpkg-query")? and the status check/anyhow::bail! on non-success) so behavior
remains the same but uses the validated/safer execution path used elsewhere in
the codebase.
- Around line 1-11: Replace the leading doc-style block comment /** ... */ with
a plain C-style comment /* ... */ (so it is not treated as a Rust doc comment)
and update the copyright owner inside that header to "AI Venture Holdings LLC";
ensure the existing module-level doc comments (the //! lines) remain unchanged
and that the new header is a non-doc comment placed above them.
- Around line 658-664: The truncate function currently slices bytes and can
panic on multi-byte UTF-8 characters; update fn truncate(s: &str, max_len:
usize) to operate on characters instead of bytes (e.g., use s.chars().count() or
s.chars().take(...) to build a safe String), handle the edge case when max_len <
3 so ellipsis fits, and append "..." only when the char-length exceeds max_len;
reference the truncate function and replace the &s[..max_len.saturating_sub(3)]
byte slice with a chars-based take/collect approach (or use
unicode-segmentation) to ensure UTF-8 safety.
- Around line 213-236: query_osv currently calls ureq::post(...) without
timeouts, which can block indefinitely; create a single ureq::Agent with
explicit timeouts (e.g., timeout_connect, timeout_read, timeout_write, timeout
overall) and reuse it for all requests, then replace ureq::post(OSV_API_URL)
calls in query_osv (and the other ureq::post use around the scanner code) with
agent.post(OSV_API_URL) so every OSV API request uses the configured timeouts;
ensure the Agent is built once (e.g., at the start of query_osv or higher-level
scope) and reused for subsequent send_json/send calls and error handling.
In `@wezterm/src/cli/security/scheduler.rs`:
- Around line 60-96: The create_schedule path embeds user input
(ScheduleCreateCommand::name / cmd.name) directly into systemd ExecStart which
can break parsing or inject args; validate and sanitize schedule names in
create_schedule (and the corresponding install-timer handler) by enforcing an
allowlist (e.g., alphanumeric, hyphen/underscore) via the project's
CommandValidator/SafeCommand types, reject or normalize invalid names, and when
generating the unit file always pass the name as a properly escaped/quoted
single argument (or use SafeCommand escaping utilities) rather than
interpolating raw cmd.name into ExecStart.
- Around line 214-283: The install_timer function currently invokes external
programs directly via Command (e.g., reload_cmd, enable_cmd, status_cmd calling
"systemctl") using user-derived unit_name/schedule.name; update these
invocations to route through the project's CommandValidator/SafeCommand API
instead of std::process::Command: replace all Command::new("systemctl") usages
in install_timer with the SafeCommand construction/validation pattern (validate
the executable and each arg via CommandValidator), ensure unit_name (even if
sanitized by sanitize_name) is validated by SafeCommand before being used, and
apply the same change for other similar call sites mentioned (notify-send, wall
in the other ranges) so every security-critical external call uses
CommandValidator/SafeCommand rather than raw Command.
- Around line 36-42: get_systemd_user_dir currently falls back to
PathBuf::from("~/.config") which does not expand ~; change the fallback to use
dirs::home_dir() and join(".config") so the returned path is based on the actual
home directory (e.g. dirs::home_dir().map(|h| h.join(".config"))) or make
get_systemd_user_dir return a Result/Option and propagate an error if neither
config_dir nor home_dir is available; update the implementation of
get_systemd_user_dir to use dirs::home_dir().map(|h| h.join(".config")) as the
fallback instead of the literal "~/.config".
- Around line 1-11: Replace the leading doc-style block comment and incorrect
owner with a plain non-doc block comment containing the BSL 1.1 header and the
correct copyright owner; specifically, change the initial "/** ... */" to a
regular "/* ... */" (or line comments) and update the copyright line to "AI
Venture Holdings LLC", leaving the rest of the license text intact, and keep the
module-level "//! ..." docs as-is so rustfmt/rustc no longer treat the header as
documentation.
- Around line 458-464: The truncate function currently slices bytes and can
panic on multi-byte UTF‑8; update truncate to operate on Unicode scalar values
instead: in fn truncate(s: &str, max_len: usize) check character count with
s.chars().count() (or compare by collecting as needed) and when truncation is
required build the prefix using
s.chars().take(max_len.saturating_sub(3)).collect::<String>() (handle small
max_len via saturating_sub) then append "..." — replace the byte-slice usage in
truncate with this char-safe approach.
🧹 Nitpick comments (2)
wezterm/src/cli/mod.rs (1)
34-35: Use the required// CX Terminal:prefix and state intent.The new CX comments are using a different prefix and read as “what” rather than “why.” Consider aligning them to the required prefix and intent-based wording.
🔧 Example adjustment
-// CX Security: Vulnerability management +// CX Terminal: expose security commands that run without a mux session. pub mod security; @@ -// CX Security: Vulnerability Management +// CX Terminal: route security operations through a mux-free command path. /// Security vulnerability scanning and patching #[command(name = "security", about = "Security vulnerability management")] Security(security::SecurityCommand),As per coding guidelines: "Rust: Mark CX additions with '// CX Terminal:' comment prefix" and "Code comments must explain WHY, never WHAT - focus on intent and reasoning."
Also applies to: 219-222
wezterm/src/cli/security/database.rs (1)
7-41: Rewrite doc comments to “why” intent and add the CX Terminal prefix.Most comments describe what the code does. Please convert them to “why/intent” phrasing and apply the required CX Terminal prefix (including module-level docs).
As per coding guidelines, “Write comments explaining WHY, never WHAT or HOW, following zero documentation philosophy” and “Rust: Mark CX additions with '// CX Terminal:' comment prefix”.♻️ Example adjustment
-//! Security Database Module -//! -//! SQLite-backed persistence for: -//! - Vulnerability scan results -//! - Patch history with rollback support -//! - Scheduled security jobs -//! - Vulnerability cache +// CX Terminal: Persist scan/patch/schedule state so rollbacks and audits survive restarts. ... -/// Get the database directory +// CX Terminal: Keep security data in a stable path shared across CLI runs.
| /** | ||
| * Copyright (c) 2026 CX Linux | ||
| * Licensed under the Business Source License 1.1 | ||
| * You may not use this file except in compliance with the License. | ||
| */ | ||
|
|
||
| //! Security Database Module | ||
| //! | ||
| //! SQLite-backed persistence for: | ||
| //! - Vulnerability scan results | ||
| //! - Patch history with rollback support | ||
| //! - Scheduled security jobs | ||
| //! - Vulnerability cache | ||
|
|
||
| use anyhow::{Context, Result}; | ||
| use rusqlite::{Connection, params}; | ||
| use serde::{Deserialize, Serialize}; | ||
| use std::collections::HashMap; | ||
| use std::path::PathBuf; | ||
| use std::time::SystemTime; | ||
|
|
||
| use super::scanner::{Vulnerability, VulnerablePackage, ScanSummary, InstalledPackage, Severity}; | ||
| use super::scheduler::Schedule; | ||
| use super::{ScheduleFrequency, PatchStrategy}; | ||
|
|
||
| /// Get the database directory | ||
| fn get_db_dir() -> PathBuf { | ||
| dirs::data_dir() | ||
| .unwrap_or_else(|| PathBuf::from("/var/lib")) | ||
| .join("cx-linux") | ||
| } |
There was a problem hiding this comment.
Update BSL header ownership and CX branding.
The header still names “CX Linux,” and the data directory uses “cx-linux.” Both conflict with the required ownership/branding. Please update to AI Venture Holdings LLC and the cxlinux-ai naming.
As per coding guidelines, “Include BSL 1.1 license header on all new files with copyright to AI Venture Holdings LLC” and “Never use 'cortex' or 'cortexlinux' branding - use 'cx' and 'cxlinux-ai' instead”.
✅ Suggested fix
- * Copyright (c) 2026 CX Linux
+ * Copyright (c) 2026 AI Venture Holdings LLC
...
- .join("cx-linux")
+ .join("cxlinux-ai")🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/database.rs` around lines 1 - 31, Update the file
header and the data directory branding: replace the BSL header copyright and
owner text that currently references "CX Linux" with the required "AI Venture
Holdings LLC" BSL 1.1 header, and change the data directory string in the
get_db_dir() function (currently joining "cx-linux") to "cxlinux-ai"; locate
these changes in the top-of-file comment block and the get_db_dir() function to
ensure all occurrences of "CX Linux" and "cx-linux" are replaced with the
mandated "AI Venture Holdings LLC" and "cxlinux-ai" respectively.
| /// Save scan result | ||
| pub fn save_scan_result(&self, summary: &ScanSummary) -> Result<()> { | ||
| let scan_id = uuid::Uuid::new_v4().to_string(); | ||
| let timestamp = chrono::Utc::now().to_rfc3339(); | ||
|
|
||
| self.conn.execute( | ||
| "INSERT INTO scan_results (id, timestamp, packages_scanned, vulnerabilities_found, | ||
| critical_count, high_count, medium_count, low_count, duration_ms) | ||
| VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", | ||
| params![ | ||
| scan_id, | ||
| timestamp, | ||
| summary.scanned_packages, | ||
| summary.vulnerabilities_found, | ||
| summary.critical_count, | ||
| summary.high_count, | ||
| summary.medium_count, | ||
| summary.low_count, | ||
| summary.scan_duration_ms | ||
| ], | ||
| )?; | ||
|
|
||
| // Clear and update pending patches | ||
| self.conn.execute("DELETE FROM pending_patches", [])?; | ||
|
|
||
| // Save vulnerable packages and vulnerabilities | ||
| for vp in &summary.vulnerable_packages { | ||
| let vp_id = uuid::Uuid::new_v4().to_string(); | ||
|
|
||
| self.conn.execute( | ||
| "INSERT INTO vulnerable_packages (id, scan_id, package_name, package_version) | ||
| VALUES (?1, ?2, ?3, ?4)", | ||
| params![vp_id, scan_id, vp.package.name, vp.package.version], | ||
| )?; | ||
|
|
||
| for vuln in &vp.vulnerabilities { | ||
| let v_id = uuid::Uuid::new_v4().to_string(); | ||
| let aliases = serde_json::to_string(&vuln.aliases)?; | ||
|
|
||
| self.conn.execute( | ||
| "INSERT INTO vulnerabilities (id, vuln_package_id, vuln_id, aliases, summary, | ||
| severity, cvss_score, fixed_version) | ||
| VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", | ||
| params![ | ||
| v_id, | ||
| vp_id, | ||
| vuln.id, | ||
| aliases, | ||
| vuln.summary, | ||
| format!("{:?}", vuln.severity), | ||
| vuln.cvss_score, | ||
| vuln.fixed_version | ||
| ], | ||
| )?; | ||
|
|
||
| // Add to pending patches if there's a fix | ||
| if let Some(ref fixed) = vuln.fixed_version { | ||
| let vuln_ids = serde_json::to_string(&vuln.aliases)?; | ||
| self.conn.execute( | ||
| "INSERT OR REPLACE INTO pending_patches | ||
| (id, package_name, current_version, fixed_version, severity, vuln_ids) | ||
| VALUES (?1, ?2, ?3, ?4, ?5, ?6)", | ||
| params![ | ||
| uuid::Uuid::new_v4().to_string(), | ||
| vp.package.name, | ||
| vp.package.version, | ||
| fixed, | ||
| format!("{:?}", vuln.severity), | ||
| vuln_ids | ||
| ], | ||
| )?; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(()) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists
git ls-files | grep -i "database.rs"Repository: cxlinux-ai/cx-core
Length of output: 98
🏁 Script executed:
# Find the file more broadly
find . -name "database.rs" -type fRepository: cxlinux-ai/cx-core
Length of output: 100
🏁 Script executed:
# Check the structure of the repository
ls -la | head -20Repository: cxlinux-ai/cx-core
Length of output: 1245
🏁 Script executed:
# Check the file size first
wc -l wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 102
🏁 Script executed:
# Read the file to understand the structure and find the save_scan_result method
head -250 wezterm/src/cli/security/database.rs | tail -150Repository: cxlinux-ai/cx-core
Length of output: 5845
🏁 Script executed:
# Find all callers of save_scan_result
rg "save_scan_result" --type rustRepository: cxlinux-ai/cx-core
Length of output: 244
🏁 Script executed:
# Check the struct definition and see if it has impl blocks
head -150 wezterm/src/cli/security/database.rs | grep -A 20 "impl\|struct"Repository: cxlinux-ai/cx-core
Length of output: 738
🏁 Script executed:
# Check if transaction method exists in this file or codebase
rg "transaction\(\)" --type rustRepository: cxlinux-ai/cx-core
Length of output: 44
🏁 Script executed:
# Search for transaction usage in the codebase
rg "\.transaction\(" --type rustRepository: cxlinux-ai/cx-core
Length of output: 44
🏁 Script executed:
# Check where save_scan_result is called in scanner.rs
rg "save_scan_result" wezterm/src/cli/security/scanner.rs -A 5 -B 5Repository: cxlinux-ai/cx-core
Length of output: 245
🏁 Script executed:
# Check the full method signature and context in scanner.rs
cat wezterm/src/cli/security/scanner.rs | grep -A 20 -B 10 "save_scan_result"Repository: cxlinux-ai/cx-core
Length of output: 1043
🏁 Script executed:
# Check the Cargo.toml for rusqlite version
grep -A 5 "rusqlite" Cargo.tomlRepository: cxlinux-ai/cx-core
Length of output: 298
🏁 Script executed:
# Check for any use of &mut in method calls related to db operations
rg "let mut db" --type rust -A 3Repository: cxlinux-ai/cx-core
Length of output: 434
🏁 Script executed:
# Look for database-related patterns where methods might need &mut
rg "fn.*&mut self.*Result" wezterm/src/cli/security/ --type rustRepository: cxlinux-ai/cx-core
Length of output: 44
🏁 Script executed:
# Check if other database methods use &self or &mut self
rg "impl SecurityDatabase" -A 100 wezterm/src/cli/security/database.rs | head -200Repository: cxlinux-ai/cx-core
Length of output: 3729
🏁 Script executed:
# Let's see the full context of the scan operation in scanner.rs
wc -l wezterm/src/cli/security/scanner.rsRepository: cxlinux-ai/cx-core
Length of output: 101
🏁 Script executed:
# Get more context around the save_scan_result call in scanner.rs
head -300 wezterm/src/cli/security/scanner.rs | tail -100Repository: cxlinux-ai/cx-core
Length of output: 3646
🏁 Script executed:
# Look at the full SecurityDatabase impl to see all methods
grep -n "pub fn\|fn " wezterm/src/cli/security/database.rs | head -30Repository: cxlinux-ai/cx-core
Length of output: 1544
🏁 Script executed:
# Check the rusqlite version and see if transaction requires &mut self
# Let's look for transaction usage patterns in the codebase
rg "Transaction" --type rustRepository: cxlinux-ai/cx-core
Length of output: 44
🏁 Script executed:
# Check if Connection::transaction takes &self or &mut self
# Let's search for any documentation or examples in dependencies
grep -r "transaction" Cargo.lock | head -10Repository: cxlinux-ai/cx-core
Length of output: 44
🏁 Script executed:
# Let's verify the exact signature by checking online docs or inferred usage
# Create a small test to understand rusqlite's transaction API
python3 << 'EOF'
# Check if rusqlite 0.32's transaction method signature
# According to rusqlite 0.32 docs, Connection::transaction(&self) returns a Transaction
# which means &self is sufficient, not &mut self
print("rusqlite 0.32 API: Connection::transaction(&self) -> Result<Transaction>")
print("This means &self is sufficient - no need to change to &mut self")
EOFRepository: cxlinux-ai/cx-core
Length of output: 148
🏁 Script executed:
# Check all other methods in SecurityDatabase to understand the pattern
sed -n '50,530p' wezterm/src/cli/security/database.rs | grep -E "pub fn|fn " | head -20Repository: cxlinux-ai/cx-core
Length of output: 991
🏁 Script executed:
# Let's check the actual rusqlite 0.32 transaction method signature
# by looking at examples in the codebase or documentation
rg "conn\.transaction\(\)" --type rust -B 3 -A 3Repository: cxlinux-ai/cx-core
Length of output: 44
🏁 Script executed:
# Check how other write methods like record_patch handle multi-step operations
sed -n '338,360p' wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 846
🏁 Script executed:
# Check if there are any existing transaction patterns for comparison
rg "execute_batch\|execute\(" wezterm/src/cli/security/database.rs -A 1 | head -50Repository: cxlinux-ai/cx-core
Length of output: 44
🏁 Script executed:
# Let's check the web for rusqlite 0.32 transaction API
# We need to understand if transaction() requires &mut self or &self
cat > /tmp/check_rusqlite.py << 'EOF'
import json
# Based on rusqlite documentation:
# In rusqlite 0.32, Connection::transaction(&self) returns Result<Transaction<'_>>
# The transaction() method takes &self, NOT &mut self
# This is because the connection maintains internal state for the transaction
info = {
"rusqlite_version": "0.32",
"transaction_signature": "fn transaction(&self) -> Result<Transaction<'_>>",
"requires_mut": False,
"reason": "Connection manages transaction state internally, &self is sufficient"
}
print(json.dumps(info, indent=2))
EOF
python3 /tmp/check_rusqlite.pyRepository: cxlinux-ai/cx-core
Length of output: 282
🏁 Script executed:
# Let's verify the proposed fix is compatible with the caller
# Check the exact call site in scanner.rs
sed -n '1,665p' wezterm/src/cli/security/scanner.rs | grep -B 10 -A 5 "save_scan_result"Repository: cxlinux-ai/cx-core
Length of output: 406
🏁 Script executed:
# Let's verify all the method calls in the proposed fix are compatible
# Check if any methods called within save_scan_result would break
sed -n '148,225p' wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 3151
🏁 Script executed:
# Verify how the db instance is used in the caller - check if making it mutable would be problematic
sed -n '500,665p' wezterm/src/cli/security/scanner.rs | tail -80Repository: cxlinux-ai/cx-core
Length of output: 2631
🏁 Script executed:
# Double-check: look for any other transaction usage patterns in Rust code
rg "\.commit\(\)" --type rustRepository: cxlinux-ai/cx-core
Length of output: 1059
Wrap scan persistence in a transaction to prevent partial writes.
save_scan_result clears pending_patches and then performs multiple inserts. Any failure mid-way leaves the DB in a partial state. Use a transaction with conn.transaction() and tx.commit() at the end.
🧰 Proposed fix
pub fn save_scan_result(&self, summary: &ScanSummary) -> Result<()> {
+ let tx = self.conn.transaction()?;
let scan_id = uuid::Uuid::new_v4().to_string();
let timestamp = chrono::Utc::now().to_rfc3339();
- self.conn.execute(
+ tx.execute(
"INSERT INTO scan_results (id, timestamp, packages_scanned, vulnerabilities_found,
critical_count, high_count, medium_count, low_count, duration_ms)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
params![ /* ... */ ],
)?;
- self.conn.execute("DELETE FROM pending_patches", [])?;
+ tx.execute("DELETE FROM pending_patches", [])?;
// Save vulnerable packages and vulnerabilities
for vp in &summary.vulnerable_packages {
let vp_id = uuid::Uuid::new_v4().to_string();
- self.conn.execute(
+ tx.execute(
"INSERT INTO vulnerable_packages (id, scan_id, package_name, package_version)
VALUES (?1, ?2, ?3, ?4)",
params![ /* ... */ ],
)?;
for vuln in &vp.vulnerabilities {
let v_id = uuid::Uuid::new_v4().to_string();
let aliases = serde_json::to_string(&vuln.aliases)?;
- self.conn.execute(
+ tx.execute(
"INSERT INTO vulnerabilities (id, vuln_package_id, vuln_id, aliases, summary,
severity, cvss_score, fixed_version)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![ /* ... */ ],
)?;
if let Some(ref fixed) = vuln.fixed_version {
let vuln_ids = serde_json::to_string(&vuln.aliases)?;
- self.conn.execute(
+ tx.execute(
"INSERT OR REPLACE INTO pending_patches
(id, package_name, current_version, fixed_version, severity, vuln_ids)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
params![ /* ... */ ],
)?;
}
}
}
+ tx.commit()?;
Ok(())
}🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/database.rs` around lines 147 - 222, The
save_scan_result function currently performs multiple deletes and inserts
directly on self.conn, risking partial writes; wrap the whole persistence block
in a rusqlite transaction by calling let tx = self.conn.transaction()? at the
start, replace calls to self.conn.execute(...) with tx.execute(...) (and use
tx.prepare/tx.execute_named if needed for other operations), perform the DELETE
FROM pending_patches and all INSERTs using tx, and call tx.commit()? at the end
to persist; this ensures the transaction will roll back automatically on error
and prevents partial state if any insert fails.
| let packages: Vec<(String, String, String)> = stmt.query_map([&last_scan.id], |row| { | ||
| Ok((row.get(0)?, row.get(1)?, row.get(2)?)) | ||
| })?.filter_map(|r| r.ok()).collect(); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and read the file
fd -type f -name "database.rs" | grep -E "security|cli"Repository: cxlinux-ai/cx-core
Length of output: 232
🏁 Script executed:
# Find the database.rs file with correct fd syntax
fd database.rsRepository: cxlinux-ai/cx-core
Length of output: 98
🏁 Script executed:
# Check file size
wc -l wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 102
🏁 Script executed:
# Read the relevant sections with context
sed -n '260,310p' wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 2163
🏁 Script executed:
# Read lines 320-335 to see the third occurrence
sed -n '320,335p' wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 584
🏁 Script executed:
# Let me check if there's any error handling context or logging around these operations
sed -n '250,275p' wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 1047
🏁 Script executed:
# Let me verify the function signatures and check the return type and error handling approach
sed -n '240,260p' wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 705
🏁 Script executed:
# Let me check the full context around line 300 to see the complete vulns query
sed -n '275,310p' wezterm/src/cli/security/database.rsRepository: cxlinux-ai/cx-core
Length of output: 1512
Remove .filter_map(|r| r.ok()) error suppression in database queries.
This pattern silently discards row-level database errors, masking potential data corruption in a security-critical vulnerability database. Replace with proper error propagation using collect::<Result<Vec<_>, _>>()?.
Suggested fix
- let packages: Vec<(String, String, String)> = stmt.query_map([&last_scan.id], |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?))
- })?.filter_map(|r| r.ok()).collect();
+ let packages: Vec<(String, String, String)> = stmt
+ .query_map([&last_scan.id], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
+ .collect::<Result<Vec<_>, _>>()?;Apply the same fix to the vulnerabilities query (around line 277) and patches query (around line 325).
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/database.rs` around lines 265 - 268, The query
currently collects rows with silent error suppression using
stmt.query_map(...).filter_map(|r| r.ok()).collect(), which hides row-level DB
errors; change the collection to propagate errors by using
collect::<Result<Vec<_>, _>>()? instead of filter_map so failures bubble up
(apply this change for the packages query where packages: Vec<(String, String,
String)> is built from stmt.query_map, and make the same replacement for the
vulnerabilities query and the patches query to ensure any row mapping errors are
returned rather than discarded).
| /** | ||
| * Copyright (c) 2026 CX Linux | ||
| * Licensed under the Business Source License 1.1 | ||
| * You may not use this file except in compliance with the License. | ||
| */ | ||
|
|
||
| //! Security Vulnerability Management Module | ||
| //! | ||
| //! Provides continuous vulnerability scanning, autonomous patching, | ||
| //! and scheduled security maintenance for CX Linux. | ||
| //! | ||
| //! # Features | ||
| //! - Vulnerability scanning against OSV and NVD databases | ||
| //! - Autonomous patching with safety controls | ||
| //! - Scheduled security maintenance via systemd timers | ||
| //! - Full rollback support via installation history | ||
| //! | ||
| //! # Example Usage | ||
| //! ```bash | ||
| //! # Scan all installed packages | ||
| //! cx security scan --all | ||
| //! | ||
| //! # Scan specific package | ||
| //! cx security scan --package openssl | ||
| //! | ||
| //! # Show only critical vulnerabilities | ||
| //! cx security scan --critical | ||
| //! | ||
| //! # Autonomous patching (dry-run by default) | ||
| //! cx security patch --scan-and-patch --strategy critical_only | ||
| //! | ||
| //! # Apply patches | ||
| //! cx security patch --scan-and-patch --strategy critical_only --apply | ||
| //! | ||
| //! # Schedule monthly patching | ||
| //! cx security schedule create monthly-patch --frequency monthly --enable-patch | ||
| //! ``` |
There was a problem hiding this comment.
Fix the BSL header so it’s non-doc and uses the required copyright owner.
The /** ... */ header is treated as a doc comment and, combined with the //! block, triggers the rustfmt/rustc error shown in the pipeline. Switch the header to a plain comment and update the copyright holder.
🔧 Suggested header
-/**
- * Copyright (c) 2026 CX Linux
- * Licensed under the Business Source License 1.1
- * You may not use this file except in compliance with the License.
- */
+// Copyright (c) 2026 AI Venture Holdings LLC
+// Licensed under the Business Source License 1.1
+// You may not use this file except in compliance with the License.As per coding guidelines: "/*.{py,rs,js,ts,tsx,go,java}: Include BSL 1.1 license header on all new files with copyright to AI Venture Holdings LLC."
🧰 Tools
🪛 GitHub Actions: CX Terminal Build
[error] 7-37: Rust compile error: expected outer doc comment due to inner doc comments (//!). Inner doc comments can only appear before items. Convert to regular comments (//) or use correct outer doc comment syntax. This block includes many lines starting with '//!' that should be regular comments.
🪛 GitHub Actions: fmt
[error] 7-7: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 8-8: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 9-9: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 10-10: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 11-11: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 12-12: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 13-13: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 14-14: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 15-15: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 16-16: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 17-17: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 18-18: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 19-19: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 20-20: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 21-21: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 22-22: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 23-23: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 24-24: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 25-25: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 26-26: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 27-27: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 28-28: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 29-29: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 30-30: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 31-31: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 32-32: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 33-33: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 34-34: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 35-35: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 36-36: expected outer doc comment. Inner doc comments like '//!' can only appear before items.
[error] 37-37: expected outer doc comment. Inner doc comments like '//!' can only appear before items. The inner doc block is closing the module without annotation.
[error] 37-37: annotate the module with an outer doc comment (///) instead of inner (// or //!).
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/mod.rs` around lines 1 - 37, Replace the leading
doc-style block comment with a plain block comment and update the copyright
owner: change the opening "/** ... */" header to a non-doc "/* ... */" block so
it isn't treated as documentation, and replace "Copyright (c) 2026 CX Linux"
with "Copyright (c) 2026 AI Venture Holdings LLC"; leave the subsequent //!
module-level documentation intact so rustfmt/rustc no longer errors.
| /** | ||
| * Copyright (c) 2026 CX Linux | ||
| * Licensed under the Business Source License 1.1 | ||
| * You may not use this file except in compliance with the License. | ||
| */ | ||
|
|
||
| //! Autonomous Patcher Module | ||
| //! | ||
| //! Applies security patches with safety controls including: | ||
| //! - Dry-run by default | ||
| //! - Whitelist/blacklist support | ||
| //! - Severity-based filtering | ||
| //! - Rollback capability via installation history | ||
|
|
There was a problem hiding this comment.
Fix the BSL header so it’s non-doc and uses the required copyright owner.
The /** ... */ header is treated as a doc comment and, combined with the //! block, triggers the rustfmt/rustc error seen in the pipeline. Switch the header to a plain comment and update the copyright holder.
🔧 Suggested header
-/**
- * Copyright (c) 2026 CX Linux
- * Licensed under the Business Source License 1.1
- * You may not use this file except in compliance with the License.
- */
+// Copyright (c) 2026 AI Venture Holdings LLC
+// Licensed under the Business Source License 1.1
+// You may not use this file except in compliance with the License.As per coding guidelines: "/*.{py,rs,js,ts,tsx,go,java}: Include BSL 1.1 license header on all new files with copyright to AI Venture Holdings LLC."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * Copyright (c) 2026 CX Linux | |
| * Licensed under the Business Source License 1.1 | |
| * You may not use this file except in compliance with the License. | |
| */ | |
| //! Autonomous Patcher Module | |
| //! | |
| //! Applies security patches with safety controls including: | |
| //! - Dry-run by default | |
| //! - Whitelist/blacklist support | |
| //! - Severity-based filtering | |
| //! - Rollback capability via installation history | |
| // Copyright (c) 2026 AI Venture Holdings LLC | |
| // Licensed under the Business Source License 1.1 | |
| // You may not use this file except in compliance with the License. | |
| //! Autonomous Patcher Module | |
| //! | |
| //! Applies security patches with safety controls including: | |
| //! - Dry-run by default | |
| //! - Whitelist/blacklist support | |
| //! - Severity-based filtering | |
| //! - Rollback capability via installation history |
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/patcher.rs` around lines 1 - 14, The file header is
currently a doc comment (/** ... */) which combined with the //! module docs
causes rustfmt/rustc issues; change that block to a plain block comment (/* ...
*/) and update the copyright owner to "AI Venture Holdings LLC" while preserving
the Business Source License 1.1 text. Locate the top-of-file header in
patcher.rs and replace the /** ... */ doc-style header with a non-doc /* ... */
BSL 1.1 header containing the required copyright string, leaving the following
//! module documentation intact.
| /** | ||
| * Copyright (c) 2026 CX Linux | ||
| * Licensed under the Business Source License 1.1 | ||
| * You may not use this file except in compliance with the License. | ||
| */ | ||
|
|
||
| //! Security Scheduler Module | ||
| //! | ||
| //! Manages scheduled security scans and patches using systemd timers. | ||
| //! Supports daily, weekly, and monthly schedules with configurable actions. | ||
|
|
There was a problem hiding this comment.
Fix the BSL header so it’s non-doc and uses the required copyright owner.
The /** ... */ header is treated as a doc comment and, combined with the //! block, triggers the rustfmt/rustc error seen in the pipeline. Switch the header to a plain comment and update the copyright holder.
🔧 Suggested header
-/**
- * Copyright (c) 2026 CX Linux
- * Licensed under the Business Source License 1.1
- * You may not use this file except in compliance with the License.
- */
+// Copyright (c) 2026 AI Venture Holdings LLC
+// Licensed under the Business Source License 1.1
+// You may not use this file except in compliance with the License.As per coding guidelines: "/*.{py,rs,js,ts,tsx,go,java}: Include BSL 1.1 license header on all new files with copyright to AI Venture Holdings LLC."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * Copyright (c) 2026 CX Linux | |
| * Licensed under the Business Source License 1.1 | |
| * You may not use this file except in compliance with the License. | |
| */ | |
| //! Security Scheduler Module | |
| //! | |
| //! Manages scheduled security scans and patches using systemd timers. | |
| //! Supports daily, weekly, and monthly schedules with configurable actions. | |
| // Copyright (c) 2026 AI Venture Holdings LLC | |
| // Licensed under the Business Source License 1.1 | |
| // You may not use this file except in compliance with the License. | |
| //! Security Scheduler Module | |
| //! | |
| //! Manages scheduled security scans and patches using systemd timers. | |
| //! Supports daily, weekly, and monthly schedules with configurable actions. |
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/scheduler.rs` around lines 1 - 11, Replace the
leading doc-style block comment and incorrect owner with a plain non-doc block
comment containing the BSL 1.1 header and the correct copyright owner;
specifically, change the initial "/** ... */" to a regular "/* ... */" (or line
comments) and update the copyright line to "AI Venture Holdings LLC", leaving
the rest of the license text intact, and keep the module-level "//! ..." docs
as-is so rustfmt/rustc no longer treat the header as documentation.
| /// Systemd unit file paths | ||
| fn get_systemd_user_dir() -> PathBuf { | ||
| dirs::config_dir() | ||
| .unwrap_or_else(|| PathBuf::from("~/.config")) | ||
| .join("systemd") | ||
| .join("user") | ||
| } |
There was a problem hiding this comment.
Avoid a literal "~/.config" fallback path.
PathBuf::from("~/.config") won’t expand ~, so this can create an unexpected directory relative to the CWD. Prefer dirs::home_dir().map(|h| h.join(".config")) or return an error if no config directory is available.
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/scheduler.rs` around lines 36 - 42,
get_systemd_user_dir currently falls back to PathBuf::from("~/.config") which
does not expand ~; change the fallback to use dirs::home_dir() and
join(".config") so the returned path is based on the actual home directory (e.g.
dirs::home_dir().map(|h| h.join(".config"))) or make get_systemd_user_dir return
a Result/Option and propagate an error if neither config_dir nor home_dir is
available; update the implementation of get_systemd_user_dir to use
dirs::home_dir().map(|h| h.join(".config")) as the fallback instead of the
literal "~/.config".
| /// Create a new security schedule | ||
| fn create_schedule(cmd: ScheduleCreateCommand) -> Result<()> { | ||
| println!("📆 Creating security schedule: {}", cmd.name); | ||
|
|
||
| let db = SecurityDatabase::open()?; | ||
|
|
||
| // Check if schedule already exists | ||
| if db.get_schedule(&cmd.name)?.is_some() { | ||
| anyhow::bail!("Schedule '{}' already exists", cmd.name); | ||
| } | ||
|
|
||
| let schedule = Schedule { | ||
| id: uuid::Uuid::new_v4().to_string(), | ||
| name: cmd.name.clone(), | ||
| frequency: cmd.frequency, | ||
| enable_patch: cmd.enable_patch, | ||
| patch_strategy: cmd.strategy, | ||
| notify: cmd.notify, | ||
| created_at: chrono::Utc::now().to_rfc3339(), | ||
| last_run: None, | ||
| next_run: Some(calculate_next_run(cmd.frequency)), | ||
| timer_installed: false, | ||
| }; | ||
|
|
||
| db.save_schedule(&schedule)?; | ||
|
|
||
| println!("✅ Schedule created: {}", cmd.name); | ||
| println!(); | ||
| println!(" Frequency: {:?}", cmd.frequency); | ||
| println!(" Patching: {}", if cmd.enable_patch { "Enabled" } else { "Scan only" }); | ||
| if cmd.enable_patch { | ||
| println!(" Strategy: {:?}", cmd.strategy); | ||
| } | ||
| println!(" Notify: {}", if cmd.notify { "Yes" } else { "No" }); | ||
| println!(); | ||
| println!("ℹ️ Run 'cx security schedule install-timer {}' to activate systemd timer", cmd.name); | ||
|
|
There was a problem hiding this comment.
Validate schedule names and safely pass them into systemd units.
cmd.name is user input and is embedded in ExecStart without quoting/escaping. Names with spaces or -- will break parsing or inject unintended args. Validate allowed characters up front and/or escape the argument in ExecStart.
✅ Example validation in `create_schedule`
fn create_schedule(cmd: ScheduleCreateCommand) -> Result<()> {
+ if !cmd.name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
+ anyhow::bail!("Schedule name must be alphanumeric or '-' only");
+ }
println!("📆 Creating security schedule: {}", cmd.name);As per coding guidelines: "/src//{agent,command,security,validation}/**/.{py,rs}**: Input validation must be enforced in all security-critical paths using CommandValidator and SafeCommand types."
Also applies to: 347-375
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/scheduler.rs` around lines 60 - 96, The
create_schedule path embeds user input (ScheduleCreateCommand::name / cmd.name)
directly into systemd ExecStart which can break parsing or inject args; validate
and sanitize schedule names in create_schedule (and the corresponding
install-timer handler) by enforcing an allowlist (e.g., alphanumeric,
hyphen/underscore) via the project's CommandValidator/SafeCommand types, reject
or normalize invalid names, and when generating the unit file always pass the
name as a properly escaped/quoted single argument (or use SafeCommand escaping
utilities) rather than interpolating raw cmd.name into ExecStart.
| /// Install systemd timer for a schedule | ||
| fn install_timer(id: &str) -> Result<()> { | ||
| let db = SecurityDatabase::open()?; | ||
|
|
||
| let mut schedule = db.get_schedule(id)? | ||
| .ok_or_else(|| anyhow::anyhow!("Schedule '{}' not found", id))?; | ||
|
|
||
| println!("⏱️ Installing systemd timer for: {}", schedule.name); | ||
|
|
||
| // Determine if we need root (system-wide) or user timer | ||
| let (timer_dir, use_user_mode) = if is_root() { | ||
| (get_systemd_system_dir(), false) | ||
| } else { | ||
| let user_dir = get_systemd_user_dir(); | ||
| fs::create_dir_all(&user_dir)?; | ||
| (user_dir, true) | ||
| }; | ||
|
|
||
| let unit_name = format!("cx-security-{}", sanitize_name(&schedule.name)); | ||
|
|
||
| // Create service unit | ||
| let service_content = generate_service_unit(&schedule, use_user_mode); | ||
| let service_path = timer_dir.join(format!("{}.service", unit_name)); | ||
| fs::write(&service_path, service_content) | ||
| .context("Failed to write service unit")?; | ||
|
|
||
| // Create timer unit | ||
| let timer_content = generate_timer_unit(&schedule); | ||
| let timer_path = timer_dir.join(format!("{}.timer", unit_name)); | ||
| fs::write(&timer_path, timer_content) | ||
| .context("Failed to write timer unit")?; | ||
|
|
||
| // Reload systemd and enable timer | ||
| let systemctl_args: Vec<&str> = if use_user_mode { | ||
| vec!["--user"] | ||
| } else { | ||
| vec![] | ||
| }; | ||
|
|
||
| // Reload daemon | ||
| let mut reload_cmd = Command::new("systemctl"); | ||
| reload_cmd.args(&systemctl_args); | ||
| reload_cmd.arg("daemon-reload"); | ||
| reload_cmd.status().context("Failed to reload systemd")?; | ||
|
|
||
| // Enable and start timer | ||
| let mut enable_cmd = Command::new("systemctl"); | ||
| enable_cmd.args(&systemctl_args); | ||
| enable_cmd.args(["enable", "--now", &format!("{}.timer", unit_name)]); | ||
| let status = enable_cmd.status().context("Failed to enable timer")?; | ||
|
|
||
| if !status.success() { | ||
| anyhow::bail!("Failed to enable systemd timer"); | ||
| } | ||
|
|
||
| // Update schedule | ||
| schedule.timer_installed = true; | ||
| db.save_schedule(&schedule)?; | ||
|
|
||
| println!("✅ Timer installed and started"); | ||
| println!(); | ||
| println!(" Service: {}.service", unit_name); | ||
| println!(" Timer: {}.timer", unit_name); | ||
| println!(); | ||
|
|
||
| // Show timer status | ||
| let mut status_cmd = Command::new("systemctl"); | ||
| status_cmd.args(&systemctl_args); | ||
| status_cmd.args(["status", &format!("{}.timer", unit_name)]); | ||
| let _ = status_cmd.status(); |
There was a problem hiding this comment.
Route external commands through CommandValidator/SafeCommand.
This module executes systemctl, notify-send, and wall in a security-critical path, with user-derived schedule names. The guidelines require SafeCommand-based validation for such invocations.
As per coding guidelines: "/src//{agent,command,security,validation}/**/.{py,rs}**: Input validation must be enforced in all security-critical paths using CommandValidator and SafeCommand types."
Also applies to: 300-336, 418-441
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/scheduler.rs` around lines 214 - 283, The
install_timer function currently invokes external programs directly via Command
(e.g., reload_cmd, enable_cmd, status_cmd calling "systemctl") using
user-derived unit_name/schedule.name; update these invocations to route through
the project's CommandValidator/SafeCommand API instead of std::process::Command:
replace all Command::new("systemctl") usages in install_timer with the
SafeCommand construction/validation pattern (validate the executable and each
arg via CommandValidator), ensure unit_name (even if sanitized by sanitize_name)
is validated by SafeCommand before being used, and apply the same change for
other similar call sites mentioned (notify-send, wall in the other ranges) so
every security-critical external call uses CommandValidator/SafeCommand rather
than raw Command.
| /// Truncate string to max length | ||
| fn truncate(s: &str, max_len: usize) -> String { | ||
| if s.len() <= max_len { | ||
| s.to_string() | ||
| } else { | ||
| format!("{}...", &s[..max_len.saturating_sub(3)]) | ||
| } |
There was a problem hiding this comment.
Make truncate UTF‑8 safe to avoid panics.
Byte slicing (&s[..]) can panic on multi‑byte characters. Use chars().take(...) (or unicode-segmentation) instead.
🔧 Safer truncation
fn truncate(s: &str, max_len: usize) -> String {
- if s.len() <= max_len {
- s.to_string()
- } else {
- format!("{}...", &s[..max_len.saturating_sub(3)])
- }
+ if s.chars().count() <= max_len {
+ s.to_string()
+ } else {
+ let take = max_len.saturating_sub(3);
+ let prefix: String = s.chars().take(take).collect();
+ format!("{}...", prefix)
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// Truncate string to max length | |
| fn truncate(s: &str, max_len: usize) -> String { | |
| if s.len() <= max_len { | |
| s.to_string() | |
| } else { | |
| format!("{}...", &s[..max_len.saturating_sub(3)]) | |
| } | |
| /// Truncate string to max length | |
| fn truncate(s: &str, max_len: usize) -> String { | |
| if s.chars().count() <= max_len { | |
| s.to_string() | |
| } else { | |
| let take = max_len.saturating_sub(3); | |
| let prefix: String = s.chars().take(take).collect(); | |
| format!("{}...", prefix) | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@wezterm/src/cli/security/scheduler.rs` around lines 458 - 464, The truncate
function currently slices bytes and can panic on multi-byte UTF‑8; update
truncate to operate on Unicode scalar values instead: in fn truncate(s: &str,
max_len: usize) check character count with s.chars().count() (or compare by
collecting as needed) and when truncation is required build the prefix using
s.chars().take(max_len.saturating_sub(3)).collect::<String>() (handle small
max_len via saturating_sub) then append "..." — replace the byte-slice usage in
truncate with this char-safe approach.
Introduces three specialized agents to enforce Sovereign build standards: 1. cx-linux-architect - 60fps performance enforcement - UI symmetry standards (max-w-md mx-auto) - Brand guardrails (reject Cortex/Alex naming) - Memory hygiene for async operations 2. cx-linux-devops - Repository synchronization (cxlinux-ai/* only) - Port management and zombie process detection - Branch integrity (5-tab core verification) - Build validation (clean dist, no legacy refs) 3. cx-linux-security-auditor - BSL 1.1 license header enforcement - OWASP security standards - Secret leak prevention - Agent Fleet telemetry integrity (101 agents) These agents prevent the regressions and cross-contamination issues that caused 5-hour delays in previous development cycles. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @.github/copilot-agents/cx-linux-devops.md:
- Around line 93-101: Add a blank line before and after the "Core Branches"
markdown table so it is separated from surrounding content; locate the table
that starts with the "### Core Branches" heading and the pipe-delimited rows
(including `main`, `develop`, `navigation-update`, `feature/*`) and insert one
empty line above the first table row and one empty line between the final table
row and the subsequent "### 5-Tab Core Verification" heading to satisfy
markdownlint.
- Around line 279-288: The "Common Issues & Fixes" markdown table (heading "###
Common Issues & Fixes" and the following table rows) needs blank lines inserted
before the table and after the trailing horizontal rule to satisfy markdownlint
table spacing; edit the block so there is an empty line between the heading and
the table start and another empty line after the table/end-of-block (the line
with ---) to ensure proper spacing without changing table content.
In @.github/copilot-agents/cx-linux-security-auditor.md:
- Line 337: The grep pattern used in the pre-commit check (the command grep -rn
"sk_live_\|AKIA" --include="*.ts" .) is missing the GitHub token prefix ghp_
that your CI/CD workflow checks for; update that grep pattern to include ghp_
(e.g., add \|ghp_ into the quoted pattern) so the pre-commit hook and CI/CD
secret scan use the same set of token prefixes.
- Around line 343-347: The pre-commit hook uses a piped pipeline with `git diff
--cached --name-only --diff-filter=A | ... | while read f; do ... exit 1; done`
so the `exit 1` runs in the subshell and won't stop the parent script; change
the logic to either (A) run the loop via process substitution (e.g. `while read
f; do ...; done < <(git diff --cached --name-only --diff-filter=A | grep -E
"\.(ts|tsx|rs)$")`) so `exit 1` can terminate the main shell, or (B) collect
violations into a variable (e.g. append missing filenames inside the `while read
f` loop) and after the loop check the variable and call `exit 1` if non-empty;
update the `while read f` block and the `exit 1` placement accordingly so
missing BSL headers are enforced.
🧹 Nitpick comments (1)
.github/copilot-agents/cx-linux-security-auditor.md (1)
1-380: Consider addressing markdown linting issues.Static analysis identified several formatting inconsistencies that could be cleaned up:
- Line 14: Fenced code block missing language specifier (should be
```bash)- Lines 318, 73, 85, 173: Tables should be surrounded by blank lines per MD058
These are style issues that don't affect functionality but improve consistency with markdown best practices.
| ### Core Branches | ||
| | Branch | Purpose | Protection | | ||
| |--------|---------|------------| | ||
| | `main` | Production | Protected, requires PR | | ||
| | `develop` | Integration | Protected | | ||
| | `navigation-update` | 5-tab core (fbeaf48e) | Feature branch | | ||
| | `feature/*` | New features | Ephemeral | | ||
|
|
||
| ### 5-Tab Core Verification |
There was a problem hiding this comment.
Add blank lines around the “Core Branches” table.
Markdownlint flags missing blank lines around tables here.
🔧 Suggested fix
### Core Branches
+
| Branch | Purpose | Protection |
|--------|---------|------------|
| `main` | Production | Protected, requires PR |
| `develop` | Integration | Protected |
| `navigation-update` | 5-tab core (fbeaf48e) | Feature branch |
| `feature/*` | New features | Ephemeral |
+
### 5-Tab Core Verification📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### Core Branches | |
| | Branch | Purpose | Protection | | |
| |--------|---------|------------| | |
| | `main` | Production | Protected, requires PR | | |
| | `develop` | Integration | Protected | | |
| | `navigation-update` | 5-tab core (fbeaf48e) | Feature branch | | |
| | `feature/*` | New features | Ephemeral | | |
| ### 5-Tab Core Verification | |
| ### Core Branches | |
| | Branch | Purpose | Protection | | |
| |--------|---------|------------| | |
| | `main` | Production | Protected, requires PR | | |
| | `develop` | Integration | Protected | | |
| | `navigation-update` | 5-tab core (fbeaf48e) | Feature branch | | |
| | `feature/*` | New features | Ephemeral | | |
| ### 5-Tab Core Verification |
🤖 Prompt for AI Agents
In @.github/copilot-agents/cx-linux-devops.md around lines 93 - 101, Add a blank
line before and after the "Core Branches" markdown table so it is separated from
surrounding content; locate the table that starts with the "### Core Branches"
heading and the pipe-delimited rows (including `main`, `develop`,
`navigation-update`, `feature/*`) and insert one empty line above the first
table row and one empty line between the final table row and the subsequent "###
5-Tab Core Verification" heading to satisfy markdownlint.
| ### Common Issues & Fixes | ||
|
|
||
| | Issue | Symptom | Fix | | ||
| |-------|---------|-----| | ||
| | Split identity | Wrong remote URL | `git remote set-url origin https://github.com/cxlinux-ai/cx-web.git` | | ||
| | Port blocked | "Address in use" | `lsof -ti :PORT \| xargs kill -9` | | ||
| | Stale cache | Old assets in build | `rm -rf dist node_modules/.cache` | | ||
| | Wrong branch | Missing features | `git checkout main && git pull` | | ||
|
|
||
| --- |
There was a problem hiding this comment.
Add blank lines around the “Common Issues & Fixes” table.
This will satisfy markdownlint’s table spacing rule.
🔧 Suggested fix
### Common Issues & Fixes
+
| Issue | Symptom | Fix |
|-------|---------|-----|
| Split identity | Wrong remote URL | `git remote set-url origin https://github.com/cxlinux-ai/cx-web.git` |
| Port blocked | "Address in use" | `lsof -ti :PORT \| xargs kill -9` |
| Stale cache | Old assets in build | `rm -rf dist node_modules/.cache` |
| Wrong branch | Missing features | `git checkout main && git pull` |
+
---🤖 Prompt for AI Agents
In @.github/copilot-agents/cx-linux-devops.md around lines 279 - 288, The
"Common Issues & Fixes" markdown table (heading "### Common Issues & Fixes" and
the following table rows) needs blank lines inserted before the table and after
the trailing horizontal rule to satisfy markdownlint table spacing; edit the
block so there is an empty line between the heading and the table start and
another empty line after the table/end-of-block (the line with ---) to ensure
proper spacing without changing table content.
| . "$(dirname "$0")/_/husky.sh" | ||
|
|
||
| # Check for secrets | ||
| if grep -rn "sk_live_\|AKIA" --include="*.ts" .; then |
There was a problem hiding this comment.
Inconsistent secret patterns between pre-commit and CI/CD.
The pre-commit hook checks for sk_live_\|AKIA (line 337), but the CI/CD workflow adds ghp_ for GitHub tokens (line 368). GitHub personal access tokens could bypass the pre-commit hook.
🔧 Align patterns across hooks
# Check for secrets
-if grep -rn "sk_live_\|AKIA" --include="*.ts" .; then
+if grep -rn "sk_live_\|AKIA\|ghp_" --include="*.ts" .; then
echo "❌ Potential secret detected!"
exit 1
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if grep -rn "sk_live_\|AKIA" --include="*.ts" .; then | |
| if grep -rn "sk_live_\|AKIA\|ghp_" --include="*.ts" .; then |
🤖 Prompt for AI Agents
In @.github/copilot-agents/cx-linux-security-auditor.md at line 337, The grep
pattern used in the pre-commit check (the command grep -rn "sk_live_\|AKIA"
--include="*.ts" .) is missing the GitHub token prefix ghp_ that your CI/CD
workflow checks for; update that grep pattern to include ghp_ (e.g., add \|ghp_
into the quoted pattern) so the pre-commit hook and CI/CD secret scan use the
same set of token prefixes.
| git diff --cached --name-only --diff-filter=A | grep -E "\.(ts|tsx|rs)$" | while read f; do | ||
| if ! grep -q "Business Source License 1.1" "$f"; then | ||
| echo "❌ Missing BSL 1.1 header in $f" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Critical: Pre-commit hook logic will not prevent commits.
The exit 1 statement inside the while read loop runs in a subshell (created by the pipe) and will not terminate the parent script. Files missing BSL headers will not block the commit.
🔧 Proposed fix using process substitution
# Check for BSL header in new files
-git diff --cached --name-only --diff-filter=A | grep -E "\.(ts|tsx|rs)$" | while read f; do
+while read f; do
if ! grep -q "Business Source License 1.1" "$f"; then
echo "❌ Missing BSL 1.1 header in $f"
exit 1
fi
-done
+done < <(git diff --cached --name-only --diff-filter=A | grep -E "\.(ts|tsx|rs)$")Alternatively, collect violations first:
missing_files=$(git diff --cached --name-only --diff-filter=A | grep -E "\.(ts|tsx|rs)$" | while read f; do
if ! grep -q "Business Source License 1.1" "$f"; then
echo "$f"
fi
done)
if [ -n "$missing_files" ]; then
echo "❌ Missing BSL 1.1 header in:"
echo "$missing_files"
exit 1
fi🤖 Prompt for AI Agents
In @.github/copilot-agents/cx-linux-security-auditor.md around lines 343 - 347,
The pre-commit hook uses a piped pipeline with `git diff --cached --name-only
--diff-filter=A | ... | while read f; do ... exit 1; done` so the `exit 1` runs
in the subshell and won't stop the parent script; change the logic to either (A)
run the loop via process substitution (e.g. `while read f; do ...; done < <(git
diff --cached --name-only --diff-filter=A | grep -E "\.(ts|tsx|rs)$")`) so `exit
1` can terminate the main shell, or (B) collect violations into a variable (e.g.
append missing filenames inside the `while read f` loop) and after the loop
check the variable and call `exit 1` if non-empty; update the `while read f`
block and the `exit 1` placement accordingly so missing BSL headers are
enforced.
|
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@mikejmorgan-ai I've opened a new pull request, #689, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
|
||
| /// Generate systemd service unit content | ||
| fn generate_service_unit(schedule: &Schedule, use_user_mode: bool) -> String { | ||
| let mut args = vec!["security", "schedule", "run", &schedule.name]; |
There was a problem hiding this comment.
The args variable is created but never used in the service unit generation. The exec arguments should be formatted using this variable, but line 372 uses args.join(\" \") where args only contains the command structure. The correct implementation should pass the schedule.name as an argument, but this appears to be unused dead code.
|
|
||
| /// Check if running as root | ||
| fn is_root() -> bool { | ||
| unsafe { libc::geteuid() == 0 } |
There was a problem hiding this comment.
Using unsafe to call libc::geteuid() is unnecessary when safer alternatives exist. Consider using the nix crate's geteuid() function which provides a safe wrapper, or add proper documentation explaining why unsafe is required here.
| unsafe { libc::geteuid() == 0 } | |
| nix::unistd::geteuid().is_root() |
| let _ = Command::new("apt-get") | ||
| .args(["update", "-qq"]) | ||
| .output(); |
There was a problem hiding this comment.
The apt-get update command is run without checking for errors (result is discarded with let _). If the update fails, subsequent operations may work with stale package information, potentially installing incorrect package versions or missing security updates. Consider logging the error or handling the failure case explicitly.
| // Check cache first | ||
| let cache_key = format!("{}:{}", package.name, package.version); | ||
| if let Some(cached) = cache.get(&cache_key) { | ||
| if cached.timestamp.elapsed().unwrap_or(Duration::MAX) < Duration::from_secs(CACHE_DURATION_SECS) { |
There was a problem hiding this comment.
Using unwrap_or(Duration::MAX) when elapsed() fails will cause cached entries to always appear expired (since Duration::MAX is never less than CACHE_DURATION_SECS). This defeats the caching mechanism. Consider using unwrap_or(Duration::ZERO) to treat errors as expired cache entries, or handle the error explicitly.
| if cached.timestamp.elapsed().unwrap_or(Duration::MAX) < Duration::from_secs(CACHE_DURATION_SECS) { | |
| if cached.timestamp.elapsed().unwrap_or(Duration::ZERO) < Duration::from_secs(CACHE_DURATION_SECS) { |
| where | ||
| S: Serializer, | ||
| { | ||
| let duration = time.duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO); |
There was a problem hiding this comment.
Using unwrap_or(Duration::ZERO) when duration_since fails means times before Unix epoch will serialize as epoch time (0), which could lead to incorrect cache behavior. Consider handling this error case more explicitly or using a Result type for serialization.
| let duration = time.duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO); | |
| let duration = time | |
| .duration_since(UNIX_EPOCH) | |
| .map_err(serde::ser::Error::custom)?; |



Summary
Implements autonomous security vulnerability scanning and patching for CX Linux, addressing issue #422.
This PR adds a comprehensive security module that:
Features
Vulnerability Scanner
/var/lib/dpkg/statusfor installed packagesAutonomous Patcher
critical_only,high_and_above,all,automaticSecurity Scheduler
CLI Commands
Example Output
Technical Implementation
security/mod.rssecurity/scanner.rssecurity/patcher.rssecurity/scheduler.rssecurity/database.rsDependencies Added
rusqlite- SQLite databaseureq- Lightweight HTTP client for OSV APIuuid- Unique IDs for recordsdirs- Cross-platform directory pathsTesting
Acceptance Criteria from #422
cx security scan --allscans installed packages against CVE databasescx security scan --package <name>scans specific packagecx security scan --criticalshows only critical vulnerabilitiescx security patch --scan-and-patchcreates patch plan (dry-run)cx security patch --scan-and-patch --applyapplies patchescx security schedule createcreates automated schedulescx security schedule listlists all schedulescx security schedule run <id>manually runs a schedulecx security schedule install-timerinstalls systemd timerCloses #422
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.