Skip to content

fix: expand bandit --exclude paths into globs so nested files are pruned#600

Open
f0rodo wants to merge 1 commit intopeteromallet:mainfrom
f0rodo:fix/bandit-exclude-glob
Open

fix: expand bandit --exclude paths into globs so nested files are pruned#600
f0rodo wants to merge 1 commit intopeteromallet:mainfrom
f0rodo:fix/bandit-exclude-glob

Conversation

@f0rodo
Copy link
Copy Markdown

@f0rodo f0rodo commented Apr 27, 2026

Problem

The Security dimension was reporting 5,958 issues on a project that had .claude and .claude/worktrees in its exclude config. Inspection showed 5,932 of those were in .claude/worktrees/... — exactly the directories the user told desloppify to skip.

The exclusion is honored by the file-discovery layer (_find_source_files_cached correctly prunes via _is_excluded_dir). But the bandit_adapter calls bandit with the project root as its scan target and a --exclude value built from collect_exclude_dirs() — a list of bare absolute paths like /repo/.claude.

Bandit's --exclude is matched against the filenames it discovers during its own recursive walk: it filters files whose name equals one of the supplied strings. So /repo/.claude filters the directory entry itself, but /repo/.claude/worktrees/foo.py is still scanned (the full filename does not equal the exclude string).

Repro:

# Project with .claude/worktrees containing test files (assert-heavy)
$ bandit -r --exclude /repo/.claude /repo
# Returns thousands of B101 findings inside .claude/worktrees/

vs the form bandit's pattern matcher actually understands:

$ bandit -r --exclude '**/.claude/**' /repo
# Properly prunes the subtree

Fix

_exclude_globs(paths) (new helper in bandit_adapter.py) walks the path components of each input and emits **/<name> + **/<name>/** glob siblings alongside the original absolute path. The originals are kept as a belt-and-braces fallback; the globs are what actually do the work.

detect_with_bandit now passes _exclude_globs(exclude_dirs) to --exclude instead of the raw list.

Verified

End-to-end on a real project that excludes .claude and .claude/worktrees:

Before After
Security findings 5,958 3
In .claude/worktrees/... 5,932 0
Security score 8.9% 99.8%

Test

test_exclude_dirs_expanded_to_glob_for_nested_pruning asserts the emitted --exclude arg contains **/.claude, **/.claude/**, and **/worktrees/** when given /project/.claude and /project/.claude/worktrees as input.

5,533 existing tests still pass; the unrelated test_bundled_sync failure on scoring.md predates this branch.

Made with Cursor

Bandit's ``--exclude`` flag is matched against filenames it discovers
during its recursive walk: it filters files whose name *equals* one of
the supplied strings. When desloppify passed bare absolute directory
paths (e.g. ``/repo/.claude``), bandit only matched the directory entry
itself; files *under* it (``/repo/.claude/worktrees/foo.py``) were still
scanned.

The downstream symptom was a Security dimension score of 8.9% on a
project that excluded ``.claude`` and ``.claude/worktrees``: the file
discovery layer correctly skipped those dirs, but bandit was given the
project root and walked into them anyway, returning ~6,000 B101 (use of
assert) findings from agent-tool worktrees that aren't user code.

Fix: ``_exclude_globs`` expands each input path's components into
``**/<name>`` and ``**/<name>/**`` patterns alongside the original
absolute path. The ``**`` form is what bandit's pattern matcher needs to
prune the whole subtree.

Verified end-to-end:
  Before: 5,958 security findings (5,932 in .claude/worktrees/)
  After:  3 security findings (clean rescan, .claude properly pruned)

Test:
  test_exclude_dirs_expanded_to_glob_for_nested_pruning asserts the
  emitted ``--exclude`` arg includes ``**/.claude``, ``**/.claude/**``,
  and ``**/worktrees/**`` for nested input paths.

5,533 existing tests still pass (the unrelated test_bundled_sync
``scoring.md`` failure pre-dates this branch).

Made-with: Cursor
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