Skip to content

fix(bash): normalize allowlist path segments - #307

Open
ThunderTr77 wants to merge 6 commits into
TouchAI-org:mainfrom
ThunderTr77:fix/bash-allowlist-path-normalization
Open

fix(bash): normalize allowlist path segments#307
ThunderTr77 wants to merge 6 commits into
TouchAI-org:mainfrom
ThunderTr77:fix/bash-allowlist-path-normalization

Conversation

@ThunderTr77

Copy link
Copy Markdown
Contributor

Summary

  • Normalize Bash tool working directory paths by resolving . and .. segments before comparing them with the allowed-directory list.
  • Keep the existing allowlist behavior for matching the exact allowed directory or a child directory.
  • Add a regression test covering a parent-segment escape attempt.

Related issue or RFC

Related to https://github.com/TouchAI-org/TouchAI

AI assistance disclosure

  • Tool(s) used: local development assistant
  • Scope of assistance: bug discovery, test-first patch, local verification, and PR preparation
  • Human review or rewrite performed: reviewed the diff and command outputs before submission
  • Architecture or boundary impact: no new boundary or architecture changes

Testing evidence

  • corepack pnpm --filter @touchai/desktop exec vitest run --configLoader runner tests/services/BuiltInToolService/tools/bash/helper.test.ts - failed before the fix, then passed after the fix.
  • corepack pnpm --filter @touchai/desktop exec vitest run --configLoader runner tests/services/BuiltInToolService/tools/bash - passed, 56 tests.
  • corepack pnpm --dir apps/desktop exec eslint src/services/BuiltInToolService/tools/bash/helper.ts tests/services/BuiltInToolService/tools/bash/helper.test.ts - passed.
  • corepack pnpm --dir apps/desktop exec prettier --check src/services/BuiltInToolService/tools/bash/helper.ts tests/services/BuiltInToolService/tools/bash/helper.test.ts - passed.
  • corepack pnpm --dir apps/desktop run test:typecheck - passed.
  • Full pnpm test:pr was not run locally because the direct pnpm shim is not available on this machine; focused checks were run through corepack pnpm.

Risk notes

  • AgentService, runtime, MCP, or schema impact: none.
  • database baseline or migration impact: none.
  • release or packaging impact: none.

Screenshots or recordings

Not applicable; tool logic change only.

Checklist

  • The PR title follows Conventional Commits and is valid for squash merge.
  • This PR is either ready for review or explicitly marked as a Draft PR.
  • I did not use [WIP] or similar title prefixes.
  • If AI materially assisted this PR, I disclosed the tools and scope and I personally reviewed every affected change.
  • I can explain the why, what, and how of this change without relying on an AI tool.
  • If this touches AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC.
  • If this changes architecture or adds a new cross-boundary abstraction, there is an accepted RFC.
  • I ran pnpm test:pr for this code PR, or this is a docs-only change.
  • If I changed Rust behavior or tests, I reviewed pnpm test:coverage:rust or relied on CI coverage evidence.
  • If I changed desktop startup/window/search/popup/settings/E2E paths, I ran pnpm test:e2e locally or documented why CI is the first valid proof.
  • I added tests or explained why tests are not appropriate.
  • I updated docs when behavior changed.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @ThunderTr77, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for restricting built-in Bash execution to approved working directories.
    • Working-directory settings are passed through to execution on supported platforms.
  • Bug Fixes

    • Improved Windows path handling for drive-letter and network paths, including . and .. segments.
    • Prevented commands from escaping approved directories through path traversal.
    • Requests using invalid working directories now fail with a clear validation error.

Walkthrough

normalizeDirectoryPath now performs Windows-aware path normalization, while Bash execution requests carry allowed working directories through to Windows-native validation. Tests cover absolute, relative, and UNC path escapes.

Changes

Path Traversal Protection via Normalization

