Skip to content

Rework path resolvers to use workspace-rooted semantics#98

Merged
Sewer56 merged 4 commits intomainfrom
rework-external-directory
Apr 10, 2026
Merged

Rework path resolvers to use workspace-rooted semantics#98
Sewer56 merged 4 commits intomainfrom
rework-external-directory

Conversation

@Sewer56
Copy link
Copy Markdown
Member

@Sewer56 Sewer56 commented Apr 10, 2026

Summary

  • Replace the multi-base-directory + external-permission-fallback model with a single workspace root, resolved via resolve_workspace_root() (git root > cwd)
  • Remove the external_permission ruleset fallback from both AllowedPathResolver and AllowedGlobResolver, and rework AllowedGlobResolver to accept a single workspace root
  • Add GlobPolicy::builder_with_base() so relative glob patterns are resolved against the workspace root at construction time, while absolute patterns pass through unchanged

Motivation

The external_directory permission was a global ruleset shared across all resolvers, preventing per-tool concrete access configuration.

Moving to workspace-rooted glob patterns gives each resolver its own policy, making fine-grained access control straightforward.

Changes

Area Before After
AllowedGlobResolver bases Multiple Vec<PathBuf> Single &Path workspace root
External path access with_external_permission(Ruleset) Glob patterns with ** under workspace root
Pattern resolution Absolute-only patterns Relative patterns auto-joined to base path
AllowedPathResolver with_external_permission() API Removed (paths outside bases are simply rejected)
Workspace root detection N/A New resolve_workspace_root() (git root, then cwd)

Stats

  • 9 files changed, +706 / -993 lines (net -287)

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 85.00000% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.37%. Comparing base (3bacd1e) to head (1e9e0c6).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...llm-coding-tools-core/src/path/allowed_glob/mod.rs 86.04% 6 Missing ⚠️
src/llm-coding-tools-core/src/path/allowed.rs 50.00% 4 Missing ⚠️
...rc/llm-coding-tools-core/benches/path_resolvers.rs 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #98      +/-   ##
==========================================
+ Coverage   81.08%   81.37%   +0.29%     
==========================================
  Files         107      108       +1     
  Lines        4573     4479      -94     
