Skip to content

fix: confine directory exclusions around symlinks (VULN-565) - #208

Merged
howonlee merged 1 commit into
mainfrom
bugfix/565-1
Oct 27, 2026
Merged

howonlee merged 1 commit into
mainfrom
bugfix/565-1

Conversation

@howonlee

@howonlee howonlee commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

A directory exclusion from .gtrconfig could expand through a preserved symlink and delete files outside the worktree during git gtr new.

Expand exclusion globs one component at a time, refusing to descend through symlinks, parent aliases, or Git metadata. Check every component of the exclusion root before entering it. A matched leaf symlink is unlinked without touching its target, and shell options remain local to the exclusion pass.

Regression tests use disposable repositories and sentinel files, including worktree creation with committed configuration and a committed symlink. Copy settings continue to work without requiring trust approval.

Validation: focused BATS tests passed; ShellCheck and generated-completion checks passed; Bash 3.2 CLI create/list/go/remove smoke test passed. Full BATS suite passed (564 passed, 3 skipped; using isolated temporary Git configuration).

Tracking: VULN-565.

Summary by CodeRabbit

  • Bug Fixes

    • Directory exclusions no longer follow symlinks, helping prevent changes to files or directories outside the copied tree. Matching symlinks are removed without affecting their targets.
    • Exclusions are skipped when the copied directory or a component of the exclusion path is a symlink.
  • Documentation

    • Clarified how directory exclusions behave when symlinks are involved.

@linear

linear Bot commented Oct 27, 2026

Copy link
Copy Markdown

VULN-565

@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 6d555d6 and 6eb4b10.

Files selected for processing: 4
  • docs/configuration.md
  • lib/copy.sh
  • tests/cmd_create_integration.bats
  • tests/copy_safety.bats

Included review availability: This review used your included allowance. Your plan provides up to 100 included reviews per hour; 97 remain after this review.

Walkthrough

Directory exclusions now validate the destination root and expand glob patterns one path component at a time. Traversal skips symlinks, parent components, and .git; matching leaf symlinks are removed without touching their targets. Tests cover safety checks, shell-option preservation, and worktree creation from untrusted configuration.

Changes

Directory exclusion safety

Layer / File(s) Summary
Validate and remove exclusion matches
lib/copy.sh, tests/copy_safety.bats, tests/cmd_create_integration.bats, docs/configuration.md
The exclusion logic validates roots and expands patterns component by component without descending through symlinks. Tests cover external targets, unsafe paths, leaf symlinks, spaces, shell-option state, and worktree creation. The documentation describes the exclusion behavior.

Priority: High

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: High

Suggested reviewers: helizaga

Merge Risk: Minimal · up to 6eb4b

The changed exclusion handling has no identified merge-blocking issue; normal checks can proceed.

Security architecture risk: Low · up to 6eb4b

The change restricts symlink-assisted deletion without adding privileges or expanding access. Remaining risk is low: cleanup still assumes the destination is not maliciously changed concurrently, and interrupted deletion is not transactional.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The relevant authority is the invoking user's filesystem access. Repository-controlled configuration and symlinks previously allowed exclusion deletion to reach removable files outside the worktree. The PR narrows that deterministic path; it does not introduce a service identity or privilege elevation.

Trust Boundaries and Controls

  • observed — Enforcement remains at the destructive consumer rather than relying on configuration trust. Root checks reject unsafe relative paths, symlink components, and explicit .git components. Matching also rejects dot, parent, and .git names produced by glob expansion. Regression assertions cover external sentinels, committed configuration and symlinks, dangling leaf links, and glob-spelled traversal.

Resilience and Maintainability Implications

  • inferred — Validation and later pathname-based operations are not atomic, so concurrent directory replacement can invalidate checks. Failures or interruption can leave partial deletion, and success logging indicates at least one successful removal rather than complete exclusion. These limitations predate the PR; no new caller, authority, or concurrent exposure was identified that materially worsens them.
Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely identifies the main change: confining directory exclusions around symlinks. It also includes the relevant vulnerability identifier.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within survi…
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 3 functions across 3 files. (1 skipped: 1 unsupported.)

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

I thump beside the worktree gate,
No symlink sends me past its place.
The leaf-link hops; its target stays,
Safe globs now clear the copied maze,
And carrot crumbs mark bounded ways.

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

@howonlee
howonlee merged commit 07b4205 into main Oct 27, 2026
4 checks passed
@howonlee
howonlee deleted the bugfix/565-1 branch October 27, 2026 17:40
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.

1 participant