Python fp and docs updtes (#58)

* refactor: Update comments for clarity and add expectations.json files for performance metrics

* feat: Implement FP guard for JS/TS local-collection receivers to suppress missing ownership checks

* feat: Enhance Rust parameter handling to classify local collections and prevent false ownership checks

* refactor: Simplify code formatting for better readability in multiple files

* refactor: Improve UTF-8 sequence length handling and enhance clarity in loop iteration

* feat: Update Java and Python patterns to include new security rules

* refactor: Improve comment clarity and consistency across multiple Rust files

* refactor: Simplify code formatting for improved readability in integration tests and module files

* refactor: Improve comment formatting and enhance clarity in assertions across multiple files
This commit is contained in:
Eli Peter 2026-04-29 19:53:34 -04:00 committed by GitHub
parent 4db0805de6
commit a438886217
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
291 changed files with 9485 additions and 3851 deletions

View file

@ -72,7 +72,7 @@ pub struct AppState {
pub findings_cache: Arc<RwLock<Option<CachedFindings>>>,
}
/// 50 MiB cap on request bodies generous for config uploads, tight
/// 50 MiB cap on request bodies, generous for config uploads, tight
/// enough to prevent OOM from a rogue client.
const MAX_BODY_BYTES: usize = 50 * 1024 * 1024;
@ -286,7 +286,7 @@ mod tests {
}
/// Panic inside a thread that holds a write guard on the shared config lock.
/// With `parking_lot::RwLock`, the lock must remain usable afterwards
/// With `parking_lot::RwLock`, the lock must remain usable afterwards ,
/// this is the poison-recovery contract we rely on in every route handler.
#[tokio::test]
async fn config_lock_survives_panic_in_write_guard() {

View file

@ -782,7 +782,7 @@ pub struct FuncSummaryView {
/// Enclosing container path (class / impl / module / outer function).
/// Empty for free top-level functions.
pub container: String,
/// Structural [`crate::symbol::FuncKind`] slug `"fn"`, `"method"`,
/// Structural [`crate::symbol::FuncKind`] slug, `"fn"`, `"method"`,
/// `"closure"`, etc. Lets the UI distinguish anonymous closures from
/// named functions for filtering.
pub func_kind: String,
@ -934,10 +934,10 @@ pub struct PointerView {
pub locations: Vec<PointerLocationView>,
pub values: Vec<PointerValueView>,
/// Field reads attributed to params/receiver via the field-points-to
/// extractor (Phase 5).
/// extractor.
pub field_reads: Vec<PointerFieldEntryView>,
/// Field writes attributed to params/receiver via the field-points-to
/// extractor (Phase 5).
/// extractor.
pub field_writes: Vec<PointerFieldEntryView>,
/// Number of distinct interned locations beyond the reserved Top sentinel.
pub location_count: usize,
@ -998,7 +998,7 @@ impl PointerView {
});
}
// Per-value pt sets emit only values with non-empty sets to keep
// Per-value pt sets, emit only values with non-empty sets to keep
// the payload focused on interesting facts.
let mut values: Vec<PointerValueView> = Vec::new();
for v in 0..ssa.num_values() as u32 {
@ -1064,12 +1064,12 @@ pub struct TypeFactDetailView {
pub ssa_value: u32,
pub var_name: Option<String>,
pub line: usize,
/// Type kind tag matches the [`TypeKind`] discriminant
/// Type kind tag, matches the [`TypeKind`] discriminant
/// (`String`, `Int`, `HttpClient`, `Dto`, …).
pub kind: String,
/// True when the value is allowed to be null/None.
pub nullable: bool,
/// Container/class name set for `HttpClient`, `DatabaseConnection`,
/// Container/class name, set for `HttpClient`, `DatabaseConnection`,
/// `Dto`, etc. Mirrors [`TypeKind::container_name`].
#[serde(skip_serializing_if = "Option::is_none")]
pub container: Option<String>,
@ -1437,7 +1437,7 @@ pub fn function_list(analysis: &FileAnalysis) -> Vec<FunctionInfo> {
/// Lower a single function to SSA and optimize it.
///
/// Returns the per-function body graph alongside the SSA. SSA is lowered
/// against `body.graph`, whose `NodeIndex` space is body-local the file's
/// against `body.graph`, whose `NodeIndex` space is body-local, the file's
/// top-level CFG (`analysis.cfg()`) has a different index space, so any
/// downstream analysis that indexes by `inst.cfg_node` must use the returned
/// `&Cfg`, not `analysis.cfg()`.
@ -1638,7 +1638,7 @@ pub fn analyse_file_summaries(
/// Run the file-level authorization extraction pipeline for the debug UI.
///
/// Returns the structured `AuthorizationModel` (routes, units, sensitive
/// operations, auth checks) plus the file bytes and an `enabled` flag
/// operations, auth checks) plus the file bytes and an `enabled` flag ,
/// the bytes drive line-number resolution in the view, and `enabled`
/// surfaces "auth analysis is off for this language" without conflating
/// it with an empty result.
@ -1651,7 +1651,7 @@ pub fn analyse_file_auth(
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::BAD_REQUEST)?;
// Determine whether the auth rules were actually enabled for this
// file's language `extract_auth_model_for_debug` returns an empty
// file's language, `extract_auth_model_for_debug` returns an empty
// model both when the rules are disabled and when the file just
// happens to have no routes. The view distinguishes the two so the
// UI can show "analysis disabled" instead of "no routes found".
@ -2122,7 +2122,7 @@ fn main() {
// Belt-and-suspenders: assert that calling with the wrong (top-level)
// CFG would have panicked. We can't catch the panic across rayon
// worker threads here, but we can confirm at least one `inst.cfg_node`
// index lies outside `analysis.cfg()`'s range that's what triggers
// index lies outside `analysis.cfg()`'s range, that's what triggers
// the OOB indexing inside `transfer_inst`.
let toplevel_count = analysis.cfg().node_count();
let max_inst_idx = ssa

View file

@ -1,4 +1,4 @@
//! Health-score scoring engine v3.5.
//! Health-score scoring engine, v3.5.
//!
//! Pure-function scoring over a `HealthInputs` struct. Documented in
//! `docs/health-score-audit.md` (calibration, rationale) and
@ -15,7 +15,7 @@
//!
//! 2. **HIGH-count guardrails.** The *qualitative* axis: HIGH counts
//! cap the maximum grade and floor "no HIGH" to at least C. These
//! are non-negotiable promises even a perfect-everywhere-else
//! are non-negotiable promises, even a perfect-everywhere-else
//! repo with 6 confirmed HIGHs grades F.
//!
//! Modifiers (triage, trend, stale, regression, suppression hygiene)
@ -27,17 +27,17 @@
//! * Verdict-weighted credibility (`Confirmed > NotAttempted >
//! Inconclusive > Infeasible`). This is the structural protection
//! against false-positive-driven F grades while the scanner is
//! still maturing it auto-tightens as symex coverage grows.
//! still maturing, it auto-tightens as symex coverage grows.
//! * Cross-file vs intra-file vs AST-only weighting via
//! `context_factor`.
//! * Test-path downweight (0.3×) a HIGH in a test fixture is
//! * Test-path downweight (0.3×), a HIGH in a test fixture is
//! genuinely less concerning than one in a request handler.
//! * Effective HIGH count for ceilings the HIGH-count caps key on
//! * Effective HIGH count for ceilings, the HIGH-count caps key on
//! credibility-adjusted HIGHs, not raw HIGHs. A repo with 5
//! low-confidence HIGHs that got `NotAttempted` from symex doesn't
//! pay the same ceiling cost as a repo with 5 `Confirmed` HIGHs.
//! * Tighter modifier ranges so they can't flip a band.
//! * No `parse_success_rate` (it's actually a cache-miss metric
//! * No `parse_success_rate` (it's actually a cache-miss metric ,
//! see `project_parse_success_rate_misnomer.md`).
use crate::commands::scan::Diag;
@ -48,11 +48,11 @@ use crate::server::models::{BacklogStats, FindingSummary, HealthComponent, Healt
// ── Tunables ─────────────────────────────────────────────────────────────────
//
// Calibrated for v0.5.0 scanner FP rate. As Nyx symex coverage and
// rule precision improve, the HIGH ceilings should tighten see
// rule precision improve, the HIGH ceilings should tighten, see
// `docs/health-score-audit.md` "Calibration trajectory" for the
// roadmap.
/// Below this file count, we floor the size divisor at 1.0 tiny
/// Below this file count, we floor the size divisor at 1.0, tiny
/// repos can't claim infinite per-LOC dilution from one finding.
const FILES_FLOOR: f64 = 100.0;
@ -66,7 +66,7 @@ const QUALITY_DRAG_PER_FINDING: f64 = 0.05;
const QUALITY_DRAG_CAP: f64 = 15.0;
/// Below this finding count, the Triage component contributes
/// weight 0 we don't punish fresh users for not having triaged
/// weight 0, we don't punish fresh users for not having triaged
/// what didn't need triaging.
const TRIAGE_FLOOR: usize = 20;
@ -77,7 +77,7 @@ const STALE_PENALTY_CAP: f64 = 10.0;
// ── Public API ───────────────────────────────────────────────────────────────
/// Pure inputs to the health-score calculation. No app state, no DB
/// handles those upstream concerns are flattened into primitives the
/// handles, those upstream concerns are flattened into primitives the
/// scorer actually consumes.
#[derive(Debug, Clone, Copy)]
pub struct HealthInputs<'a> {
@ -120,7 +120,7 @@ pub fn compute(inp: &HealthInputs<'_>) -> HealthScore {
let quality_drag = quality_drag(weighted.quality_count);
let base_after_drag = (base_score - quality_drag).clamp(0.0, 100.0);
// Step 5: HIGH-count guardrails keyed on *effective* HIGH count
// Step 5: HIGH-count guardrails, keyed on *effective* HIGH count
// (credibility-weighted), not raw count. This is what protects
// users from FP-driven F grades while the scanner is maturing.
let ceiling = high_total_ceiling(weighted.effective_high);
@ -161,9 +161,9 @@ struct WeightedAggregate {
/// context_factor` across security findings. Quality lints are
/// handled separately via `quality_drag`.
raw_weight: f64,
/// Number of `*.quality.*` findings drives `quality_drag`.
/// Number of `*.quality.*` findings, drives `quality_drag`.
quality_count: usize,
/// Credibility-adjusted HIGH count (rounded) drives the HIGH
/// Credibility-adjusted HIGH count (rounded), drives the HIGH
/// ceiling and floor. A low-confidence + Inconclusive HIGH might
/// contribute 0.2; five of them would round to 1.
effective_high: usize,
@ -171,10 +171,10 @@ struct WeightedAggregate {
raw_high: usize,
raw_medium: usize,
raw_low_security: usize,
/// Confidence rate (high+medium*0.5)/total drives the
/// Confidence rate (high+medium*0.5)/total, drives the
/// confidence component. 100 if no findings.
confidence_rate: f64,
/// Symex coverage % of taint findings with any non-NotAttempted
/// Symex coverage, % of taint findings with any non-NotAttempted
/// verdict. Surfaced in component detail; not currently in score.
symex_coverage: f64,
}
@ -218,7 +218,7 @@ fn aggregate_findings(findings: &[Diag]) -> WeightedAggregate {
_ => 0.0,
};
// Symex coverage tracking only meaningful for findings with
// Symex coverage tracking, only meaningful for findings with
// taint-flow evidence (the ones symex even attempts).
if let Some(ev) = f.evidence.as_ref()
&& ev.symbolic.is_some()
@ -294,7 +294,7 @@ fn context_factor(f: &Diag) -> f64 {
return 0.3;
}
let Some(ev) = f.evidence.as_ref() else {
return 0.75; // No evidence at all pattern match
return 0.75; // No evidence at all, pattern match
};
if ev.flow_steps.is_empty() {
return 0.75;
@ -351,7 +351,7 @@ fn quality_drag(quality_count: usize) -> f64 {
(quality_count as f64 * QUALITY_DRAG_PER_FINDING).min(QUALITY_DRAG_CAP)
}
// ── HIGH guardrails calibrated for v0.5.0 FP rate ──────────────────────────
// ── HIGH guardrails, calibrated for v0.5.0 FP rate ──────────────────────────
/// Final-score ceiling keyed on *effective* HIGH count (credibility-
/// weighted, not raw). See module docstring for the rationale.
@ -398,7 +398,7 @@ fn build_components(
let sev_score = base_after_drag.round().clamp(0.0, 100.0) as u8;
let sev_detail = severity_detail(weighted, size_divisor, inp.repo_files, inp.backlog);
// Confidence component high-conf rate scaled into 0..=100.
// Confidence component, high-conf rate scaled into 0..=100.
let conf_score = weighted.confidence_rate.round().clamp(0.0, 100.0) as u8;
let conf_detail = format!(
"High-confidence rate {:.0}% across {} security finding{}",
@ -407,7 +407,7 @@ fn build_components(
plural_s(total - weighted.quality_count)
);
// Trend component only contributes weight when has_history.
// Trend component, only contributes weight when has_history.
let net = inp.fixed_since_last as i64 - inp.new_since_last as i64;
let trend_score = (50 + net * 5).clamp(0, 100) as u8;
let trend_weight = if inp.has_history { 0.20 } else { 0.0 };
@ -420,7 +420,7 @@ fn build_components(
"Not applicable: no prior scan to compare against (re-scan to populate)".into()
};
// Triage drops out when total < TRIAGE_FLOOR.
// Triage, drops out when total < TRIAGE_FLOOR.
let triage_active = total >= TRIAGE_FLOOR;
let triage_score = (inp.triage_coverage * 100.0).round().clamp(0.0, 100.0) as u8;
let triage_weight = if triage_active { 0.20 } else { 0.0 };
@ -470,7 +470,7 @@ fn build_components(
HealthComponent {
label: "Severity pressure".into(),
score: sev_score,
weight: 1.0, // Severity is the *base*, not a modifier full weight in the blend.
weight: 1.0, // Severity is the *base*, not a modifier, full weight in the blend.
detail: sev_detail,
},
HealthComponent {
@ -770,7 +770,7 @@ mod tests {
.collect();
let s = summary_of(&findings);
let h = compute(&first_scan(&s, &findings, 0.0, 100));
// The score reflects credibility should NOT crater to F.
// The score reflects credibility, should NOT crater to F.
assert!(
h.score >= 60,
"low-credibility HIGHs shouldn't crater to F, got {}",

View file

@ -65,7 +65,7 @@ pub struct JobManager {
job_order: Mutex<Vec<String>>,
active_job_id: Mutex<Option<String>>,
max_jobs: usize,
/// Dedicated rayon pool for scans keeps the global pool (and tokio
/// Dedicated rayon pool for scans, keeps the global pool (and tokio
/// worker threads) free so the web UI stays responsive during a scan.
scan_pool: rayon::ThreadPool,
}

View file

@ -632,7 +632,7 @@ pub struct HealthScore {
pub struct HealthComponent {
/// Human label (e.g. "Severity pressure", "Trend", "Triage").
pub label: String,
/// 0100 already inverted so higher = healthier.
/// 0100, already inverted so higher = healthier.
pub score: u8,
/// Weight applied when blending into the final score (0.01.0).
pub weight: f64,
@ -662,7 +662,7 @@ pub struct BacklogStats {
pub median_age_days: Option<u32>,
/// Findings older than 30 days that remain open.
pub stale_count: usize,
/// Histogram buckets (label, count) fixed 5 buckets.
/// Histogram buckets (label, count), fixed 5 buckets.
pub age_buckets: Vec<OverviewCount>,
}
@ -691,12 +691,12 @@ pub struct ConfidenceDistribution {
pub struct ScannerQuality {
pub files_scanned: u64,
pub files_skipped: u64,
/// 0.01.0 files_scanned / (files_scanned + files_skipped).
/// 0.01.0, files_scanned / (files_scanned + files_skipped).
pub parse_success_rate: f64,
pub functions_analyzed: u64,
pub call_edges: u64,
pub unresolved_calls: u64,
/// 0.01.0 call_edges / (call_edges + unresolved_calls).
/// 0.01.0, call_edges / (call_edges + unresolved_calls).
pub call_resolution_rate: f64,
/// % of taint findings that received a symbolic verdict (Confirmed|Infeasible|Inconclusive).
pub symex_verified_rate: f64,
@ -712,7 +712,7 @@ pub struct IssueCategoryBucket {
pub count: usize,
}
/// "Hot sink" a single callee that absorbs many findings.
/// "Hot sink", a single callee that absorbs many findings.
#[derive(Debug, Clone, Serialize)]
pub struct HotSink {
/// Callee name (best-effort; from flow_steps last Sink).
@ -723,7 +723,7 @@ pub struct HotSink {
/// One OWASP Top-10 (2021) bucket.
#[derive(Debug, Clone, Serialize)]
pub struct OwaspBucket {
/// "A01:2021 Broken Access Control" etc.
/// "A01:2021, Broken Access Control" etc.
pub code: String,
pub label: String,
pub count: usize,

View file

@ -41,7 +41,7 @@ pub async fn observe(mut request: Request, next: Next) -> Response {
response.headers_mut().insert(REQUEST_ID_HEADER, value);
}
// Skip noisy SSE channel long-lived stream pollutes logs.
// Skip noisy SSE channel, long-lived stream pollutes logs.
if path != "/api/events" {
if status.is_server_error() {
tracing::error!(

View file

@ -1,15 +1,15 @@
//! Static rule-id → OWASP Top-10 (2021) mapping for the dashboard.
//!
//! Rule IDs follow the convention `{lang}.{family}.{name}` (e.g. `js.xss.outer_html`).
//! The family segment is what determines the bucket. Conservative when in doubt,
//! The family segment is what determines the bucket. Conservative, when in doubt,
//! map to the closest fit; rules with no obvious bucket are left unbucketed.
use crate::server::models::OwaspBucket;
use std::collections::HashMap;
/// Extract the family token from a rule ID. Handles two ID shapes:
/// 1. `lang.family.name` typical (e.g. `js.xss.outer_html`)
/// 2. `family-subname` or single-segment engine-emitted (e.g.
/// 1. `lang.family.name`, typical (e.g. `js.xss.outer_html`)
/// 2. `family-subname` or single-segment, engine-emitted (e.g.
/// `state-resource-leak`, `taint-unsanitised-flow`, `cfg-error-fallthrough`)
fn extract_family(rule_id: &str) -> &str {
if let Some(idx) = rule_id.find('.') {
@ -33,23 +33,23 @@ pub fn owasp_bucket_for(rule_id: &str) -> Option<(&'static str, &'static str)> {
}
Some(match family {
// A01 Broken Access Control
// A01, Broken Access Control
"auth" | "csrf" | "mass_assign" | "path" | "redirect" => ("A01", "Broken Access Control"),
// A02 Cryptographic Failures
// A02, Cryptographic Failures
"crypto" | "secrets" => ("A02", "Cryptographic Failures"),
// A03 Injection (covers SQLi, XSS, command, code-eval, template, NoSQL, LDAP, reflection,
// A03, Injection (covers SQLi, XSS, command, code-eval, template, NoSQL, LDAP, reflection,
// and engine-level taint findings without a more specific family tag).
"sqli" | "xss" | "cmdi" | "code_exec" | "template" | "nosql" | "ldap" | "reflection"
| "taint" => ("A03", "Injection"),
// A05 Security Misconfiguration (TLS verify off, cookie flags, prototype pollution)
// A05, Security Misconfiguration (TLS verify off, cookie flags, prototype pollution)
"config" | "transport" | "prototype" => ("A05", "Security Misconfiguration"),
// A08 Software and Data Integrity Failures
// A08, Software and Data Integrity Failures
"deser" => ("A08", "Software and Data Integrity Failures"),
// A09 Logging & Monitoring Failures
// A09, Logging & Monitoring Failures
"log" => ("A09", "Logging and Monitoring Failures"),
// A10 SSRF
// A10, SSRF
"ssrf" => ("A10", "Server-Side Request Forgery"),
// Memory-safety + state-machine resource lifecycle bugs closest OWASP fit is
// Memory-safety + state-machine resource lifecycle bugs, closest OWASP fit is
// A04 Insecure Design (defensive depth).
"memory" | "state" => ("A04", "Insecure Design"),
// Quality findings (e.g. rs.quality.unwrap) and CFG structural issues
@ -162,7 +162,7 @@ mod tests {
fn malformed_rule_returns_none() {
// single-segment "not" → family "not" → unmapped → None
assert_eq!(owasp_bucket_for("not-a-rule"), None);
// "js.onlytwo" family is "onlytwo" which is unmapped
// "js.onlytwo", family is "onlytwo" which is unmapped
assert_eq!(owasp_bucket_for("js.onlytwo"), None);
}

View file

@ -282,7 +282,7 @@ async fn remove_terminator(
// ── Sources / Sinks / Sanitizers (by kind) ───────────────────────────────────
fn list_by_kind(state: &AppState, target_kind: &str) -> Vec<LabelEntryView> {
// Built-in rules live on /api/rules keep this endpoint focused on the
// Built-in rules live on /api/rules, keep this endpoint focused on the
// user's own additions in nyx.local.
let target_rule_kind = match target_kind {
"source" => RuleKind::Source,

View file

@ -306,8 +306,8 @@ async fn get_type_facts(
}
/// GET /api/debug/auth?file=<path>
/// Return the file-scoped authorization model routes, units,
/// sensitive operations, and auth checks for the debug UI.
/// Return the file-scoped authorization model, routes, units,
/// sensitive operations, and auth checks, for the debug UI.
async fn get_auth(
State(state): State<AppState>,
Query(q): Query<FileQuery>,

View file

@ -55,7 +55,7 @@ struct TreeEntry {
struct SymbolEntry {
name: String,
/// Legacy display kind (`"function"` / `"method"`) used by existing CSS
/// classes in the frontend. Kept for backward-compat new consumers
/// classes in the frontend. Kept for backward-compat, new consumers
/// should prefer `func_kind`.
kind: String,
/// Structural [`crate::symbol::FuncKind`] slug (`"fn"`, `"method"`,
@ -291,7 +291,7 @@ async fn get_symbols(
let entries: Vec<SymbolEntry> = symbols
.into_iter()
.map(|(name, arity, _lang, namespace, container, func_kind)| {
// Legacy `kind` field still used by existing CSS classes
// Legacy `kind` field, still used by existing CSS classes
// (`symbol-kind-method`, `symbol-kind-function`). Map any
// method-like FuncKind onto `"method"` and everything else
// onto `"function"` so the rendered icon stays sensible.

View file

@ -73,7 +73,7 @@ fn load_latest_findings_internal(state: &AppState) -> LoadedFindings {
/// Build (or fetch from cache) the per-scan derived views.
///
/// Returns clones of `Arc`s so callers can drop the lock immediately and work
/// without contention. Triage state is *not* baked into the cached views it
/// without contention. Triage state is *not* baked into the cached views, it
/// changes on a different cadence and is overlaid per request.
fn cached_for_latest(state: &AppState) -> CachedFindings {
let loaded = load_latest_findings_internal(state);
@ -85,7 +85,7 @@ fn cached_for_latest(state: &AppState) -> CachedFindings {
}
}
// Slow path: rebuild. Guard against concurrent rebuilds of the same key
// Slow path: rebuild. Guard against concurrent rebuilds of the same key ,
// a second writer that finds the cache already populated for our key
// simply returns it.
let mut guard = state.findings_cache.write();

View file

@ -29,7 +29,7 @@ pub fn routes() -> Router<AppState> {
.route("/overview/baseline/{scan_id}", post(set_baseline_path))
}
/// GET /api/overview aggregated dashboard data.
/// GET /api/overview, aggregated dashboard data.
async fn overview(State(state): State<AppState>) -> Json<OverviewResponse> {
// 1. Load latest findings (in-memory → DB fallback)
let findings = crate::server::routes::findings::load_latest_findings(&state);
@ -121,7 +121,7 @@ async fn overview(State(state): State<AppState>) -> Json<OverviewResponse> {
new_since_last,
fixed_since_last,
reintroduced: reintroduced_count,
// Files-scanned proxy for repo size used for size-aware
// Files-scanned proxy for repo size, used for size-aware
// severity dampening in `health::compute`. See
// `docs/health-score-audit.md` for calibration data.
repo_files: scanner_quality
@ -129,10 +129,10 @@ async fn overview(State(state): State<AppState>) -> Json<OverviewResponse> {
.map(|q| q.files_scanned)
.filter(|&f| f > 0),
backlog: backlog.as_ref(),
// Trend is meaningless without ≥2 completed scans
// Trend is meaningless without ≥2 completed scans ,
// matches the first-scan check `compare_to_current` uses.
has_history: history.scans.len() >= 2,
// Suppression-hygiene modifier populated when the
// Suppression-hygiene modifier, populated when the
// suppression panel was computable for this scan.
blanket_suppression_rate: suppression_hygiene.as_ref().map(|s| s.blanket_rate),
},
@ -173,7 +173,7 @@ async fn overview(State(state): State<AppState>) -> Json<OverviewResponse> {
})
}
/// GET /api/overview/trends scan-over-scan finding counts.
/// GET /api/overview/trends, scan-over-scan finding counts.
async fn overview_trends(State(state): State<AppState>) -> Json<Vec<TrendPoint>> {
let mut points = Vec::new();
@ -218,7 +218,7 @@ struct BaselineBody {
scan_id: String,
}
/// POST /api/overview/baseline { scan_id } pin a scan as the baseline for drift comparison.
/// POST /api/overview/baseline { scan_id }, pin a scan as the baseline for drift comparison.
async fn set_baseline(
State(state): State<AppState>,
Json(body): Json<BaselineBody>,
@ -226,7 +226,7 @@ async fn set_baseline(
set_baseline_inner(&state, &body.scan_id)
}
/// POST /api/overview/baseline/:scan_id convenience path-form for clients without a JSON body.
/// POST /api/overview/baseline/:scan_id, convenience path-form for clients without a JSON body.
async fn set_baseline_path(
State(state): State<AppState>,
AxPath(scan_id): AxPath<String>,
@ -248,7 +248,7 @@ fn set_baseline_inner(state: &AppState, scan_id: &str) -> Result<StatusCode, Sta
Ok(StatusCode::NO_CONTENT)
}
/// DELETE /api/overview/baseline clear the pinned baseline.
/// DELETE /api/overview/baseline, clear the pinned baseline.
async fn clear_baseline(State(state): State<AppState>) -> Result<StatusCode, StatusCode> {
let pool = state
.db_pool
@ -381,7 +381,7 @@ impl ScanHistory {
(new_count, fixed_count, reintroduced)
}
/// Trend slope across the last N totals 1.0 means strictly improving,
/// Trend slope across the last N totals, 1.0 means strictly improving,
/// -1.0 strictly regressing, 0.0 unchanged. Returns None with <3 points.
fn trend_slope(&self) -> Option<f64> {
if self.scans.len() < 3 {
@ -712,7 +712,7 @@ fn compute_cross_file_ratio(findings: &[Diag]) -> f64 {
cross as f64 / findings.len() as f64
}
/// Hot sinks are *only* meaningful for taint findings counting AST rule IDs
/// Hot sinks are *only* meaningful for taint findings, counting AST rule IDs
/// (e.g. `rs.quality.unwrap`) here just duplicates the Top Rules table. So we
/// deliberately require a real Sink-step callee (or a parsable sink snippet)
/// and skip everything else. Empty result → frontend hides the card.
@ -751,7 +751,7 @@ fn compute_hot_sinks(findings: &[Diag], limit: usize) -> Vec<HotSink> {
rows
}
/// Pull the leading identifier from a sink snippet a best-effort heuristic
/// Pull the leading identifier from a sink snippet, a best-effort heuristic
/// for the dashboard's "hot sinks" list.
fn extract_callee_from_snippet(s: &str) -> String {
let trimmed = s.trim();
@ -932,7 +932,7 @@ fn compute_suppression_hygiene(state: &AppState, findings: &[Diag]) -> Suppressi
}
fn compute_backlog(state: &AppState, findings: &[Diag], history: &ScanHistory) -> BacklogStats {
// No useful aging data on the first scan every fingerprint was first-seen
// No useful aging data on the first scan, every fingerprint was first-seen
// today by definition. Avoid the misleading "0d / 0d / 0" display.
if history.scans.len() <= 1 {
return BacklogStats {
@ -1046,7 +1046,7 @@ fn build_posture(
current_total: usize,
) -> PostureSummary {
// First-scan case: no prior data to diff against. Saying "stable / no change"
// is misleading we genuinely don't know yet.
// is misleading, we genuinely don't know yet.
if history.scans.len() <= 1 {
return PostureSummary {
trend: "unknown".into(),

View file

@ -61,7 +61,7 @@ fn build_rule_list(state: &AppState) -> Vec<RuleInfo> {
rules
}
/// GET /api/rules list all rules with finding counts.
/// GET /api/rules, list all rules with finding counts.
async fn list_rules(State(state): State<AppState>) -> Json<Vec<RuleListItem>> {
let rules = build_rule_list(&state);
@ -99,7 +99,7 @@ async fn list_rules(State(state): State<AppState>) -> Json<Vec<RuleListItem>> {
Json(items)
}
/// GET /api/rules/:id full detail for one rule.
/// GET /api/rules/:id, full detail for one rule.
async fn get_rule(
State(state): State<AppState>,
Path(id): Path<String>,
@ -140,7 +140,7 @@ async fn get_rule(
}))
}
/// POST /api/rules/:id/toggle enable/disable a rule.
/// POST /api/rules/:id/toggle, enable/disable a rule.
async fn toggle_rule(
State(state): State<AppState>,
Path(id): Path<String>,
@ -162,7 +162,7 @@ async fn toggle_rule(
Ok(Json(serde_json::json!({ "status": "ok", "rule_id": id })))
}
/// POST /api/rules/clone clone a built-in rule to custom.
/// POST /api/rules/clone, clone a built-in rule to custom.
async fn clone_rule(
State(state): State<AppState>,
Json(body): Json<serde_json::Value>,

View file

@ -213,7 +213,7 @@ async fn delete_scan(
Json(serde_json::json!({ "error": msg })),
));
}
// "Scan not found" in memory is fine may be DB-only
// "Scan not found" in memory is fine, may be DB-only
}
// Delete from DB (CASCADE handles metrics + logs)

View file

@ -3,8 +3,8 @@
//! This file is designed to be committed to version control so that triage
//! decisions travel with the code and are shared across team members.
//!
//! The file uses **portable fingerprints** computed with paths relative to the
//! project root so they match across machines regardless of where the repo is
//! The file uses **portable fingerprints**, computed with paths relative to the
//! project root, so they match across machines regardless of where the repo is
//! checked out.
use crate::commands::scan::Diag;