Skip to content

Commit 8c9afd9

Browse files
Merge branch 'main' into docs/readme-hygiene
2 parents 4b25388 + 600f6fc commit 8c9afd9

21 files changed

Lines changed: 124 additions & 116 deletions

.github/workflows/codeql.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-License-Identifier: PMPL-1.0
1+
# SPDX-License-Identifier: MPL-2.0
22
name: CodeQL Security Analysis
33

44
on:
@@ -30,7 +30,11 @@ jobs:
3030
fail-fast: false
3131
matrix:
3232
include:
33-
- language: javascript-typescript
33+
# Repo is pure Rust; CodeQL does not support Rust as a target.
34+
# We analyse `actions` (GitHub Actions workflow files) so the
35+
# SAST capability still adds value — workflow files are a real
36+
# supply-chain surface for this repo.
37+
- language: actions
3438
build-mode: none
3539

3640
steps:

.github/workflows/dogfood-gate.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ jobs:
9090
with:
9191
path: '.'
9292
strict: 'false'
93+
# Preserves the action's default carve-outs and adds generated/ —
94+
# k9iser output in generated/ uses a scaffold dialect that lacks
95+
# the K9! magic + pedigree block the validator requires. Track the
96+
# generator fix in the k9iser repo, not here.
97+
paths-ignore: |
98+
vendor/
99+
vendored/
100+
verified-container-spec/
101+
.audittraining/
102+
integration/fixtures/
103+
test/fixtures/
104+
tests/fixtures/
105+
generated/
93106
94107
- name: Write summary
95108
run: |

.github/workflows/secret-scanner.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-License-Identifier: PMPL-1.0
1+
# SPDX-License-Identifier: MPL-2.0
22
# Prevention workflow - scans for hardcoded secrets before they reach main
33
name: Secret Scanner
44

@@ -67,9 +67,17 @@ jobs:
6767
'password.*=.*"[^"]+"'
6868
)
6969
70+
# panic-attack is itself a static-analysis tool: src/assail/ and
71+
# src/signatures/ contain the secret-detection regexes by design.
72+
# Excluding them prevents the scanner from flagging its own pattern
73+
# definitions (see hyperpolymath/hypatia#243 — the same fixture-vs-
74+
# target carve-out the k9-validate-action documents).
75+
EXCLUDE_RE='^src/(assail|signatures)/'
7076
found=0
7177
for pattern in "${PATTERNS[@]}"; do
72-
if grep -rn --include="*.rs" -E "$pattern" src/; then
78+
matches=$(grep -rn --include="*.rs" -E "$pattern" src/ | grep -vE "$EXCLUDE_RE" || true)
79+
if [ -n "$matches" ]; then
80+
echo "$matches"
7381
echo "WARNING: Potential hardcoded secret found matching: $pattern"
7482
found=1
7583
fi

FUTURE-IMPROVEMENTS.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,28 @@
33
# Future Improvements: Insights from Scanning the Eclexia Compiler Toolchain
44

