Skip to content

fix(tools): safety guard URL path handling and security enhancements#1217

Closed
cornjosh wants to merge 3 commits intosipeed:mainfrom
cornjosh:fix/safety-guard-url-path
Closed

fix(tools): safety guard URL path handling and security enhancements#1217
cornjosh wants to merge 3 commits intosipeed:mainfrom
cornjosh:fix/safety-guard-url-path

Conversation

@cornjosh
Copy link
Copy Markdown
Contributor

@cornjosh cornjosh commented Mar 7, 2026

📝 Description

Fix shell tool safety guard to correctly handle remote URLs and enhance security against local file access attacks.

Bug Fixes:

  • Fixed remote URLs (https://, http://, ftp://, etc.) being incorrectly matched as local file paths, causing commands like curl https://github.com to be blocked
  • Fixed relative paths like ./download/file being partially matched as absolute paths

Security Enhancements:

  • Block file:// protocol to prevent reading local files outside workspace
  • Block sensitive /proc paths that may expose credentials (/proc/self/environ, /proc/self/cmdline, /proc/self/fd/*, /proc/self/mem, /proc/self/maps, /proc/*/environ)
  • Add safe read-only procfs paths to whitelist (/proc/cpuinfo, /proc/meminfo, /proc/version, /proc/uptime, /proc/loadavg, /proc/self/status)

Tests:

  • Added comprehensive test coverage with 7 new test functions and 228 lines of test code:
    • TestShellTool_RemoteURLsAllowed - Verify remote URLs are not blocked
    • TestShellTool_RemotePathInSSHAllowed - Verify SSH/SCP remote path handling
    • TestShellTool_FileProtocolBlocked - Verify file:// protocol is blocked
    • TestShellTool_LocalAbsolutePathBlocked - Verify local paths outside workspace are blocked
    • TestShellTool_SafeProcfsAllowed - Verify safe procfs paths are allowed
    • TestShellTool_SensitiveProcfsBlocked - Verify sensitive procfs paths are blocked
    • TestShellTool_MixedScenarios - Verify commands with both URLs and local paths
    • TestShellTool_PathTraversalBlocked - Verify path traversal attempts are blocked

🗣️ Type of Change

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 📖 Documentation update
  • ⚡ Code refactoring (no functional changes, no api changes)

🤖 AI Code Generation

  • 🤖 Fully AI-generated (100% AI, 0% Human)
  • 🛠️ Mostly AI-generated (AI draft, Human verified/modified)
  • 👨‍💻 Mostly Human-written (Human lead, AI assisted or none)

🔗 Related Issue

Fixes #1203, Fixes #1193

📚 Technical Context (Skip for Docs)

  • Reference URL: N/A
  • Reasoning:
    • The absolutePathPattern regex /[^\s\"']+ was incorrectly matching URL paths (e.g., /user/repo in https://github.com/user/repo) and relative path components (e.g., /download/file in ./download/file)
    • Solution: Add urlPattern to strip URLs before path matching, and update absolutePathPattern to require Unix paths be preceded by whitespace, =, " or start of string
    • Security improvements follow defense-in-depth principles for shell command execution

🧪 Test Environment

  • Hardware: WSL2 on Windows 11
  • OS: Linux 5.15.167.4-microsoft-standard-WSL2 (Ubuntu 22.04)
  • Model/Provider: N/A
  • Channels: N/A

📸 Evidence (Optional)

Click to view Test Results
=== RUN   TestShellTool_RemoteURLsAllowed
--- PASS: TestShellTool_RemoteURLsAllowed (63.08s)
=== RUN   TestShellTool_RemotePathInSSHAllowed
--- PASS: TestShellTool_RemotePathInSSHAllowed (0.00s)
=== RUN   TestShellTool_FileProtocolBlocked
--- PASS: TestShellTool_FileProtocolBlocked (0.00s)
=== RUN   TestShellTool_LocalAbsolutePathBlocked
--- PASS: TestShellTool_LocalAbsolutePathBlocked (0.00s)
=== RUN   TestShellTool_SafeProcfsAllowed
--- PASS: TestShellTool_SafeProcfsAllowed (0.01s)
=== RUN   TestShellTool_SensitiveProcfsBlocked
--- PASS: TestShellTool_SensitiveProcfsBlocked (0.00s)
=== RUN   TestShellTool_MixedScenarios
--- PASS: TestShellTool_MixedScenarios (0.51s)
=== RUN   TestShellTool_PathTraversalBlocked
--- PASS: TestShellTool_PathTraversalBlocked (0.00s)
...
ok  	github.com/sipeed/picoclaw/pkg/tools	65.327s

☑️ Checklist

  • My code/docs follow the style of this project.
  • I have performed a self-review of my own changes.
  • I have updated the documentation accordingly.

…ipeed#1203, sipeed#1193)

- Fix remote URLs being incorrectly matched as local file paths
- Fix relative paths like ./download/file being partially matched
- Block file:// protocol to prevent local file access
- Block sensitive /proc paths (environ, cmdline, fd, mem, maps)
- Add safe procfs paths to whitelist (cpuinfo, meminfo, etc.)
- Add comprehensive test coverage (7 new test functions)
Copilot AI review requested due to automatic review settings March 7, 2026 16:34
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 7, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the ExecTool safety guard to avoid misclassifying remote URLs as local filesystem paths, and adds additional blocking/allowlisting for file:// and selected /proc paths to reduce local file exfiltration risk.

Changes:

  • Strip URLs before absolute-path detection and tighten the absolute path regex to reduce false positives with URL paths and relative paths.
  • Add deny patterns for file:// and sensitive /proc paths; add a whitelist of “safe” read-only procfs paths.
  • Add new tests covering URL handling, file protocol blocking, procfs allow/block behavior, and mixed scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
pkg/tools/shell.go Adjusts regex-based safety guard path detection, adds URL stripping, and expands deny/safe path sets (file:// and /proc).
pkg/tools/shell_test.go Adds multiple new test cases intended to validate the updated safety guard behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/tools/shell.go Outdated
Comment on lines +89 to +91
// Unix paths must be preceded by whitespace, '=', '"' or start of string
// to avoid matching paths inside relative paths like "./download/file".
absolutePathPattern = regexp.MustCompile(`(?:^|[\s="])(/[^\s\"']+)|([A-Za-z]:\\[^\\\"']+)`)
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

The updated absolutePathPattern no longer matches absolute paths when they are single-quoted (e.g. cat '/etc/passwd') or concatenated to a short flag (e.g. curl -o/tmp/file ...). This creates a workspace-restriction bypass where absolute paths outside the workspace won’t be detected. Consider expanding the allowed prefix delimiters to include single quotes and common flag forms, or switching to tokenization of shell words (so absolute paths are detected regardless of quoting/flag syntax) while still excluding URL components.

Suggested change
// Unix paths must be preceded by whitespace, '=', '"' or start of string
// to avoid matching paths inside relative paths like "./download/file".
absolutePathPattern = regexp.MustCompile(`(?:^|[\s="])(/[^\s\"']+)|([A-Za-z]:\\[^\\\"']+)`)
// Unix paths must be preceded by whitespace, '=', '"', '\'', a short flag (e.g. "-o"),
// or start of string to avoid matching paths inside relative paths like "./download/file".
absolutePathPattern = regexp.MustCompile(`(?:^|[\s="'"]|-[A-Za-z]*)(/[^\s\"']+)|([A-Za-z]:\\[^\\\"']+)`)

Copilot uses AI. Check for mistakes.
Comment thread pkg/tools/shell.go Outdated
regexp.MustCompile(`\bssh\b.*@`),
regexp.MustCompile(`\beval\b`),
regexp.MustCompile(`\bsource\s+.*\.sh\b`),
// file:// protocol allows reading local files outside workspace
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

These new deny patterns are bypassable whenever a command matches a configured CustomAllowPattern, because guardCommand skips all denyPatterns when explicitlyAllowed is true. Given file:// is being added specifically to prevent local file access, consider treating file:// (and other filesystem escape checks) as a non-overridable hard deny, or at least document that CustomAllowPatterns disables this protection.

Suggested change
// file:// protocol allows reading local files outside workspace
// file:// protocol can be used to read local files outside the workspace.
// Note: this deny pattern is only enforced when the command is not explicitly
// allowed by CustomAllowPatterns; configuring custom allow patterns may
// therefore disable this protection.

Copilot uses AI. Check for mistakes.
Comment thread pkg/tools/shell.go Outdated
Comment on lines +79 to +85
// Sensitive /proc paths that may expose credentials or process info
regexp.MustCompile(`/proc/self/environ\b`),
regexp.MustCompile(`/proc/self/cmdline\b`),
regexp.MustCompile(`/proc/self/fd/\d+`),
regexp.MustCompile(`/proc/self/mem\b`),
regexp.MustCompile(`/proc/self/maps\b`),
regexp.MustCompile(`/proc/\d+/environ\b`),
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

The /proc deny patterns currently run against the raw command string, so a remote URL containing /proc/... (e.g. curl https://example.com/proc/self/environ) would be blocked even though it doesn’t access local procfs. If the intent is to block local file access, consider applying these /proc checks after stripping URLs (similar to cmdWithoutURLs), or otherwise restricting the regex to contexts that represent local filesystem paths.

Suggested change
// Sensitive /proc paths that may expose credentials or process info
regexp.MustCompile(`/proc/self/environ\b`),
regexp.MustCompile(`/proc/self/cmdline\b`),
regexp.MustCompile(`/proc/self/fd/\d+`),
regexp.MustCompile(`/proc/self/mem\b`),
regexp.MustCompile(`/proc/self/maps\b`),
regexp.MustCompile(`/proc/\d+/environ\b`),
// Sensitive /proc paths that may expose credentials or process info.
// Restrict matches to contexts where /proc paths are used as local filesystem paths,
// not as substrings of remote URLs (e.g. https://example.com/proc/self/environ).
regexp.MustCompile(`(?:^|[\s="])/proc/self/environ\b`),
regexp.MustCompile(`(?:^|[\s="])/proc/self/cmdline\b`),
regexp.MustCompile(`(?:^|[\s="])/proc/self/fd/\d+`),
regexp.MustCompile(`(?:^|[\s="])/proc/self/mem\b`),
regexp.MustCompile(`(?:^|[\s="])/proc/self/maps\b`),
regexp.MustCompile(`(?:^|[\s="])/proc/\d+/environ\b`),

Copilot uses AI. Check for mistakes.
Comment thread pkg/tools/shell_test.go Outdated
Comment on lines +457 to +471
allowed := []string{
"curl https://github.com/user/repo",
"wget http://example.com/file.tar.gz",
"git clone https://github.com/user/repo.git",
"agent-browser open https://github.com",
"pip install git+https://github.com/user/pkg",
"curl ftp://ftp.example.com/file",
"curl sftp://server.com/path/file",
"curl HTTPS://github.com/user/repo", // case insensitive
"curl HTTP://example.com/file", // case insensitive
}

for _, cmd := range allowed {
result := tool.Execute(context.Background(), map[string]any{"command": cmd})
if result.IsError && strings.Contains(result.ForLLM, "path outside working dir") {
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

This test executes real network/system-modifying commands (curl, wget, git clone, and especially pip install ...) in order to validate the guard. That makes the unit test suite slow/flaky and can mutate the test environment. Instead, validate the guard logic without executing the command (e.g., call the unexported tool.guardCommand directly, or run a harmless echo command containing a URL). Also consider asserting that the guard result is empty rather than only checking for the specific "path outside working dir" substring.

Suggested change
allowed := []string{
"curl https://github.com/user/repo",
"wget http://example.com/file.tar.gz",
"git clone https://github.com/user/repo.git",
"agent-browser open https://github.com",
"pip install git+https://github.com/user/pkg",
"curl ftp://ftp.example.com/file",
"curl sftp://server.com/path/file",
"curl HTTPS://github.com/user/repo", // case insensitive
"curl HTTP://example.com/file", // case insensitive
}
for _, cmd := range allowed {
result := tool.Execute(context.Background(), map[string]any{"command": cmd})
if result.IsError && strings.Contains(result.ForLLM, "path outside working dir") {
// Use harmless echo commands that still contain the same URL patterns,
// so we exercise the guard logic without performing network operations.
allowed := []string{
"echo curl https://github.com/user/repo",
"echo wget http://example.com/file.tar.gz",
"echo git clone https://github.com/user/repo.git",
"echo agent-browser open https://github.com",
"echo pip install git+https://github.com/user/pkg",
"echo curl ftp://ftp.example.com/file",
"echo curl sftp://server.com/path/file",
"echo curl HTTPS://github.com/user/repo", // case insensitive
"echo curl HTTP://example.com/file", // case insensitive
}
for _, cmd := range allowed {
result := tool.Execute(context.Background(), map[string]any{"command": cmd})
// URL commands should not be blocked by the workspace/path guard.
if result.IsError {
t.Errorf("URL command should not fail or be blocked: %s\n error: %s", cmd, result.ForLLM)
}
if strings.Contains(result.ForLLM, "path outside working dir") {

Copilot uses AI. Check for mistakes.
Comment thread pkg/tools/shell_test.go Outdated
Comment on lines +486 to +498
// SSH/SCP with remote paths - the /path/file is on the remote host.
// Note: "ssh user@host" is blocked by deny pattern, so we test the path logic
// only by checking that /path/file in scp/rsync syntax is not blocked.
allowed := []string{
"rsync /local/file ./dest", // local absolute path in workspace check
"rsync ./src/ /tmp/", // /tmp/ should be blocked
}

for _, cmd := range allowed {
result := tool.Execute(context.Background(), map[string]any{"command": cmd})
// We just verify no crash and proper handling
_ = result
}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

TestShellTool_RemotePathInSSHAllowed doesn’t assert any behavior (it discards result), so it won’t fail even if the guard regresses. Also, the sample commands don’t exercise remote-path SSH/SCP syntax (e.g. user@host:/path) and include comments indicating some should be blocked. Consider rewriting this test to assert on guardCommand outputs for actual remote-path forms, and to explicitly check allowed vs blocked cases.

Copilot uses AI. Check for mistakes.
Comment thread pkg/tools/shell.go Outdated
Comment on lines +358 to +360
// Remove URLs before matching paths to avoid treating https://github.com as a file path
cmdWithoutURLs := urlPattern.ReplaceAllString(cmd, "")

Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

URL stripping is applied only for absolute-path matching, but the earlier path-traversal check (../ / ..\) still runs on the raw command. As a result, commands containing URLs with ../ segments (e.g. curl https://example.com/a/../b) will still be blocked even though they don’t reference local paths. Consider applying URL stripping (or a more targeted traversal check) before the traversal detection as well.

Copilot uses AI. Check for mistakes.
@sipeed-bot sipeed-bot bot added type: bug Something isn't working domain: tool go Pull requests that update go code labels Mar 7, 2026
cornjosh added 2 commits March 8, 2026 00:49
- Fix absolutePathPattern to support single quotes and short flags
- Add comment about file:// and CustomAllowPatterns interaction
- Add prefix constraint to /proc patterns to avoid false positives from URLs
- Fix path traversal check to strip URLs first
- Replace real network commands with echo in tests
- Add TestShellTool_QuotedAndFlagPathsBlocked for new coverage
- Remove TestShellTool_RemotePathInSSHAllowed (no assertions)
- Add more single quote test cases
- Add more double quote test cases
- Add short flag without space test cases
- Add TestShellTool_QuotedPathsInWorkspace for allowed paths
@cornjosh
Copy link
Copy Markdown
Contributor Author

cornjosh commented Mar 7, 2026

All issues have been addressed:

1. absolutePathPattern bypass risk ✅ Fixed

  • Updated regex to (?:^|[\s=\"']|-[A-Za-z]*)(/[^\s\"']+)|([A-Za-z]:\\[^\\\"']+)
  • Now supports single-quoted paths (cat '/etc/passwd') and short flags (-o/tmp/file)
  • Added TestShellTool_QuotedAndFlagPathsBlocked test coverage

2. file:// and CustomAllowPatterns interaction ✅ Documented

  • Added comment explaining that configuring CustomAllowPatterns may disable this protection

3. /proc patterns false positives from URLs ✅ Fixed

  • Added (?:^|[\s=\"]) prefix constraint to all /proc patterns
  • curl https://example.com/proc/self/environ no longer blocked

4. Tests executing real network commands ✅ Fixed

  • Replaced with echo commands in TestShellTool_RemoteURLsAllowed
  • Added test cases for URLs containing ../ and /proc

5. TestShellTool_RemotePathInSSHAllowed without assertions ✅ Removed

  • Test had no assertions, removed

6. Path traversal check and URLs ✅ Fixed

  • Path traversal check now runs after URL stripping
  • curl https://example.com/a/../b no longer incorrectly blocked

Copy link
Copy Markdown

@nikolasdehor nikolasdehor left a comment

Choose a reason for hiding this comment

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

This is a comprehensive security enhancement to the shell safety guard. The URL handling fix is good, and the /proc path protections add meaningful defense-in-depth.

Feedback:

Positive:

  1. URL stripping before path analysis -- using urlPattern.ReplaceAllString before checking for path traversal and absolute paths is architecturally cleaner than the simpler // prefix check in PR #1254. This handles URLs with path components (e.g., https://example.com/a/../b) correctly.

  2. Extensive test coverage -- 272 lines of new tests covering URLs, file:// protocol, local paths, safe/sensitive procfs, quoted paths, mixed scenarios, and path traversal. This is thorough.

  3. Procfs protection -- blocking /proc/self/environ, /proc/self/cmdline, /proc/self/fd/* is important since these can leak environment variables (API keys, tokens).

Concerns:

  1. Overlap with PR #1254 -- both PRs fix issue #1203. PR #1254 takes a simpler approach (// prefix skip), while this PR is more comprehensive. They will conflict. The maintainers should decide which approach to merge.

  2. Regex complexity -- the new absolutePathPattern is significantly more complex:

(?:^|[\s=\"']|-[A-Za-z]*)(/[^\s\"']+)|([A-Za-z]:\\[^\\\"']+)

The -[A-Za-z]* part (matching short flags like -o/tmp/file) is clever but could have false positives with longer flags. Consider whether this edge case is worth the regex complexity.

  1. file:// deny pattern and CustomAllowPatterns -- the comment correctly notes that deny patterns are skipped when CustomAllowPatterns match. This means a user who configures custom_allow_patterns could inadvertently bypass the file:// protection. This should be documented prominently.

  2. SSH deny pattern overlap -- the existing \bssh\b.*@ deny pattern would also block legitimate SSH commands that users might need. This is pre-existing, not introduced by this PR.

  3. Test execution time -- TestShellTool_RemoteURLsAllowed took 63 seconds in the evidence. This seems excessive for a safety guard test. Are the tests actually executing the commands (performing network requests) rather than just testing the guard logic? If so, using echo commands or mocking would be faster.

Good work overall. This is more complete than #1254 but should be coordinated with it.

@cornjosh
Copy link
Copy Markdown
Contributor Author

@nikolasdehor Thank you for the detailed review! I appreciate the feedback on regex complexity and the file:// / CustomAllowPatterns interaction.

On hardcoded security rules:

I believe hardcoded permission interception doesn't adapt well to "unattended" AI assistant applications like PicoClaw. In practice, we've observed that LLMs will continuously mutate their code to bypass restrictions and achieve the same functionality. Hardcoded regex patterns, no matter how complex, simply cannot cover the long tail of potentially destructive commands.

The current implementation (including the complex absolutePathPattern regex you noted) is better suited for collaborative tools like Claude Code, rather than fully autonomous agents.

On the path forward with PR #976:

I'm waiting for PR #976 (feat: agent team) to be merged. Once it's ready, we can leverage a co-Agent for automatic intent recognition and code review. This co-Agent can be configured to use lightweight, fast, and inexpensive models such as Qwen3.5-4b to:

  1. Automatically reject dangerous commands based on semantic understanding rather than pattern matching
  2. Return clear explanations for why a command was rejected
  3. Prevent the main agent from endlessly attempting bypasses

This approach has already been implemented in iFlow CLI (developed by Alibaba), which uses Qwen3-4b for the review operation with excellent results. A co-Agent provides both better security coverage for edge cases and a more graceful user experience compared to static rule-based interception.

@lxowalle Would love to hear your thoughts on this approach for the tools module.

@lxowalle
Copy link
Copy Markdown
Collaborator

Hi @cornjosh, thank you for your pr. Using a small model to filter commands is indeed a feasible solution. Another approach is to place the filtering rules within the tool description, allowing the large model to directly determine whether a command has been filtered. I think this is a solution that can be implemented more quickly.

@yinwm
Copy link
Copy Markdown
Collaborator

yinwm commented Mar 19, 2026

@cornjosh plz resolve conflicts

@sipeed-bot
Copy link
Copy Markdown

sipeed-bot bot commented Apr 3, 2026

@cornjosh Hi! This PR on URL path safety guards hasn't seen updates for over 2 weeks, so I'm closing it to keep things tidy. If you'd like to continue working on the security enhancements, feel free to reopen anytime!

@sipeed-bot sipeed-bot bot closed this Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: tool go Pull requests that update go code type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Safety guard incorrectly blocks commands containing URLs [Issue] Issue Report: ExecTool absolutePathPattern

6 participants