==========================================
- Hits         3708     3645      -63     
+ Misses        865      834      -31     
Flag Coverage Δ
async 80.63% <85.00%> (+0.28%) ⬆️
blocking 59.15% <85.00%> (-0.27%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...-coding-tools-core/src/path/allowed_glob/policy.rs 90.74% <100.00%> (+4.25%) ⬆️
src/llm-coding-tools-core/src/path/mod.rs 88.00% <ø> (+0.50%) ⬆️
src/llm-coding-tools-core/src/workspace.rs 100.00% <100.00%> (ø)
...rc/llm-coding-tools-core/benches/path_resolvers.rs 0.00% <0.00%> (ø)
src/llm-coding-tools-core/src/path/allowed.rs 67.79% <50.00%> (-9.32%) ⬇️
...llm-coding-tools-core/src/path/allowed_glob/mod.rs 84.90% <86.04%> (+12.87%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Warning

Rate limit exceeded

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

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 15 seconds.

⌛ 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d41b2325-79ec-4333-82d5-034edf2ec62e

📥 Commits

Reviewing files that changed from the base of the PR and between 4a5915c and 1e9e0c6.

📒 Files selected for processing (2)
  • src/llm-coding-tools-core/src/path/allowed_glob/mod.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/policy.rs

Walkthrough

Refactors path-resolution to a workspace-root model: adds a new workspace module with resolve_workspace_root(), changes AllowedGlobResolver to accept a single canonicalized workspace root (removing multi-base iteration and external-permission fallback), removes external-directory permission support from AllowedPathResolver, adds GlobPolicy::builder_with_base() with shell-expansion and pattern normalization, updates benchmarks/tests to the new API, and updates README docs describing workspace-relative glob semantics and permission glob behavior.

Possibly related PRs

  • Sewer56/llm-coding-tools PR 94 — Adds the external_directory permission API and with_external_permission fallback that this change removes (direct API and behavior overlap).
  • Sewer56/llm-coding-tools PR 89 — Modifies AllowedGlobResolver, GlobPolicy, and pattern normalization, overlapping the builder/policy and resolver refactor.
  • Sewer56/llm-coding-tools PR 92 — Changes normalize/shell-expansion functionality used by GlobPolicy::builder_with_base() and pattern resolution.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: reworking path resolvers from multi-base + external-permission model to workspace-rooted semantics.
Description check ✅ Passed The description provides clear structure with Summary, Motivation, Changes, and Stats sections that comprehensively document the PR objectives and changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 rework-external-directory

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

🧹 Nitpick comments (2)
src/llm-coding-tools-core/src/workspace.rs (1)

71-85: Test modifies process-wide CWD, which can interfere with parallel test execution.

std::env::set_current_dir affects all threads in the process. If cargo test runs tests in parallel, this could cause flaky failures in other tests that depend on the current directory.

Consider using #[serial] from the serial_test crate, or restructuring the test to avoid CWD manipulation (e.g., spawning a subprocess or accepting this as an integration test limitation).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/llm-coding-tools-core/src/workspace.rs` around lines 71 - 85, The test
resolve_workspace_root_should_fall_back_to_cwd_when_not_in_repo mutates
process-wide CWD via std::env::set_current_dir (using TempDir) which can break
parallel tests; fix it by either marking the test to run serially (add #[serial]
from the serial_test crate to the test function) or avoid changing the process
CWD altogether by running resolve_workspace_root in a subprocess/helper child
process (spawn a Command that sets its own cwd to TempDir and invokes a small
test helper that calls resolve_workspace_root and prints the result) so the main
test harness CWD is not mutated.
src/llm-coding-tools-core/README.md (1)

90-104: Consider using builder_with_base in the documentation example for consistency.

The example creates AllowedGlobResolver::new(&root) but then uses GlobPolicy::builder() without a base path. This means the policy patterns (src/**, *.rs, target/**) are matched verbatim rather than being joined with the workspace root.

While this works because AllowedGlobResolver normalizes paths before checking policy, the pattern semantics differ from what's documented in the "Permission glob semantics" section (lines 107-116), which states "relative patterns are implicitly joined with the workspace root."

For documentation consistency, consider either:

  1. Using GlobPolicy::builder_with_base(&root)? in the example, or
  2. Adding a note clarifying that builder() vs builder_with_base() have different pattern semantics.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/llm-coding-tools-core/README.md` around lines 90 - 104, Update the README
example so the policy patterns are joined with the workspace root by replacing
GlobPolicy::builder() with GlobPolicy::builder_with_base(&root)? (referencing
AllowedGlobResolver::new and GlobPolicy::builder_with_base) so the example
matches the "Permission glob semantics" description; alternatively, if you
prefer not to change the example, add a short clarifying note explaining the
difference between GlobPolicy::builder() (verbatim patterns) and
GlobPolicy::builder_with_base(&root)? (workspace-root-joined relative patterns)
and why one is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/llm-coding-tools-core/src/workspace.rs`:
- Around line 20-33: The function resolve_workspace_root must not fall back to a
relative PathBuf; change its signature to return Result<PathBuf, std::io::Error>
(or Option<PathBuf>) and propagate std::env::current_dir()’s error instead of
using unwrap_or_else; inside resolve_workspace_root call
std::env::current_dir()? to obtain an absolute cwd (returning Err on failure),
then run the existing upward .git search on that cwd and return
Ok(candidate.to_path_buf() or Ok(cwd) as before so the function’s callers
receive a guaranteed absolute PathBuf or an error they can handle.

---

Nitpick comments:
In `@src/llm-coding-tools-core/README.md`:
- Around line 90-104: Update the README example so the policy patterns are
joined with the workspace root by replacing GlobPolicy::builder() with
GlobPolicy::builder_with_base(&root)? (referencing AllowedGlobResolver::new and
GlobPolicy::builder_with_base) so the example matches the "Permission glob
semantics" description; alternatively, if you prefer not to change the example,
add a short clarifying note explaining the difference between
GlobPolicy::builder() (verbatim patterns) and
GlobPolicy::builder_with_base(&root)? (workspace-root-joined relative patterns)
and why one is used.

In `@src/llm-coding-tools-core/src/workspace.rs`:
- Around line 71-85: The test
resolve_workspace_root_should_fall_back_to_cwd_when_not_in_repo mutates
process-wide CWD via std::env::set_current_dir (using TempDir) which can break
parallel tests; fix it by either marking the test to run serially (add #[serial]
from the serial_test crate to the test function) or avoid changing the process
CWD altogether by running resolve_workspace_root in a subprocess/helper child
process (spawn a Command that sets its own cwd to TempDir and invokes a small
test helper that calls resolve_workspace_root and prints the result) so the main
test harness CWD is not mutated.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82f4643e-2e81-4c61-a26c-61ec41ec59ae

📥 Commits

Reviewing files that changed from the base of the PR and between 3bacd1e and 4b11d4f.

📒 Files selected for processing (9)
  • src/llm-coding-tools-agents/README.md
  • src/llm-coding-tools-core/README.md
  • src/llm-coding-tools-core/benches/path_resolvers.rs
  • src/llm-coding-tools-core/src/lib.rs
  • src/llm-coding-tools-core/src/path/allowed.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/mod.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/policy.rs
  • src/llm-coding-tools-core/src/path/mod.rs
  • src/llm-coding-tools-core/src/workspace.rs
💤 Files with no reviewable changes (1)
  • src/llm-coding-tools-core/src/path/mod.rs
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-03-28T02:14:04.465Z
Learnt from: Sewer56
Repo: Sewer56/llm-coding-tools PR: 69
File: src/llm-coding-tools-bubblewrap/src/profile/validation.rs:57-67
Timestamp: 2026-03-28T02:14:04.465Z
Learning: In `src/llm-coding-tools-bubblewrap/src/profile/` (Rust, llm-coding-tools-bubblewrap crate), the `Builder` API paths (workspace, synthetic_home, cache_root, mount lists, overlays, etc.) are always set by trusted application/operator code — the library consumer is the trusted party. Path normalization and `..`-component hardening in validators like `validate_absolute_path` is therefore NOT required to defend against traversal attacks. Untrusted input (LLM-generated shell commands) only enters through `wrap_command`/`execute_command_with_mode`, not through the `Builder`.

Applied to files:

  • src/llm-coding-tools-core/src/lib.rs
  • src/llm-coding-tools-core/src/workspace.rs
  • src/llm-coding-tools-core/README.md
  • src/llm-coding-tools-core/benches/path_resolvers.rs
  • src/llm-coding-tools-core/src/path/allowed.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/policy.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/mod.rs
🔇 Additional comments (12)
src/llm-coding-tools-core/src/path/allowed.rs (2)

28-28: LGTM! Unified rejection path with consistent error messaging.

The removal of external permission fallback simplifies the resolution logic. All rejection paths now flow through the not_allowed() helper, returning a consistent ToolError::InvalidPath message. The documentation accurately reflects the new behavior.

Also applies to: 199-199, 232-238


334-345: LGTM! Test updated to match new semantics.

The renamed test rejects_absolute_path_outside_all_bases correctly verifies that absolute paths outside configured directories are now rejected with InvalidPath instead of being handled via external permissions.

src/llm-coding-tools-agents/README.md (1)

113-115: LGTM! Clear documentation of glob semantics.

The added documentation succinctly explains the glob pattern behavior (* vs **), that bare allow maps to **, and that patterns are workspace-relative. This aligns with the implementation in GlobPolicy::builder_with_base().

src/llm-coding-tools-core/src/lib.rs (1)

23-23: LGTM! Clean module addition and re-export.

The new workspace module and resolve_workspace_root re-export follow the crate's existing pattern of exposing key functionality at the root level for ergonomic imports.

Also applies to: 33-33

src/llm-coding-tools-core/benches/path_resolvers.rs (1)

100-102: LGTM! Benchmarks updated to use new single-root API.

The build_policy helper now correctly canonicalizes the current directory before passing to builder_with_base(), and AllowedGlobResolver::new() calls reflect the new single workspace root signature. The benchmark structure remains sound.

Also applies to: 154-159

src/llm-coding-tools-core/README.md (1)

107-116: LGTM! Clear documentation of glob semantics.

The new "Permission glob semantics" section clearly explains the * vs ** behavior, bare allow expansion, relative/absolute pattern handling, and last-match-wins ordering. This is valuable user-facing documentation.

src/llm-coding-tools-core/src/path/allowed_glob/policy.rs (3)

104-110: LGTM! Clean implementation of builder_with_base.

The method correctly shell-expands the base path before storing it, and the builder structure properly handles both relative (joined with base) and absolute (pass-through) patterns in resolve_pattern().


197-212: LGTM! Pattern resolution logic is correct.

The resolve_pattern method correctly:

  1. Expands shell variables in the pattern
  2. Preserves absolute patterns unchanged
  3. Joins relative patterns with the base path when present
  4. Normalizes to forward slashes for cross-platform glob matching

430-455: LGTM! Comprehensive test coverage for shell expansion edge cases.

The tests properly use temp_env::with_var to isolate environment variable changes, covering both successful expansion and failure cases for unset variables.

Also applies to: 531-545

src/llm-coding-tools-core/src/path/allowed_glob/mod.rs (3)

57-62: LGTM! Clean refactor to single workspace root.

The AllowedGlobResolver now stores a single workspace_root: Arc<Path> instead of multiple base directories. The constructor properly expands shell patterns, validates the directory exists, and canonicalizes the path. This simplifies the mental model and aligns with workspace-rooted semantics.

Also applies to: 83-106


200-249: LGTM! Resolution tiers correctly enforce containment and policy.

Each resolution tier (canonicalize, new-file fast path, soft_canonicalize) properly:

  1. Checks that the resolved path stays within workspace_root via starts_with()
  2. Normalizes the path and checks glob policy against the absolute path
  3. Returns early on rejection with consistent error messaging

The pattern of checking policy against normalize_path(&resolved) ensures glob patterns match the same path format used during policy construction via builder_with_base().


276-285: LGTM! Test helper correctly demonstrates intended usage pattern.

The resolver_with_policy helper shows the recommended pattern: create policy with builder_with_base(&root) so relative patterns are joined with the workspace root, matching how AllowedGlobResolver evaluates paths against policy.

Comment thread src/llm-coding-tools-core/src/workspace.rs Outdated
The multi-base + external_permission fallback model introduced complexity
(canonicalization edge cases, permission bypass surface, cross-platform path
comparison issues) without clear benefit over workspace-rooted globs.

- Add resolve_workspace_root() (git root > cwd) in new workspace module
- Rework AllowedGlobResolver from multi-base-directory to single workspace root
- Remove external_permission fallback from AllowedPathResolver and AllowedGlobResolver
- Add GlobPolicy::builder_with_base() for workspace-rooted pattern resolution
- Update benchmarks and documentation for new workspace-rooted semantics
@Sewer56 Sewer56 force-pushed the rework-external-directory branch from 4b11d4f to 227a56d Compare April 10, 2026 20:36
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/llm-coding-tools-core/benches/path_resolvers.rs (1)

142-150: ⚠️ Potential issue | 🟡 Minor

Complex-policy benchmark became materially more permissive.

Switching from src/**/*.rs to src/** changes what is matched (and likely match cost), which can make benchmark comparisons less meaningful.

Suggested benchmark policy adjustment
 let complex_policy = build_policy(|b| {
-    b.allow("src/**")?
+    b.allow("src/**/*.rs")?
         .deny("target/**")?
         .allow("*.toml")?
         .deny("*.log")?
         .allow("benches/**")?
         .deny("**/test_data/**")?
         .allow("tests/**/*.rs")?
         .deny("node_modules/**")?
         .allow("examples/**")
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/llm-coding-tools-core/benches/path_resolvers.rs` around lines 142 - 150,
The benchmark policy became more permissive by changing the pattern from
"src/**/*.rs" to "src/**"; revert the matcher on the builder chain (the calls on
variable b: allow("src/**")? ... .allow("examples/**")) back to the original
more specific Rust-only pattern (e.g., allow("src/**/*.rs")?) so the benchmark
matches and cost remain comparable, ensuring the same sequence of allow/deny
calls (on b) preserves previous specificity and ordering.
🧹 Nitpick comments (2)
src/llm-coding-tools-core/src/path/allowed_glob/mod.rs (2)

207-245: Extract repeated containment/policy validation into a helper.

The same validation block is duplicated in all three tiers, which makes future edits error-prone.

Refactor sketch
+fn validate_resolved(
+    workspace_root: &Path,
+    policy: Option<&GlobPolicy>,
+    path: &str,
+    resolved: PathBuf,
+) -> ToolResult<PathBuf> {
+    if !resolved.starts_with(workspace_root) {
+        return Err(reject(path));
+    }
+    if let Some(policy) = policy {
+        let abs_str = normalize_path(&resolved);
+        if !policy.is_allowed(abs_str.as_ref()) {
+            return Err(reject(path));
+        }
+    }
+    Ok(resolved)
+}
+
 fn resolve_candidate(
     workspace_root: &Path,
     policy: Option<&GlobPolicy>,
     path: &str,
     candidate: &Path,
 ) -> ToolResult<PathBuf> {
-    if let Ok(resolved) = candidate.canonicalize() {
-        if !resolved.starts_with(workspace_root) {
-            return Err(reject(path));
-        }
-        if let Some(policy) = policy {
-            let abs_str = normalize_path(&resolved);
-            if !policy.is_allowed(abs_str.as_ref()) {
-                return Err(reject(path));
-            }
-        }
-        return Ok(resolved);
-    }
+    if let Ok(resolved) = candidate.canonicalize() {
+        return validate_resolved(workspace_root, policy, path, resolved);
+    }
 
-    if let Some(resolved) = resolve_new_file_fast(candidate) {
-        ...
-    }
+    if let Some(resolved) = resolve_new_file_fast(candidate) {
+        return validate_resolved(workspace_root, policy, path, resolved);
+    }
 
-    if let Ok(resolved) = soft_canonicalize(candidate) {
-        ...
-    }
+    if let Ok(resolved) = soft_canonicalize(candidate) {
+        return validate_resolved(workspace_root, policy, path, resolved);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/llm-coding-tools-core/src/path/allowed_glob/mod.rs` around lines 207 -
245, The three branches that handle candidate.canonicalize(),
resolve_new_file_fast(), and soft_canonicalize() duplicate the same containment
and policy checks; extract that logic into a single helper (e.g.,
validate_resolved(resolved: PathBuf, path: &Path, workspace_root: &Path, policy:
Option<&Policy>) -> Result<PathBuf, Error>) and call it from each branch instead
of repeating normalize_path(), starts_with(workspace_root) and
policy.is_allowed(...) and the reject(path) error construction; update the
branches to return the helper's Result so behavior remains identical.

120-124: Consider a debug assertion in from_canonical for absolute input.

Since this constructor intentionally skips validation, a debug-time guard helps catch accidental misuse early.

Small defensive tweak
 pub fn from_canonical(workspace_root: impl AsRef<Path>) -> Self {
+    debug_assert!(
+        workspace_root.as_ref().is_absolute(),
+        "AllowedGlobResolver::from_canonical expects an absolute canonical path"
+    );
     Self {
         workspace_root: Arc::from(workspace_root.as_ref()),
         policy: None,
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/llm-coding-tools-core/src/path/allowed_glob/mod.rs` around lines 120 -
124, Add a debug-time guard in AllowedGlob::from_canonical to ensure the
provided workspace_root is absolute: in the from_canonical function add a
debug_assert that workspace_root.as_ref().is_absolute() with a short message
like "from_canonical expects an absolute workspace_root"; this keeps the
constructor fast but catches accidental misuse during development while leaving
release behavior unchanged.
🤖 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/llm-coding-tools-core/benches/path_resolvers.rs`:
- Around line 142-150: The benchmark policy became more permissive by changing
the pattern from "src/**/*.rs" to "src/**"; revert the matcher on the builder
chain (the calls on variable b: allow("src/**")? ... .allow("examples/**")) back
to the original more specific Rust-only pattern (e.g., allow("src/**/*.rs")?) so
the benchmark matches and cost remain comparable, ensuring the same sequence of
allow/deny calls (on b) preserves previous specificity and ordering.

---

Nitpick comments:
In `@src/llm-coding-tools-core/src/path/allowed_glob/mod.rs`:
- Around line 207-245: The three branches that handle candidate.canonicalize(),
resolve_new_file_fast(), and soft_canonicalize() duplicate the same containment
and policy checks; extract that logic into a single helper (e.g.,
validate_resolved(resolved: PathBuf, path: &Path, workspace_root: &Path, policy:
Option<&Policy>) -> Result<PathBuf, Error>) and call it from each branch instead
of repeating normalize_path(), starts_with(workspace_root) and
policy.is_allowed(...) and the reject(path) error construction; update the
branches to return the helper's Result so behavior remains identical.
- Around line 120-124: Add a debug-time guard in AllowedGlob::from_canonical to
ensure the provided workspace_root is absolute: in the from_canonical function
add a debug_assert that workspace_root.as_ref().is_absolute() with a short
message like "from_canonical expects an absolute workspace_root"; this keeps the
constructor fast but catches accidental misuse during development while leaving
release behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30581cea-be01-4400-9fd6-8331a8aa2715

📥 Commits

Reviewing files that changed from the base of the PR and between 4b11d4f and 227a56d.

📒 Files selected for processing (9)
  • src/llm-coding-tools-agents/README.md
  • src/llm-coding-tools-core/README.md
  • src/llm-coding-tools-core/benches/path_resolvers.rs
  • src/llm-coding-tools-core/src/lib.rs
  • src/llm-coding-tools-core/src/path/allowed.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/mod.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/policy.rs
  • src/llm-coding-tools-core/src/path/mod.rs
  • src/llm-coding-tools-core/src/workspace.rs
💤 Files with no reviewable changes (1)
  • src/llm-coding-tools-core/src/path/mod.rs
✅ Files skipped from review due to trivial changes (2)
  • src/llm-coding-tools-agents/README.md
  • src/llm-coding-tools-core/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/llm-coding-tools-core/src/lib.rs
  • src/llm-coding-tools-core/src/workspace.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/policy.rs
  • src/llm-coding-tools-core/src/path/allowed.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Blocking Windows
  • GitHub Check: Blocking macOS
  • GitHub Check: Async Windows
  • GitHub Check: Blocking Linux
  • GitHub Check: Async Linux
  • GitHub Check: Async macOS
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-03-28T02:14:04.465Z
Learnt from: Sewer56
Repo: Sewer56/llm-coding-tools PR: 69
File: src/llm-coding-tools-bubblewrap/src/profile/validation.rs:57-67
Timestamp: 2026-03-28T02:14:04.465Z
Learning: In `src/llm-coding-tools-bubblewrap/src/profile/` (Rust, llm-coding-tools-bubblewrap crate), the `Builder` API paths (workspace, synthetic_home, cache_root, mount lists, overlays, etc.) are always set by trusted application/operator code — the library consumer is the trusted party. Path normalization and `..`-component hardening in validators like `validate_absolute_path` is therefore NOT required to defend against traversal attacks. Untrusted input (LLM-generated shell commands) only enters through `wrap_command`/`execute_command_with_mode`, not through the `Builder`.

Applied to files:

  • src/llm-coding-tools-core/benches/path_resolvers.rs
  • src/llm-coding-tools-core/src/path/allowed_glob/mod.rs
🔇 Additional comments (2)
src/llm-coding-tools-core/src/path/allowed_glob/mod.rs (1)

176-248: Workspace-root enforcement is consistently applied across all resolution tiers.

Nice work here: lexical escape rejection + per-tier containment checks + policy checks make the new workspace-root model robust.

src/llm-coding-tools-core/benches/path_resolvers.rs (1)

100-101: builder_with_base and single-root resolver wiring looks correct.

These updates align cleanly with the new workspace-rooted resolver API.

Also applies to: 154-159

Sewer56 added 3 commits April 10, 2026 22:52
Consolidate identical containment and policy checks from three
resolution branches (canonicalize, resolve_new_file_fast, soft_canonicalize)
into a single validate_resolved helper function.
Unix-style /some/path is not absolute on Windows (no drive letter),
causing three tests to fail. Use cfg!(windows) to pick appropriate
paths and normalize test paths through the same pipeline as production code.
@Sewer56 Sewer56 force-pushed the rework-external-directory branch from f4bd68f to 1e9e0c6 Compare April 10, 2026 22:19
@Sewer56 Sewer56 merged commit 7ca279e into main Apr 10, 2026
21 checks passed
@Sewer56 Sewer56 deleted the rework-external-directory branch April 10, 2026 22:23
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