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
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.17.1] - 2026-10-09

### Fixed

- **Hardcoded secrets in Go short declarations, YAML/JSON keys and SQL `PASSWORD` literals are now detected.** Go `password := "..."` (with or without a trailing comment) and `var apiKey string = "..."` (#618); unquoted YAML `password: hunter2hunter2` and JSON quoted keys `"password": "..."` / `"apiKey": "..."` (#619); SQL `CREATE USER app WITH PASSWORD '<literal>'` and `IDENTIFIED BY '<literal>'` (#620). References stay quiet: `${VAR}`/`$VAR`, `!tag`, `*alias`, `&anchor`, paths, `null`/`required`-style bare words, and SQL placeholders (`PASSWORD '%s'`). The LLM secret cross-check uses the same pattern, so LLM findings on these shapes are no longer dropped.

### Changed

- **One shared post-processing module for `cora review` and `cora scan`.** The finding filter pipeline (LLM secret false-positive cross-check, Markdown code blocks, `ignore.rules`, inline `cora-ignore:`, context-line filter, scanner merge for scans) now lives in `engine::postprocess`, so a fix lands in one place. Internal refactor with no CLI or output change (#610).

- **`ReviewIssue::new` constructor and table-driven hardcoded-secret tests.** `ReviewIssue` is now built through `ReviewIssue::new(..)` plus `with_*` setters, so adding a field touches one place instead of every construction site. One end-to-end table covers the hardcoded-secret heuristics across languages; known false positives/negatives are pinned in a separate table. Internal change with no CLI or output change (#610).

## [0.17.0] - 2026-10-09

### Fixed
Expand Down Expand Up @@ -968,7 +980,8 @@ Benchmarked on the cora-code repository (1,864 symbols, 115 Rust files, x86_64):
- **Cross-platform** — Linux (x86_64, ARM64), macOS (Apple Silicon), Windows (x86_64)
- **MIT License** — fully open source

[Unreleased]: https://github.com/codecoradev/cora-code/compare/v0.17.0...develop
[Unreleased]: https://github.com/codecoradev/cora-code/compare/v0.17.1...develop
[0.17.1]: https://github.com/codecoradev/cora-code/compare/v0.17.0...v0.17.1
[0.17.0]: https://github.com/codecoradev/cora-code/compare/v0.16.1...v0.17.0
[0.16.1]: https://github.com/codecoradev/cora-code/compare/v0.16.0...v0.16.1
[0.16.0]: https://github.com/codecoradev/cora-code/compare/v0.15.0...v0.16.0
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cora-code"
version = "0.17.0"
version = "0.17.1"
edition = "2024"
description = "CLI-first AI code review — BYOK, diff/scan/branch, pre-commit hooks"
license = "Apache-2.0"
Expand Down
14 changes: 3 additions & 11 deletions src/commands/review.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,17 +792,9 @@ mod tests {
use crate::engine::types::ReviewIssue;

fn issue(severity: Severity) -> ReviewIssue {
ReviewIssue {
rule_id: None,
also_matches: Vec::new(),
file: "src/main.rs".to_string(),
line: Some(1),
severity,
issue_type: Some("bug".to_string()),
title: "test".to_string(),
body: "body".to_string(),
suggested_fix: None,
}
ReviewIssue::new("src/main.rs", Some(1), severity, "test")
.with_type("bug")
.with_body("body")
}

fn response(issues: Vec<ReviewIssue>, should_block: bool) -> ReviewResponse {
Expand Down
22 changes: 4 additions & 18 deletions src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ use colored::Colorize;
use tracing::debug;

use crate::config::schema::Config;
use crate::engine::review::apply_ignore_rules;
use crate::engine::ReviewIssue;
use crate::engine::postprocess::{Source, postprocess};
use crate::engine::review_store;
use crate::engine::scanner::{
FileEntry, batch_files, files_as_chunks, format_batch_for_prompt, walk_project,
};
use crate::engine::scanner::{FileEntry, batch_files, format_batch_for_prompt, walk_project};
use crate::engine::types::TokenUsage;
use crate::engine::{ReviewIssue, inline_suppress, rules, secrets_scanner, security_scanner};
use crate::formatters::{OutputFormat, formatter_for};

/// Default maximum files per LLM batch when `--batch-files` is not specified.
Expand Down Expand Up @@ -345,19 +343,7 @@ fn finalize_issues(
files: &[FileEntry],
issues: Vec<ReviewIssue>,
) -> Vec<ReviewIssue> {
let chunks = files_as_chunks(files);
let max = config.rules_config.max_findings;
let mut merged = issues;
for family in [
secrets_scanner::scan_secrets(&chunks, max),
security_scanner::scan_security(&chunks, max),
] {
if !family.is_empty() {
merged = rules::merge_rule_findings(merged, family);
}
}
let merged = apply_ignore_rules(merged, &config.ignore.rules);
inline_suppress::apply(merged, &chunks)
postprocess(issues, &Source::Files(files), config)
}

/// Compute a short SHA256 hash of a file's content for incremental scanning.
Expand Down
17 changes: 6 additions & 11 deletions src/engine/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,12 @@ mod tests {

fn make_response() -> ReviewResponse {
ReviewResponse {
issues: vec![ReviewIssue {
rule_id: None,
also_matches: Vec::new(),
file: "src/main.rs".to_string(),
line: Some(10),
severity: Severity::Major,
issue_type: Some("bugs".to_string()),
title: "Null pointer".to_string(),
body: "Could be null here".to_string(),
suggested_fix: Some("Add a check".to_string()),
}],
issues: vec![
ReviewIssue::new("src/main.rs", Some(10), Severity::Major, "Null pointer")
.with_type("bugs")
.with_body("Could be null here")
.with_fix("Add a check"),
],
summary: "Found 1 issue.".to_string(),
tokens_used: None,
should_block: false,
Expand Down
13 changes: 2 additions & 11 deletions src/engine/debt_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,17 +703,8 @@ mod tests {
use super::*;

fn make_issue(severity: Severity, issue_type: &str) -> ReviewIssue {
ReviewIssue {
rule_id: None,
also_matches: Vec::new(),
file: "test.rs".to_string(),
line: Some(1),
severity,
issue_type: Some(issue_type.to_string()),
title: "test issue".to_string(),
body: String::new(),
suggested_fix: None,
}
ReviewIssue::new("test.rs", Some(1), severity, "test issue")
.with_type(issue_type.to_string())
}

// ─── normalize_category ───
Expand Down
17 changes: 6 additions & 11 deletions src/engine/deterministic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,17 +279,12 @@ diff --git a/src/api.js b/src/api.js
let report = run(&chunks, &Config::default(), &fixture_bridge());
let total = report.len();
let first = report.secrets[0].clone();
let llm = vec![ReviewIssue {
rule_id: None,
also_matches: Vec::new(),
file: first.file.clone(),
line: Some(first.line),
severity: crate::engine::Severity::Major,
issue_type: None,
title: "Hardcoded secret committed to the repository".into(),
body: String::new(),
suggested_fix: None,
}];
let llm = vec![ReviewIssue::new(
first.file.clone(),
Some(first.line),
crate::engine::Severity::Major,
"Hardcoded secret committed to the repository",
)];
let merged = report.merge_into(llm);
assert_eq!(
merged[0].title,
Expand Down
48 changes: 24 additions & 24 deletions src/engine/index_scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,21 +390,21 @@ pub fn scan_project_index(
match graph::find_unused_imports(conn, &entry.path, project_id) {
Ok(unused) => {
for u in &unused {
findings.push(ReviewIssue {
rule_id: Some("index-unused-import".into()),
also_matches: Vec::new(),
file: u.file.clone(),
line: Some(u.line),
severity: crate::engine::Severity::Minor,
issue_type: Some("index".into()),
title: format!("[index-unused-import] Unused import: {}", u.target),
body: format!(
findings.push(
ReviewIssue::new(
u.file.clone(),
Some(u.line),
crate::engine::Severity::Minor,
format!("[index-unused-import] Unused import: {}", u.target),
)
.with_type("index")
.with_body(format!(
"Import `{}` is never used in this file. \
Consider removing it to keep imports clean.",
u.target
),
suggested_fix: None,
});
))
.with_rule_id("index-unused-import"),
);
}
}
Err(e) => {
Expand All @@ -426,22 +426,22 @@ pub fn scan_project_index(
match graph::find_dead_code(conn, project_id, &opts) {
Ok(dead) => {
for func in dead.into_iter().take(max_findings - findings.len()) {
findings.push(ReviewIssue {
rule_id: Some("index-dead-code".into()),
also_matches: Vec::new(),
file: func.file.clone(),
line: Some(func.line),
severity: crate::engine::Severity::Info,
issue_type: Some("index".into()),
title: format!("[index-dead-code] Potentially dead code: {}", func.name),
body: format!(
findings.push(
ReviewIssue::new(
func.file.clone(),
Some(func.line),
crate::engine::Severity::Info,
format!("[index-dead-code] Potentially dead code: {}", func.name),
)
.with_type("index")
.with_body(format!(
"Function `{}` ({}) has no callers in the \
project. Verify it's not called via reflection, \
trait dispatch, or external entry points.",
func.name, func.kind
),
suggested_fix: None,
});
))
.with_rule_id("index-dead-code"),
);
}
}
Err(e) => {
Expand Down
18 changes: 7 additions & 11 deletions src/engine/inline_suppress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,13 @@ mod tests {
const RULE: &str = "Hardcoded password or secret in variable";

fn issue(file: &str, line: u32, title: &str) -> ReviewIssue {
ReviewIssue {
rule_id: None,
also_matches: Vec::new(),
file: file.to_string(),
line: Some(line),
severity: Severity::Major,
issue_type: Some("rule".to_string()),
title: title.to_string(),
body: String::new(),
suggested_fix: None,
}
ReviewIssue::new(
file.to_string(),
Some(line),
Severity::Major,
title.to_string(),
)
.with_type("rule")
}

/// Diff adding `lines` to `f.rs` starting at line 1.
Expand Down
3 changes: 3 additions & 0 deletions src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ pub mod llm;
pub mod markdown;
pub mod memory;
pub mod path_match;
pub mod postprocess;
pub mod profiles;
pub mod quality_gate;
pub mod review;
pub mod review_store;
pub mod rules;
pub mod scanner;
#[cfg(test)]
mod secret_heuristics_tests;
pub mod secret_patterns;
pub mod secrets_scanner;
pub mod security_scanner;
Expand Down
Loading
Loading