55
**Date:** 2026-02-08
6+
**Audit refreshed:** 2026-05-26 (4 of 10 items shipped; status block at top)
67
**Author:** Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
7-
**Context:** panic-attack v1.0.0, scanning Eclexia (10 crates, ~20,000 lines of Rust)
8+
**Context:** panic-attack v1.0.0 at time of scan; current v2.5.0
9+
10+
---
11+
12+
## Status at 2026-05-26
13+
14+
| # | Improvement | Status | Evidence |
15+
|---|-------------|--------|----------|
16+
| 1 | Test Code Exclusion | **Shipped** | `Analyzer::strip_cfg_test_modules_rs``src/assail/analyzer.rs:923-934`. Applied globally before pattern counting; CLAUDE.md confirms cfg(test) skip behaviour. |
17+
| 2 | Framework Detection Accuracy | **Shipped** | `Analyzer::detect_frameworks``src/assail/analyzer.rs:4993`. Dependency-aware classification supersedes the heuristic-only path that misfired on Eclexia. |
18+
| 3 | Safe Unwrap Variant Distinction | **Shipped** | `safe_unwrap_calls` field on `ProgramStatistics` (`src/types.rs:518`) and `FileStatistics` (`src/types.rs:451`). Counted but excluded from PA006 (PanicPath) per CLAUDE.md. |
19+
| 4 | Language-Specific Severity Calibration | Outstanding | No "Hardened" or "Clean" severity tier in `src/types.rs`. Still gated on items 1 + 3 being trustworthy, which they now are. |
20+
| 5 | Workspace-Level Consolidated Reporting | Outstanding | No Cargo workspace mode in `src/main.rs`. `mass-panic` covers cross-repo but not single-workspace aggregation. |
21+
| 6 | Differential Scanning | **Shipped** | `Commands::Diff``src/main.rs:483`; logic in `src/report/diff.rs`. Listed in ROADMAP v2.2.0 as `[x]`. |
22+
| 7 | Allocation Site Context and Classification | Outstanding | No `AllocationCategory` enum in `src/types.rs`. Site counts still raw. |
23+
| 8 | Resource Dimension Awareness for DSLs | Outstanding | Long-term; no plugin-extension surface yet. |
24+
| 9 | Pattern Detection for Safe Error Handling | Outstanding | No "error handling maturity" metric. |
25+
| 10 | Configurable Severity Thresholds for CI | Outstanding | No `[thresholds]` parser; no `panic-attack.toml` consumer. Now unblocked because 1, 2, 3 are accurate. |
26+
27+
**Net:** 4/10 shipped (1, 2, 3, 6). Items 4 and 10 are now genuinely unblocked because their stated dependencies (1, 2, 3) have landed; the original "depends on" notes are still accurate but no longer blocking. Items 5, 7, 8, 9 remain as written.
828

929
---
1030

@@ -70,7 +90,7 @@ The following observations were made during the Eclexia scan session:
7090

7191
### 1. Test Code Exclusion
7292

73-
**Priority:** HIGH
93+
**Priority:** HIGH**Status: SHIPPED** (`Analyzer::strip_cfg_test_modules_rs`, `src/assail/analyzer.rs:923-934`)
7494

7595
**Problem:** panic-attack counts `unwrap()` and `panic!()` calls inside
7696
`#[cfg(test)]` modules, `#[test]` functions, and files in `tests/`
@@ -95,7 +115,7 @@ well-tested Rust codebases.
95115

96116
### 2. Framework Detection Accuracy
97117

98-
**Priority:** HIGH
118+
**Priority:** HIGH**Status: SHIPPED** (`Analyzer::detect_frameworks`, `src/assail/analyzer.rs:4993`)
99119

100120
**Problem:** panic-attack reports "WebServer" as the detected framework for
101121
pure compiler crates with zero I/O operations. This is a misdetection that
@@ -123,7 +143,7 @@ the overall report.
123143

124144
### 3. Safe Unwrap Variant Distinction
125145

126-
**Priority:** HIGH
146+
**Priority:** HIGH**Status: SHIPPED** (`safe_unwrap_calls` field on `ProgramStatistics`/`FileStatistics`, `src/types.rs:451,518`)
127147

128148
**Problem:** The Rust analyzer counts `.unwrap_or(value)`,
129149
`.unwrap_or_default()`, and `.unwrap_or_else(|| ...)` toward the
@@ -205,7 +225,7 @@ represent the majority of non-trivial Rust codebases.
205225

206226
### 6. Differential Scanning (Before/After Comparison)
207227

208-
**Priority:** MEDIUM
228+
**Priority:** MEDIUM**Status: SHIPPED** (`Commands::Diff`, `src/main.rs:483`; logic in `src/report/diff.rs`)
209229

210230
**Problem:** There is no way to compare two scans to show what improved or
211231
regressed between them. This limits the tool's usefulness in CI pipelines,

benches/scan_bench.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
//!
66
//! Measures: language detection, pattern matching, full analysis pipeline.
77
8-
use criterion::{black_box, criterion_group, criterion_main, Criterion};
8+
use criterion::{criterion_group, criterion_main, Criterion};
99
use panic_attack::types::Language;
10+
use std::hint::black_box;
1011

