Skip to content

Commit e4d0660

Browse files
committed
Added: AllowedGlobResolver for fine-grained path sandboxing with glob patterns
Implements ordered allow/deny glob policy for directory-restricted path resolution. - Add AllowedGlobResolver with glob pattern matching support - Enforces root containment (all resolved paths stay within allowed roots) - Rules evaluated in order: first match wins (allow or deny) - GlobPolicyBuilder for ergonomic rule construction - Export from path module and crate root (llm-coding-tools-core) - Re-export from serdesai for convenience - Comprehensive tests covering policy evaluation, edge cases, and error handling - Update README with usage examples
1 parent 3a2560c commit e4d0660

11 files changed

Lines changed: 878 additions & 10 deletions

File tree

src/Cargo.lock

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/llm-coding-tools-core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ serde_json = "1.0"
4444
# Zero overhead compile time bitflag generation
4545
bitflags = "2.11.0"
4646

47+
# Shell-like expansion for home directory paths (~/ and $HOME/)
48+
shellexpand = "3.1.2"
49+
4750
# Fast binary serialization for catalog cache types
4851
bitcode = "0.6.9"
4952

src/llm-coding-tools-core/README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,14 @@ Path-based tools are generic over [`PathResolver`], so wrappers can choose unres
6969

7070
- [`AbsolutePathResolver`] enforces absolute-path inputs (unrestricted mode).
7171
- [`AllowedPathResolver`] constrains operations to configured directories (sandbox mode).
72+
- [`AllowedGlobResolver`] constrains to directories with glob pattern filtering (fine-grained sandbox mode).
7273
- Failed resolution rejects traversal and out-of-sandbox paths before tool execution.
7374

7475
```rust,no_run
75-
use llm_coding_tools_core::{AbsolutePathResolver, AllowedPathResolver, PathResolver, ToolResult};
76+
use llm_coding_tools_core::{
77+
path::{AllowedGlobResolver, GlobPolicy, RuleAction},
78+
AbsolutePathResolver, AllowedPathResolver, PathResolver, ToolResult,
79+
};
7680
7781
fn demo() -> ToolResult<()> {
7882
// Unrestricted mode: any absolute path is allowed.
@@ -82,6 +86,19 @@ fn demo() -> ToolResult<()> {
8286
// Sandboxed mode: only configured directories are allowed.
8387
let sandbox = AllowedPathResolver::new(["/workspace/project", "/tmp"])?;
8488
let _lib = sandbox.resolve("src/lib.rs")?;
89+
90+
// Fine-grained sandbox (last-match-wins).
91+
let glob = AllowedGlobResolver::new(["/workspace/project"])?
92+
.with_policy(
93+
GlobPolicy::builder()
94+
.add("src/**", RuleAction::Allow)? // Matches src/lib.rs
95+
.add("*.rs", RuleAction::Allow)? // Also matches src/lib.rs
96+
.add("target/**", RuleAction::Deny)? // Blocks target/ even if *.rs matches
97+
.build()?
98+
);
99+
let _lib = glob.resolve("src/lib.rs")?;
100+
let _main = glob.resolve("main.rs")?;
101+
// glob.resolve("target/debug/app")?; // Denied
85102
Ok(())
86103
}
87104
```
@@ -313,6 +330,7 @@ let key = resolver.resolve("OPENAI_API_KEY");
313330
[`ToolContext`]: crate::context::ToolContext
314331
[`PathResolver`]: crate::PathResolver
315332
[`AbsolutePathResolver`]: crate::AbsolutePathResolver
333+
[`AllowedGlobResolver`]: crate::path::AllowedGlobResolver
316334
[`AllowedPathResolver`]: crate::AllowedPathResolver
317335
[`permissions`]: crate::permissions
318336
[`Rule`]: crate::permissions::Rule

src/llm-coding-tools-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub use context::ToolContext;
2626
pub use credentials::{CredentialLookup, CredentialResolver};
2727
pub use error::{ToolError, ToolResult};
2828
pub use output::ToolOutput;
29-
pub use path::{AbsolutePathResolver, AllowedPathResolver, PathResolver};
29+
pub use path::{AbsolutePathResolver, AllowedGlobResolver, AllowedPathResolver, PathResolver};
3030
pub use system_prompt::SystemPromptBuilder;
3131

3232
// Re-export tools (always available, sync or async based on runtime feature)

src/llm-coding-tools-core/src/path/allowed.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,15 @@ impl AllowedPathResolver {
6060
})
6161
}
6262

63-
/// Creates a resolver from already-canonicalized paths.
63+
/// Creates a resolver from already-canonicalized paths, skipping
64+
/// filesystem validation.
6465
///
65-
/// Use this when paths are known to be valid and canonicalized,
66-
/// skipping the filesystem check.
66+
/// A canonical path is absolute, with all symlinks resolved and all
67+
/// `.` and `..` components normalized. Use [`std::fs::canonicalize`] or
68+
/// [`std::path::Path::canonicalize`] to canonicalize paths.
69+
///
70+
/// Use this when paths are known to be valid and canonicalized, skipping
71+
/// the filesystem check.
6772
///
6873
/// # Safety
6974
///

0 commit comments

Comments
 (0)