Skip to content

fix: normalize paths for scan ignores and baselines#324

Merged
peaktwilight merged 1 commit into
mainfrom
issue-310-path-normalization
May 16, 2026
Merged

fix: normalize paths for scan ignores and baselines#324
peaktwilight merged 1 commit into
mainfrom
issue-310-path-normalization

Conversation

@Darkroom4364
Copy link
Copy Markdown
Collaborator

@Darkroom4364 Darkroom4364 commented May 11, 2026

Summary

  • add a shared path identity helper for project-root-relative finding keys
  • normalize baseline fingerprints and scan.ignore_rules matching across absolute, relative, nested-cwd, and Windows-style separator paths
  • keep compatibility with existing raw-path baseline fingerprints while writing new normalized baseline entries

Fixes #310

Tests

  • cargo test path_identity
  • cargo test test_config_baseline_applies_from_nested_working_directory
  • cargo test test_scan_ignore_rules_match_nested_cwd_and_windows_separators
  • cargo test
  • git diff --check

Summary by CodeRabbit

Release Notes

  • New Features

    • Added configurable project root support for improved baseline consistency across different working directories.
    • Enhanced baseline and ignore-rule application to work correctly from nested directories.
    • Improved cross-platform path handling with better support for different path separators.
  • Tests

    • Added integration tests for nested directory configuration and path handling scenarios.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@Darkroom4364 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 42 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bbff7dbf-71f7-463d-992b-3ebeba0ced16

📥 Commits

Reviewing files that changed from the base of the PR and between 2092557 and c59c551.

📒 Files selected for processing (8)
  • src/app.rs
  • src/baseline.rs
  • src/config.rs
  • src/lib.rs
  • src/main.rs
  • src/path_identity.rs
  • src/tui/input.rs
  • tests/integration.rs
📝 Walkthrough

Walkthrough

This PR implements stable path identity normalization so findings have consistent suppression behavior across different working directories and path formats. A new path_identity module computes an identity anchor (explicit config, Git toplevel, or scan root) and normalizes all finding and stored paths to root-relative keys. All baseline and ignore-rule suppression APIs gain root-aware *_at_root variants that use normalized file identities while maintaining backward compatibility with legacy fingerprints.

Changes

Path Identity Normalization

Layer / File(s) Summary
Path Identity Utilities
src/path_identity.rs, src/lib.rs
New public module exports project_root(), resolve_scan_root(), resolve_path_for_boundary(), finding_path_key(), and stored_path_key() for computing stable identity anchors and normalizing paths to root-relative keys. Internal helpers discover Git toplevel, canonicalize paths, and standardize separators.
Baseline Root-Aware API
src/baseline.rs
Adds BaselineFile::from_findings_at_root(), add_finding_at_root(), BaselineEntry::from_finding_at_root(), fingerprint_finding_at_root(), write_baseline_at_root(), append_finding_to_baseline_at_root(), and suppress_with_baseline_at_root(). Refactors persistence to share JSON serialization via internal write_baseline_file(). Suppression checks both normalized and legacy fingerprints for compatibility.
Config Integration
src/config.rs
Adds project_root: PathBuf field to FoxguardConfig. Updates suppress_with_scan_ignores() signature to accept identity_root: &Path. Removes local path helpers (resolve_scan_root, resolve_path_for_boundary, absolutize_path, normalize_path) and switches to crate::path_identity utilities for all path boundary and identity operations. Ignore-rule matching now compares normalized path keys instead of raw strings.
App Execution
src/app.rs
Computes identity_root for scan, secrets, and diff flows using finding_identity_root() helper. Switches all suppression and baseline operations to *_at_root variants, passing the computed identity root through the suppression pipeline.
Entry Points
src/main.rs, src/tui/input.rs
Baseline command and TUI triage action load config, compute identity root, and call root-aware baseline write/append operations instead of legacy helpers.
Integration Tests
tests/integration.rs
Adds test_config_baseline_applies_from_nested_working_directory() verifying baseline suppression works when scanning from nested subdirectories with root-relative baseline paths. Adds test_scan_ignore_rules_match_nested_cwd_and_windows_separators() validating ignore-rule matching with Windows-style backslashes from nested working directories.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PwnKit-Labs/foxguard#288: Modifies TUI triage baseline handling in src/tui/input.rs, which this PR also updates for root-aware operations.

Suggested reviewers

  • peaktwilight

Poem

🐰 Paths now unified, no matter where you scan,
From nested directories to Git's hallowed plan,
Identity roots anchor what once would drift and roam,
Baselines and ignore rules now call the same place home,
Windows and Unix walk together, hand in paw! 🐾

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: normalizing paths for scan ignores and baselines, which is the primary focus of all file modifications.
Linked Issues check ✅ Passed All coding requirements from #310 are met: path normalization via path_identity module, consistent application to scan ignores and baselines, identity-root handling across working directories, and tests covering relative/absolute/nested/Windows separators.
Out of Scope Changes check ✅ Passed All changes are in scope for #310: new path_identity module, baseline refactoring for root-aware operations, config updates for identity roots, app/main/tui integration, and tests validating the normalization behavior.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-310-path-normalization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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.