1112
/// Benchmark language detection from file extension
1213
fn bench_language_detect(c: &mut Criterion) {
@@ -223,6 +224,7 @@ fn bench_statistics_calculation(c: &mut Criterion) {
223224
unsafe_blocks: 5,
224225
panic_sites: 0,
225226
unwrap_calls: 50,
227+
safe_unwrap_calls: 0,
226228
allocation_sites: 12,
227229
io_operations: 4,
228230
threading_constructs: 3,

docs/figures/generate_histogram.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/assail/analyzer.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3966,7 +3966,7 @@ impl Analyzer {
39663966

39673967
// Detect function definitions
39683968
if line.trim().starts_with("fn ") && !line.trim().starts_with("fn test") {
3969-
if let Some(func_name) = line.trim().split_whitespace().nth(1) {
3969+
if let Some(func_name) = line.split_whitespace().nth(1) {
39703970
let func_name = func_name.split('(').next().unwrap_or(func_name);
39713971
current_function = func_name.to_string();
39723972
function_calls
@@ -5174,12 +5174,11 @@ impl Analyzer {
51745174
}
51755175
}
51765176

5177-
Language::Erlang => {
5178-
if content.contains("-behaviour(gen_server)")
5179-
|| content.contains("-behaviour(supervisor)")
5180-
{
5181-
frameworks.insert(Framework::OTP);
5182-
}
5177+
Language::Erlang
5178+
if (content.contains("-behaviour(gen_server)")
5179+
|| content.contains("-behaviour(supervisor)")) =>
5180+
{
5181+
frameworks.insert(Framework::OTP);
51835182
}
51845183

51855184
Language::Go => {
@@ -6007,7 +6006,7 @@ func main() {
60076006
&& wp
60086007
.location
60096008
.as_deref()
6010-
.map_or(false, |loc| loc.contains("node_modules"))
6009+
.is_some_and(|loc| loc.contains("node_modules"))
60116010
})
60126011
.collect();
60136012

src/assemblyline.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ pub fn run_with_cache(
432432
.count();
433433

434434
// Sort by weak point count descending (riskiest repos first)
435-
results.sort_by(|a, b| b.weak_point_count.cmp(&a.weak_point_count));
435+
results.sort_by_key(|r| std::cmp::Reverse(r.weak_point_count));
436436

437437
// Apply filters
438438
if config.findings_only {

src/bridge/lockfile.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ use std::path::Path;
1717
/// order. All successful parses are merged into a single list. Errors from
1818
/// individual parsers are logged as warnings and skipped so one malformed
1919
/// lockfile does not abort triage of the whole project.
20+
type LockfileParser = fn(&Path) -> Result<Vec<LockedDependency>>;
21+
2022
pub fn discover_and_parse(dir: &Path) -> Vec<LockedDependency> {
2123
let mut all = Vec::new();
2224

23-
let candidates: &[(&str, fn(&Path) -> Result<Vec<LockedDependency>>)] = &[
25+
let candidates: &[(&str, LockfileParser)] = &[
2426
("Cargo.lock", parse_cargo_lock),
2527
("mix.lock", parse_mix_lock),
2628
("package-lock.json", parse_package_lock_json),
@@ -66,7 +68,7 @@ pub fn parse_cargo_lock(path: &Path) -> Result<Vec<LockedDependency>> {
6668
if let (Some(name), Some(version)) = (current_name.take(), current_version.take()) {
6769
if current_source
6870
.as_ref()
69-
.map_or(false, |s| s.contains("registry"))
71+
.is_some_and(|s| s.contains("registry"))
7072
{
7173
deps.push(LockedDependency {
7274
name,
@@ -94,7 +96,7 @@ pub fn parse_cargo_lock(path: &Path) -> Result<Vec<LockedDependency>> {
9496
if let (Some(name), Some(version)) = (current_name, current_version) {
9597
if current_source
9698
.as_ref()
97-
.map_or(false, |s| s.contains("registry"))
99+
.is_some_and(|s| s.contains("registry"))
98100
{
99101
deps.push(LockedDependency {
100102
name,

src/bridge/reachability.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub fn check_reachability(project_dir: &Path, crate_name: &str) -> Result<Reacha
5454
}
5555

5656
let path = entry.path();
57-
if path.extension().map_or(true, |ext| ext != "rs") {
57+
if path.extension().is_none_or(|ext| ext != "rs") {
5858
continue;
5959
}
6060

0 commit comments

Comments
 (0)