Skip to content

Fix: Enforce scope on exclude patterns and improve UX - #12

Merged
fathriAbanoub merged 5 commits into
mainfrom
fix/exclude-scope-enforcement
Jul 9, 2026
Merged

Fix: Enforce scope on exclude patterns and improve UX#12
fathriAbanoub merged 5 commits into
mainfrom
fix/exclude-scope-enforcement

Conversation

@fathriAbanoub

@fathriAbanoub fathriAbanoub commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Security fix:

  • Add scope enforcement to prevent excluding files outside the selected directory/zip. Previously, the Browse picker allowed selecting arbitrary filesystem paths which would be silently converted to overly-broad globs.
  • Compute operation scope from selected directories only (files and zips contribute nothing since you can only exclude what's being glued).
  • Validate manual patterns to reject absolute paths, traversals, and out-of-scope references.
  • Disable Browse button for zip-only selections (contents unknown to GUI).
  • Point file picker initial folder at scope root, not zip's parent dir.
  • Show rejection summary when user picks out-of-scope files.

UX improvement:

  • Hide exclude section entirely when only files are selected (no dirs/zips), since excludes only apply during directory recursion.

Testing:

  • Add comprehensive tests for scope computation, pattern validation, and the prefix-separator trap in is_path_in_scope.
  • Tests use real filesystem paths via tmp_path fixture.

Fixes: exclude-outside-selection sandbox escape

Summary by CodeRabbit

  • New Features
    • File browsing now enforces an explicit input scope: valid picks are restricted to items inside selected directory roots, and browsing is disabled when the scope is empty (zip-only selections).
    • Exclude chips/patterns derived from picks are generated relative to the active scope.
  • Bug Fixes
    • Clearer rejection reasons for out-of-scope selections and invalid exclude patterns (e.g., absolute paths and .. traversal).
    • Improved scope matching to prevent incorrect prefix-based folder matches.
  • Tests
    • Added GTK-free test coverage for scope root computation and exclude-pattern validation, including edge cases.

Security fix:
- Add scope enforcement to prevent excluding files outside the selected
  directory/zip. Previously, the Browse picker allowed selecting arbitrary
  filesystem paths which would be silently converted to overly-broad globs.
- Compute operation scope from selected directories only (files and zips
  contribute nothing since you can only exclude what's being glued).
- Validate manual patterns to reject absolute paths, traversals, and
  out-of-scope references.
- Disable Browse button for zip-only selections (contents unknown to GUI).
- Point file picker initial folder at scope root, not zip's parent dir.
- Show rejection summary when user picks out-of-scope files.

UX improvement:
- Hide exclude section entirely when only files are selected (no dirs/zips),
  since excludes only apply during directory recursion.

Testing:
- Add comprehensive tests for scope computation, pattern validation, and
  the prefix-separator trap in is_path_in_scope.
- Tests use real filesystem paths via tmp_path fixture.

Fixes: exclude-outside-selection sandbox escape
@fathriAbanoub fathriAbanoub added bug Something isn't working enhancement New feature or request testing UX labels Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: 9e635c30-160b-469b-a5c0-1578208c3899

📥 Commits

Reviewing files that changed from the base of the PR and between 1eed1b1 and bc47072.

📒 Files selected for processing (2)
  • codegluer_gui.py
  • tests/test_codegluer.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • codegluer_gui.py

📝 Walkthrough

Walkthrough

This PR adds scope enforcement for GUI exclude handling and file picking. It computes allowed scope roots from selected inputs, validates exclude patterns and picked paths against those roots, updates picker defaults and rejection reporting, and adds tests for the new scope rules.

Changes

Scope-aware exclude and browse feature

Layer / File(s) Summary
Scope helpers and window scope state
codegluer_gui.py
Adds scope root computation and path validation helpers, adjusts the heavy-directory heuristic, stores computed scope roots on the window, and shows the empty-scope warning in the UI.
Exclude controls and picker scope checks
codegluer_gui.py
Refactors the exclude UI to disable browsing when no scope roots exist, validates typed exclude patterns against scope, converts picked paths into scope-relative chips, and reports rejected selections from both picker flows.
GUI scope-rule tests
tests/test_codegluer_gui.py
Adds GTK-free tests for scope-root computation, exclude-pattern validation, path scope checks, and updated output/default-name assertions.
collect_files anchored ignore test
tests/test_codegluer.py
Updates the gitignore anchoring test to force skip_default_ignore_dirs=False and documents that choice in the test body.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • fathriAbanoub/codegluer#8: Both PRs extend tests/test_codegluer_gui.py with GUI-helper coverage around command construction and related behavior.
  • fathriAbanoub/codegluer#10: Both PRs change the same GUI exclude and picker flow, including chip handling and browse behavior.
  • fathriAbanoub/codegluer#7: Both PRs modify codegluer_gui.py exclude handling and the tests around --exclude generation.

Suggested labels: GUI

🚥 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: scope enforcement for exclude patterns plus related UX improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 fix/exclude-scope-enforcement

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/test_codegluer_gui.py (1)

335-356: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a test for ~ home-path rejection.

validate_exclude_pattern explicitly rejects ~-prefixed patterns (rule 4 in the docstring), but no test covers this branch. The absolute-path rejection is tested, but the home-path rejection is a separate code path that could regress unnoticed.

✨ Suggested additional test
 def test_validate_rejects_absolute_and_traversal(tmp_path):
     scope = [str(tmp_path)]
     ok, _, err = cg.validate_exclude_pattern("/etc/passwd", scope)
     assert not ok and "Absolute" in err
     ok, _, err = cg.validate_exclude_pattern("../outside.txt", scope)
     assert not ok and "outside" in err
+    ok, _, err = cg.validate_exclude_pattern("~/secret.txt", scope)
+    assert not ok and "Absolute" in err
🤖 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 `@tests/test_codegluer_gui.py` around lines 335 - 356, Add a test in
test_codegluer_gui for the `validate_exclude_pattern` home-path branch, since it
already covers absolute and traversal rejection but not `~`-prefixed inputs.
Extend the existing validation tests to call `cg.validate_exclude_pattern` with
a `~` path and assert it is rejected with the expected error, using the
`validate_exclude_pattern` symbol to locate the logic.
codegluer_gui.py (1)

903-932: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared rejection-summary logic.

The rejection accumulation and truncated error-dialog block here (Lines 921-932) is duplicated almost verbatim in _on_file_chooser_response (Lines 986-996). Consolidating into one helper (e.g. _report_rejected_picks(rejected, n)) removes the copy and keeps the two picker paths in sync when the wording or truncation limit changes.

♻️ Suggested helper extraction
def _report_rejected_picks(self, rejected: list[str], n: int) -> None:
    if not rejected:
        return
    summary = "\n".join(f"• {r}" for r in rejected[:10])
    if len(rejected) > 10:
        summary += f"\n… and {len(rejected) - 10} more."
    self._show_error_dialog(
        f"Rejected {len(rejected)} of {n} pick(s)",
        "Only files inside the selected directory or zip "
        "can be excluded.\n\n" + summary,
    )

Then both handlers call self._report_rejected_picks(rejected, n).

🤖 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 `@codegluer_gui.py` around lines 903 - 932, The rejection-summary and
error-dialog logic is duplicated between the picker handlers, so extract it into
a shared helper such as _report_rejected_picks(rejected, n) and have both the
current selection flow and _on_file_chooser_response call it. Move the truncated
summary formatting and _show_error_dialog call into that helper so the rejection
wording and 10-item limit stay consistent in one place.
🤖 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.

Nitpick comments:
In `@codegluer_gui.py`:
- Around line 903-932: The rejection-summary and error-dialog logic is
duplicated between the picker handlers, so extract it into a shared helper such
as _report_rejected_picks(rejected, n) and have both the current selection flow
and _on_file_chooser_response call it. Move the truncated summary formatting and
_show_error_dialog call into that helper so the rejection wording and 10-item
limit stay consistent in one place.

In `@tests/test_codegluer_gui.py`:
- Around line 335-356: Add a test in test_codegluer_gui for the
`validate_exclude_pattern` home-path branch, since it already covers absolute
and traversal rejection but not `~`-prefixed inputs. Extend the existing
validation tests to call `cg.validate_exclude_pattern` with a `~` path and
assert it is rejected with the expected error, using the
`validate_exclude_pattern` symbol to locate the logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e6379ec3-5250-4b89-b5a4-51f4301e64ab

📥 Commits

Reviewing files that changed from the base of the PR and between e840568 and 84a79f9.

📒 Files selected for processing (2)
  • codegluer_gui.py
  • tests/test_codegluer_gui.py

- Extract _report_rejected_picks() helper; both picker handlers now
  share the same 10-item truncation and wording.
- Add test coverage for the ~-prefixed path rejection branch in
  validate_exclude_pattern (previously untested).
The assertions checking for output filenames in cmd were using substring
membership on a list, which checks for exact element match. Since the
output path is a single list element like '/tmp/.../Glued_Code.txt',
checking 'Glued_Code.txt' in cmd would fail.

Changed to iterate through cmd elements and check if the substring
appears in any element, which correctly validates that the output
filename is part of the full path argument.

Affected tests:
- test_build_command_plain_empty_output_defaults_to_txt
- test_build_command_markdown_empty_output_defaults_to_md
- test_build_command_custom_output_name_respected
The test_gitignore_respect_anchored test was failing after the OOM/freeze
guard was added to collect_files(). The guard skips directories like
'build' by default (via DEFAULT_IGNORE_DIR_NAMES), which prevented the
test from verifying .gitignore anchoring behavior on a directory named
'build'.

Added skip_default_ignore_dirs=False to the collect_files() call in this
test to isolate the gitignore anchoring logic from the default directory
skip logic. This allows the test to verify that anchored gitignore rules
(/build/) work correctly without interference from the OOM guard.
The previous implementation flagged any directory that CONTAINED a heavy
subdirectory (e.g., .git, node_modules) as 'heavy', which was too
aggressive. This prevented opening the file picker in normal project
directories that happen to have a .git folder.

The new logic:
1. Checks if the directory ITSELF is a heavy directory (e.g., the path
   is /home/user/project/node_modules)
2. Falls back to checking if the directory has an excessive number of
   entries (>500)

This allows the file picker to open in normal project directories while
still preventing the freeze bug when pointed at actual heavy directories
like node_modules, .git, dist, etc.

The freeze prevention still works because:
- Opening the picker AT node_modules/.git/etc is blocked (step 1)
- Opening the picker at a directory with 500+ entries is blocked (step 2)
- Normal project directories with a .git folder are now allowed
@fathriAbanoub
fathriAbanoub merged commit a6e9ee6 into main Jul 9, 2026
1 check passed
@fathriAbanoub
fathriAbanoub deleted the fix/exclude-scope-enforcement branch July 9, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request testing UX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant