Skip to content

fix: replace .unwrap() with .expect() in non-test scan paths (refs #275)#279

Merged
peaktwilight merged 8 commits into
mainfrom
275-unwrap-audit
Apr 30, 2026
Merged

fix: replace .unwrap() with .expect() in non-test scan paths (refs #275)#279
peaktwilight merged 8 commits into
mainfrom
275-unwrap-audit

Conversation

@Darkroom4364
Copy link
Copy Markdown
Collaborator

@Darkroom4364 Darkroom4364 commented Apr 26, 2026

Summary

  • Replace 7 bare .unwrap() calls in non-test code with .expect() or let-else
  • Scoped to files not touched by any open PR to avoid conflicts
  • Each site documents the invariant that makes the unwrap safe

Files changed

  • semgrep_compat.rslet-else refactor + expect on checked length
  • semgrep_taint.rsexpect on find('.') guarded by prior rfind
  • scanner.rsexpect on regex group 0 + Mutex lock
  • terminal.rsexpect on last_mut after push
  • foxguard_mcp.rsexpect on json!() serialization

Test plan

  • cargo test — all tests pass (1 pre-existing failure on main unrelated to this PR)
  • cargo fmt --check — clean

Summary by CodeRabbit

  • Bug Fixes

    • Improved seed detection logic to retain higher-confidence entries when duplicates are encountered.
  • Refactor

    • Enhanced error reporting with more descriptive diagnostic messages across multiple scanning and analysis modules.

- Add Cargo seeds: k256, secp256k1, libsecp256k1, ed448-goldilocks (tier 1), openssl (tier 2)
- Add pip seeds: pyjwt, authlib, python-jose, jwcrypto (0.8), m2crypto (0.6)
- Fix fabric: drop misleading RSA algorithm (wraps paramiko, no crypto itself)
- semgrep_compat: refactor is_none()+unwrap() to let-else
- semgrep_compat: expect("checked len == 1") on single-positive path
- semgrep_taint: expect on find('.') guarded by prior rfind
- scanner: expect on regex group 0 and Mutex::lock
- terminal: expect on last_mut after push
- foxguard_mcp: expect on json!() serialization
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b437d33-0e6a-4805-b470-8813abb9517d

📥 Commits

Reviewing files that changed from the base of the PR and between 015bce2 and 0ec3ea6.

📒 Files selected for processing (1)
  • src/rules/manifest.rs

📝 Walkthrough

Walkthrough

This PR improves error handling throughout the codebase by replacing generic unwrap() calls with explicit expect() calls that provide specific panic messages, and refines the seed deduplication logic in Cargo.lock BFS to compare and prefer higher-confidence entries.

Changes

Cohort / File(s) Summary
Error Handling Improvements
src/bin/foxguard_mcp.rs, src/report/terminal.rs, src/rules/semgrep_taint.rs
Replaced generic unwrap() calls with expect() to provide custom panic messages for JSON serialization, vector access, and attribute-chain parsing operations.
Mutex & Regex Panic Messages
src/engine/scanner.rs
Updated two Mutex lock acquisitions and one regex capture group operation to use expect() with explicit failure messages instead of unwrap().
Seed Deduplication Logic
src/rules/manifest.rs
Changed Cargo.lock BFS seed reachability tracking to compare confidence values when resolving duplicates—replaced entries only when the new seed has higher confidence, rather than keeping the first inserted entry.
Control Flow Refactoring
src/rules/semgrep_compat.rs
Replaced explicit is_none() check with else binding pattern for early returns, and updated single-positive case with expect() for clarity.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested reviewers

  • peaktwilight

Poem

🐰 Unwrap no more, expect the best,
With panic messages put to the test,
Seeds ranked by confidence, not by chance,
Error handling gets a better dance!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing unwrap() calls with expect() in non-test code paths.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 275-unwrap-audit

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/rules/manifest.rs (1)

296-317: ⚠️ Potential issue | 🟠 Major

Add deterministic tie-breaker for equal confidence values in best-seed selection.

HashMap::values() iteration order is non-deterministic. When multiple seeds have equal confidence (e.g., 0.9), the selected best seed varies across runs. Add seed name as a secondary sort criterion to ensure stable behavior.

Suggested fix
             let best = reached_seeds
                 .values()
+                .copied()
                 .max_by(|a, b| {
                     a.confidence
                         .total_cmp(&b.confidence)
+                        .then_with(|| a.name.cmp(b.name))
                 })
                 .unwrap();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rules/manifest.rs` around lines 296 - 317, The best-seed selection can be
non-deterministic when two SeedEntry items have equal confidence; update the
update logic in the reached_seeds insertion (the block using
reached_seeds.entry(entry.name).and_modify(...).or_insert(entry)) to use a
deterministic tie-breaker: replace the existing entry when entry.confidence >
existing.confidence OR when entry.confidence == existing.confidence AND
entry.name is lexicographically less (or otherwise deterministic) than
existing.name; keep using SeedEntry, reached_seeds, seed_map and entry.name to
locate and compare entries so ties break consistently by seed name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/rules/manifest.rs`:
- Around line 296-317: The best-seed selection can be non-deterministic when two
SeedEntry items have equal confidence; update the update logic in the
reached_seeds insertion (the block using
reached_seeds.entry(entry.name).and_modify(...).or_insert(entry)) to use a
deterministic tie-breaker: replace the existing entry when entry.confidence >
existing.confidence OR when entry.confidence == existing.confidence AND
entry.name is lexicographically less (or otherwise deterministic) than
existing.name; keep using SeedEntry, reached_seeds, seed_map and entry.name to
locate and compare entries so ties break consistently by seed name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aeb4c395-d056-461b-9e75-7f02db223d11

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba172a and b52bffc.

📒 Files selected for processing (7)
  • src/bin/foxguard_mcp.rs
  • src/engine/scanner.rs
  • src/report/sarif.rs
  • src/report/terminal.rs
  • src/rules/manifest.rs
  • src/rules/semgrep_compat.rs
  • src/rules/semgrep_taint.rs

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

The seven targeted .unwrap().expect() / let-else changes are all correct: every invariant message accurately describes why the call cannot fail. However, manifest.rs and sarif.rs contain substantial undocumented changes — new seed entries, a fabric attribution change, Vec→HashMap deduplication, a new rule description, and a new depName SARIF property — that are omitted from the PR description's "Files changed" list and unrelated to the cleanup goal.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/transparency concerns with no runtime defects.

Every .unwrap() replacement is guarded by a sound invariant, and the hidden manifest.rs/sarif.rs changes are functionally correct and tested. No P0 or P1 defects were found.

src/rules/manifest.rs and src/report/sarif.rs — contain undocumented behavioral changes that deserve explicit mention in the PR description.

Important Files Changed

Filename Overview
src/bin/foxguard_mcp.rs Single .unwrap().expect() on serde_json::to_string of a json!() literal; invariant is sound, no logic change.
src/engine/scanner.rs Two Mutex .unwrap().expect("lock poisoned") and one regex group-0 .unwrap().expect(); all invariants are valid and the changes are correct.
src/report/sarif.rs Adds depName to the SARIF property bag — a behavioral change to output format not mentioned in the PR description; additive and backward-compatible but undocumented.
src/report/terminal.rs .unwrap().expect("just pushed") on last_mut after a push; invariant is sound.
src/rules/manifest.rs Bundles five new Cargo seeds, four new pip packages, a fabric algorithm attribution change, a new rule description, Vec→HashMap deduplication, and new tests — none mentioned in the PR description; all changes appear correct but are out-of-scope for a cleanup PR.
src/rules/semgrep_compat.rs let-else refactor eliminating .unwrap() and .expect("checked len == 1") guarded by len == 1 check; both changes are correct.
src/rules/semgrep_taint.rs .expect("rfind guarantees at least one dot") inside the if let Some(dot) = pat.rfind('.') branch; invariant is sound since rfind already proved a dot exists.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["scan_files()"] -->|"lock().expect('lock poisoned')"| B["Mutex warnings"]
    A --> C["parse_block_comment_ignore()"]
    C -->|"captures.get(0).expect('group 0 always present')"| D["Regex group 0"]
    E["compile_pattern()"] -->|"if let Some(dot) = rfind('.')"| F{"dot found?"}
    F -->|"yes: find('.').expect('rfind guarantees dot')"| G["Split root / field"]
    F -->|no| H["return None"]
    I["build_matcher()"] -->|"positives.len()==1 guard"| J["next().expect('checked len==1')"]
    K["match_single_pattern()"] -->|"let Some(pat_node) = ... else"| L["return results"]
    M["print_findings_full()"] -->|"push() then last_mut().expect('just pushed')"| N["by_file Vec"]
    O["format_scan_result()"] -->|"to_string().expect('json!() always serializable')"| P["MCP JSON response"]
    subgraph manifest_rs ["manifest.rs (undocumented extras)"]
        Q["Vec reached_seeds"] -->|"changed to"| R["HashMap reached_seeds"]
        R -->|"deduplicates by seed name"| S["values().max_by().unwrap() — guarded"]
        T["fabric crypto_algorithm"] -->|"Some('RSA') → None"| U["no algo attributed"]
        V["5 new Cargo seeds + 4 new PIP packages"] -.-> R
    end
Loading

Comments Outside Diff (1)

  1. src/rules/manifest.rs, line 63-105 (link)

    P2 Undocumented scope creep — behavior changes hidden in a cleanup PR

    manifest.rs and sarif.rs contain substantial behavioral changes that are nowhere mentioned in the PR description, which lists only five files and describes this as a pure unwrapexpect cleanup. The actual changes include: five new CARGO_SEEDS entries (k256, secp256k1, libsecp256k1, ed448-goldilocks, openssl), four new PIP_PACKAGES entries, a behavioral change to fabric (dropping crypto_algorithm from Some("RSA") to None), a new CARGO_PQ_DESC description, seed-deduplication logic (Vec → HashMap), and the depName SARIF property added in sarif.rs. Each of these independently changes scan output for users; bundling them silently into a cleanup PR makes them hard to review and attribute in git history.

Reviews (1): Last reviewed commit: "fix: replace .unwrap() with .expect() in..." | Re-trigger Greptile

Comment thread src/rules/manifest.rs
Comment on lines 329 to 332
let best = reached_seeds
.iter()
.values()
.max_by(|a, b| a.confidence.total_cmp(&b.confidence))
.unwrap();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Remaining bare .unwrap() inconsistent with PR goal

Line 332 still contains an un-replaced .unwrap() on max_by(...):

let best = reached_seeds
    .values()
    .max_by(|a, b| a.confidence.total_cmp(&b.confidence))
    .unwrap();

It is safe (guarded by the is_empty() check at line 324), but it is inconsistent with the stated goal of the PR. A simple .expect("reached_seeds non-empty: guarded by is_empty check above") would make the invariant explicit and match the style applied to every other site in this diff.

Suggested change
let best = reached_seeds
.iter()
.values()
.max_by(|a, b| a.confidence.total_cmp(&b.confidence))
.unwrap();
let best = reached_seeds
.values()
.max_by(|a, b| a.confidence.total_cmp(&b.confidence))
.expect("reached_seeds non-empty: guarded by is_empty check above");

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@peaktwilight
Copy link
Copy Markdown
Collaborator

Approved — 7 .unwrap().expect() with documented invariants per site. Mechanical, safe. Merging.

@peaktwilight peaktwilight merged commit 658ccbc into main Apr 30, 2026
17 checks passed
@peaktwilight peaktwilight deleted the 275-unwrap-audit branch April 30, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants