Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ jobs:
- name: List artifacts
run: ls -la release-artifacts/

- name: Generate SHA256 checksums
run: |
cd release-artifacts
sha256sum * > checksums-sha256.txt
cat checksums-sha256.txt

- name: Extract version from tag
id: version
run: |
Expand Down
4 changes: 2 additions & 2 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ src/
│ ├── mod.rs
│ ├── review.rs # cora review (diff-based review)
│ ├── scan.rs # cora scan (full-file scan)
│ ├── config_cmd.rs # cora config (show/validate)
│ ├── config_cmd.rs # cora config (show/set)
│ ├── auth.rs # cora auth (API key management)
│ ├── hook_cmd.rs # cora hook (pre-commit hook install/uninstall)
│ ├── init.rs # cora init (project scaffolding)
Expand Down Expand Up @@ -67,7 +67,7 @@ src/

| File | Purpose |
|---|---|
| `commands/config_cmd.rs` | Config subcommand — display, validate, path resolution |
| `commands/config_cmd.rs` | Config subcommand — display, set, path resolution |
| `config/loader.rs` | Config loading with full priority chain resolution |
| `config/schema.rs` | All config structs, defaults, serde annotations |
| `commands/init.rs` | Project scaffolding, `.cora.yaml` generation |
Expand Down
28 changes: 15 additions & 13 deletions src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ pub async fn execute_scan(
let root_abs = root.canonicalize().unwrap_or_else(|_| root.clone());
files.retain(|f| {
let abs_path = root_abs.join(&f.path);
let hash = file_content_hash(&abs_path);
let hash = match file_content_hash(&abs_path) {
Some(h) => h,
None => return true, // can't read file, rescan it
};
match cache.get(&root_abs, &f.path) {
Some(cached_hash) if cached_hash == hash => {
debug!(file = %f.path, "skipping unchanged file (incremental)");
Expand Down Expand Up @@ -160,7 +163,10 @@ pub async fn execute_scan(
let mut cache = ScanCache::load().unwrap_or_default();
for f in &files {
let abs_path = root_abs.join(&f.path);
let hash = file_content_hash(&abs_path);
let hash = match file_content_hash(&abs_path) {
Some(h) => h,
None => continue, // can't read file, skip cache entry
};
cache.set(&root_abs, &f.path, &hash);
}
cache.save()?;
Expand All @@ -175,17 +181,13 @@ pub async fn execute_scan(
}

/// Compute a short SHA256 hash of a file's content for incremental scanning.
fn file_content_hash(path: &std::path::Path) -> String {
match std::fs::read(path) {
Ok(bytes) => {
use std::hash::{Hash, Hasher};
// Use a fast non-crypto hash — we just need change detection, not security.
let mut hasher = std::collections::hash_map::DefaultHasher::new();
bytes.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
Err(_) => String::new(),
}
/// Returns None if the file cannot be read (caller should rescan it).
fn file_content_hash(path: &std::path::Path) -> Option<String> {
use sha2::Digest;
let bytes = std::fs::read(path).ok()?;
let hash = sha2::Sha256::digest(&bytes);
// Use first 8 bytes as hex — consistent representation, no truncation
Some(hash.iter().take(8).map(|b| format!("{b:02x}")).collect())
}

/// Cache of file content hashes for incremental scanning.
Expand Down
11 changes: 11 additions & 0 deletions src/commands/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ pub async fn execute_upload(opts: &UploadOptions) -> Result<i32> {
// Read SARIF content
let sarif_content = read_sarif(opts.file.as_deref())?;

// Validate file size (GitHub limits SARIF uploads to 10MB)
const MAX_SARIF_SIZE: usize = 10 * 1024 * 1024; // 10MB
if sarif_content.len() > MAX_SARIF_SIZE {
let size_mb = sarif_content.len() as f64 / (1024.0 * 1024.0);
bail!(
"SARIF file is {} bytes ({:.2}MB), exceeding GitHub's 10MB limit. Consider reviewing a smaller diff.",
sarif_content.len(),
size_mb
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Validate it's valid JSON (basic sanity check)
let parsed: serde_json::Value =
serde_json::from_str(&sarif_content).context("SARIF file does not contain valid JSON")?;
Expand Down
16 changes: 16 additions & 0 deletions src/config/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,22 @@ pub fn load_api_key_from_auth_file() -> Result<Option<String>> {
return Ok(None);
}

// Security: warn if auth file has overly permissive permissions (Unix only)
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = std::fs::metadata(&path) {
let mode = meta.permissions().mode();
if mode & 0o077 != 0 {
tracing::warn!(
"auth file has overly permissive permissions ({:o}). Run: chmod 600 {}",
mode & 0o777,
path.display()
);
}
}
}

let content = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read {}", path.display()))?;

Expand Down
2 changes: 1 addition & 1 deletion src/engine/llm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ fn create_spinner(message: &str) -> ProgressBar {
spinner.enable_steady_tick(std::time::Duration::from_millis(80));
spinner.set_style(
ProgressStyle::with_template("{spinner:.cyan} {msg}")
.unwrap()
.expect("valid spinner template")
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
);
spinner.set_message(message.to_string());
Expand Down
5 changes: 4 additions & 1 deletion src/engine/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ pub fn walk_project(
// Read file content (skip binary / unreadable)
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => continue,
Err(e) => {
debug!(file = %relative, error = %e, "skipping unreadable file");
continue;
}
};

// Skip empty files
Expand Down
24 changes: 11 additions & 13 deletions src/engine/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,7 @@ impl Severity {
_ => Severity::Info,
}
}
}

impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Severity::Critical => write!(f, "critical"),
Severity::Major => write!(f, "major"),
Severity::Minor => write!(f, "minor"),
Severity::Info => write!(f, "info"),
}
}
}

impl Severity {
/// Get the label text for this severity.
pub fn label(&self) -> &'static str {
match self {
Expand All @@ -56,6 +43,17 @@ impl Severity {
}
}

impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Severity::Critical => write!(f, "critical"),
Severity::Major => write!(f, "major"),
Severity::Minor => write!(f, "minor"),
Severity::Info => write!(f, "info"),
}
}
}

/// Issue type categories
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down
Loading