Actionable comments posted: 1

Caution

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

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

534-538: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize existing scan.ignore_rules.path values before merge checks.

This comparison only matches entries that already use the new stored form. If a repo still has a legacy absolute/raw-path ignore entry, suppress_with_scan_ignores() will honor it, but add_scan_ignore_rule() will miss it here and append a duplicate block instead of extending rules. Compare normalized keys instead of the raw YAML value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.rs` around lines 534 - 538, path_matches currently compares the
raw YAML "path" string to stored_path, causing legacy absolute/raw-path entries
to miss merge detection; normalize the existing item_mapping "path" value before
comparison (i.e., run the same normalization/canonicalization used when storing
values) so path_matches becomes a comparison of normalized_path == stored_path;
update the logic in the block that computes path_matches (referencing
item_mapping, stored_path, and path_matches) so add_scan_ignore_rule() and
suppress_with_scan_ignores() will merge into rules instead of appending
duplicates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/path_identity.rs`:
- Around line 55-76: Both finding_path_key and stored_path_key currently call
normalize_separators and then rely on Path::is_absolute(), which misses Windows
drive-letter absolutes like "C:/..."; update them to detect drive-letter
absolute strings after normalization (e.g. match r"^[A-Za-z]:/") and treat those
as absolute paths. Concretely, add a small helper (is_windows_drive_absolute) or
inline check after let value = normalize_separators(...); and if it matches the
drive-letter pattern, call resolve_path_for_boundary on Path::new(&value) and
pass the result to root_relative_key (for finding_path_key) or to
root_relative_key/resolve_path_for_boundary flow in stored_path_key exactly
where Path::is_absolute() is used today. Use the same helper in both
finding_path_key and stored_path_key so behavior is consistent across
normalize_separators, resolve_path_for_boundary, root_relative_key.

---

Outside diff comments:
In `@src/config.rs`:
- Around line 534-538: path_matches currently compares the raw YAML "path"
string to stored_path, causing legacy absolute/raw-path entries to miss merge
detection; normalize the existing item_mapping "path" value before comparison
(i.e., run the same normalization/canonicalization used when storing values) so
path_matches becomes a comparison of normalized_path == stored_path; update the
logic in the block that computes path_matches (referencing item_mapping,
stored_path, and path_matches) so add_scan_ignore_rule() and
suppress_with_scan_ignores() will merge into rules instead of appending
duplicates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 94d7f462-98b5-437f-b123-11bdc5218fbd

📥 Commits

Reviewing files that changed from the base of the PR and between 2da1ebd and 2092557.

📒 Files selected for processing (8)
  • src/app.rs
  • src/baseline.rs
  • src/config.rs
  • src/lib.rs
  • src/main.rs
  • src/path_identity.rs
  • src/tui/input.rs
  • tests/integration.rs

Comment thread src/path_identity.rs
@Darkroom4364 Darkroom4364 force-pushed the issue-310-path-normalization branch from 2092557 to c59c551 Compare May 11, 2026 20:06
@peaktwilight peaktwilight merged commit d809d9e into main May 16, 2026
17 checks passed
@peaktwilight peaktwilight deleted the issue-310-path-normalization branch May 16, 2026 10:25
peaktwilight added a commit that referenced this pull request May 16, 2026
)

After PR #324 normalized baseline path identity, integration tests that
scan tests/fixtures/* without a current_dir override started picking up
the contributor's local .foxguard/baseline.json (which legitimately
suppresses dozens of findings for self-scan hygiene). Every
"this fixture should find N findings" assertion collapsed to the
post-baseline count, breaking 41 tests across three suites.

Fix is mechanical: invoke foxguard with --config /dev/null on the
fixture-scanning paths so config discovery never reaches into
CARGO_MANIFEST_DIR.

Approach:

- tests/integration.rs: introduce foxguard_cmd_isolated() helper next to
  the existing foxguard_cmd(), and swap 58 call sites that scan
  tests/fixtures/* directly. Call sites that set current_dir (45 of
  them, the config/baseline-discovery tests) keep foxguard_cmd()
  unchanged so config discovery still works there.
- tests/realistic_fixtures.rs: only one helper, every call goes through
  it, so just bake --config /dev/null into foxguard_cmd() directly.
- tests/cnsa2_compliance.rs: same single-helper pattern; bake it in.

Net change: 0 production code touched, 41 tests recovered, full suite
goes from 532-passed/26-failed to 609-passed/0-failed.

Refs: #324 (path normalization), #310 (the underlying baseline bug
#324 fixed). Pre-existing devs with .foxguard.yml in the repo root
were unaffected before #324 because the strict path matching never
reached fixture files.

Note: --no-verify used because the pre-commit hook self-scan flags
intentional fixture strings in tests/integration.rs (AWS keys, GitHub
tokens etc. crafted to test the scanner). These are unchanged by
this commit; the hook scans the whole file, not just the diff.
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.

fix: normalize paths for scan.ignore_rules and baseline suppression

2 participants