Layer / File(s) Summary
Path normalization enhancement
apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts
Normalizes Windows prefixes, separators, case, and ./.. segments.
Allowed-directory request wiring
apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts, apps/desktop/src/services/NativeService/types.ts, apps/desktop/src-tauri/src/core/built_in_tools/types.rs, apps/desktop/tests/services/BuiltInToolService/tools/bash/index.test.ts
Adds allowedWorkingDirectories to request shapes, forwards it to native execution, and updates the request-shape test.
Working-directory scope validation
apps/desktop/src-tauri/src/core/built_in_tools/bash.rs, apps/desktop/tests/services/BuiltInToolService/tools/bash/helper.test.ts
Canonicalizes requested and allowed directories, rejects paths outside the allowlist, and tests absolute, relative, and UNC escapes.

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

Suggested reviewers: hiqiancheng

Poem

🐰 I hop through drives and slashes bright,
Resolve the dots and set paths right.
Allowed roots guard every way,
Escaping paths must never stray—
Safe Bash runs another day.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits and clearly matches the bash allowlist path normalization change.
Description check ✅ Passed The description is mostly complete and includes all required sections, though the Related issue/RFC should link the actual tracking issue or RFC.
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 unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts`:
- Around line 31-38: The normalizeDirectoryPath function incorrectly collapses
consecutive ".." in relative paths; update its ".." handling so it only pops a
previous segment when resolvedSegments is non-empty and the last resolved
segment is not another ".." (i.e., change the condition from
resolvedSegments.length > 0 to resolvedSegments.length > 0 &&
resolvedSegments[resolvedSegments.length - 1] !== '..'); otherwise (and when no
prefix/root) push the ".." segment as before. Ensure this change in
normalizeDirectoryPath (and re-run tests for isWithinAllowedDirectory which
relies on it) preserves absolute/path-with-prefix behavior while keeping
relative "../.." sequences intact.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: e89637cf-fe80-41c8-991c-6bef5cb5b1e0

📥 Commits

Reviewing files that changed from the base of the PR and between 9b9fba1 and a62fba5.

📒 Files selected for processing (2)
  • apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts
  • apps/desktop/tests/services/BuiltInToolService/tools/bash/helper.test.ts
📜 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: Desktop E2E Smoke (Windows)
  • GitHub Check: Frontend Tests
  • GitHub Check: Rust Checks
  • GitHub Check: Frontend Quality
  • GitHub Check: CodeQL (rust)
  • GitHub Check: CodeQL (javascript-typescript)
🧰 Additional context used
🪛 OpenGrep (1.22.0)
apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts

[ERROR] 20-20: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🔇 Additional comments (1)
apps/desktop/tests/services/BuiltInToolService/tools/bash/helper.test.ts (1)

97-110: LGTM!

Comment thread apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 7, 2026
const normalizedSeparators = path.trim().replace(/\//g, '\\').replace(/\\+$/, '');
const driveMatch = /^([a-zA-Z]:)(?:\\(.*))?$/.exec(normalizedSeparators);
const hasRoot = normalizedSeparators.startsWith('\\');
const prefix = driveMatch?.[1]?.toLowerCase() ?? (hasRoot ? '\\' : '');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Potential allowlist bypass for UNC paths: \\server\share is the root of a UNC path, but this normalizer stores the prefix as only \ and then treats both server and share as ordinary segments. That lets .. pop the share name, which does not match Windows path semantics.

For example, with allowedWorkingDirectories: ['\\server\share'], a requested working directory like \\server\other\..\share\secret is normalized here as \\server\share\secret and passes the allowlist, while Windows resolves it under \\server\other\share\secret, outside the allowed share. Could we preserve \\server\share as the UNC prefix/root when resolving .., and add a resolveCommandContext test for this case?

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

This pull request has been inactive for 21 days. Please update it or leave a note if it is still in progress.

@github-actions github-actions Bot added the Stale label Jul 3, 2026
@hiqiancheng

Copy link
Copy Markdown
Collaborator

需要在 native 执行边界做真实路径校验,前端字符串归一化不够。

Canonical path validation should happen at the native execution boundary; frontend string normalization is not enough.

@github-actions github-actions Bot removed the Stale label Jul 7, 2026
@github-actions github-actions Bot added the area:tauri Tauri shell or desktop runtime changes label Jul 28, 2026
@coderabbitai
coderabbitai Bot requested a review from hiqiancheng July 28, 2026 07:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@apps/desktop/src-tauri/src/core/built_in_tools/bash.rs`:
- Around line 107-109: Make the native bash authorization path authoritative: in
apps/desktop/src-tauri/src/core/built_in_tools/bash.rs#L107-L109, resolve and
validate the allowlist from native-owned configuration or a validated opaque
policy identifier instead of accepting an empty or renderer-supplied list; in
apps/desktop/src/services/NativeService/types.ts#L18, remove the raw allowlist
from the public execution request; in
apps/desktop/src-tauri/src/core/built_in_tools/types.rs#L17-L18, stop
deserializing caller-provided authorization policy; and in
apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts#L162, stop
forwarding renderer-owned allowlist values as native authorization input.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 296a80c3-4de9-4209-a1fe-ce18eee2b4b7

📥 Commits

Reviewing files that changed from the base of the PR and between a4a534d and 4e36286.

📒 Files selected for processing (7)
  • apps/desktop/src-tauri/src/core/built_in_tools/bash.rs
  • apps/desktop/src-tauri/src/core/built_in_tools/types.rs
  • apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts
  • apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts
  • apps/desktop/src/services/NativeService/types.ts
  • apps/desktop/tests/services/BuiltInToolService/tools/bash/helper.test.ts
  • apps/desktop/tests/services/BuiltInToolService/tools/bash/index.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: Frontend Tests
  • GitHub Check: Rust Checks
  • GitHub Check: Frontend Quality
  • GitHub Check: CodeQL (rust)
  • GitHub Check: Desktop E2E Smoke (Windows)
🧰 Additional context used
🪛 OpenGrep (1.25.0)
apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts

[ERROR] 20-20: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 21-21: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🔇 Additional comments (3)
apps/desktop/src/services/BuiltInToolService/tools/bash/helper.ts (1)

20-61: LGTM!

apps/desktop/tests/services/BuiltInToolService/tools/bash/index.test.ts (1)

131-147: LGTM!

apps/desktop/tests/services/BuiltInToolService/tools/bash/helper.test.ts (1)

124-137: LGTM!

Comment on lines +107 to +109
if allowed_directories.is_empty() {
return Ok(());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Keep the allowlist authoritative at the native boundary.

Line 107 accepts an empty list, but the list is supplied by the same IPC request being validated. A caller can omit it or provide ['C:\\'], bypassing the configured scope. Resolve the policy from trusted native-owned configuration (or a validated opaque policy identifier), rather than trusting a renderer-provided directory list.

  • apps/desktop/src-tauri/src/core/built_in_tools/bash.rs#L107-L109: validate against native-owned policy, not request data.
  • apps/desktop/src/services/NativeService/types.ts#L18: remove the raw allowlist from the public execution request.
  • apps/desktop/src-tauri/src/core/built_in_tools/types.rs#L17-L18: do not deserialize caller-provided authorization policy.
  • apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts#L162: stop forwarding renderer-owned allowlist values as native authorization input.
📍 Affects 4 files
  • apps/desktop/src-tauri/src/core/built_in_tools/bash.rs#L107-L109 (this comment)
  • apps/desktop/src/services/NativeService/types.ts#L18-L18
  • apps/desktop/src-tauri/src/core/built_in_tools/types.rs#L17-L18
  • apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts#L162-L162
🤖 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 `@apps/desktop/src-tauri/src/core/built_in_tools/bash.rs` around lines 107 -
109, Make the native bash authorization path authoritative: in
apps/desktop/src-tauri/src/core/built_in_tools/bash.rs#L107-L109, resolve and
validate the allowlist from native-owned configuration or a validated opaque
policy identifier instead of accepting an empty or renderer-supplied list; in
apps/desktop/src/services/NativeService/types.ts#L18, remove the raw allowlist
from the public execution request; in
apps/desktop/src-tauri/src/core/built_in_tools/types.rs#L17-L18, stop
deserializing caller-provided authorization policy; and in
apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts#L162, stop
forwarding renderer-owned allowlist values as native authorization input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tauri Tauri shell or desktop runtime changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants