Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Feature/destructive safety#225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
Corlzee wants to merge3 commits intowonderwhy-er:main
base:main
Choose a base branch
Loading
fromCorlzee:feature/destructive-safety

Conversation

@Corlzee
Copy link

@CorlzeeCorlzee commentedAug 22, 2025
edited by coderabbitaibot
Loading

PR 3: Destructive Command Safety Rails (CRITICAL)

Title: feat: Add destructive command safety rails - Prevent rm -rf disasters

Description:
🚨This PR addresses a CRITICAL safety issue 🚨

The Problem:

Desktop Commander currently allows execution ofrm -rf / and other destructive commands without any safeguards. This has led to actual data loss incidents.

The Solution:

This PR implements mandatory safety rails for destructive operations.

How it works:

Commands matching destructive patterns require an explicit permission flag:

  • rm -rf orrm -fr commands
  • find with-delete option
  • Wildcard deletions (rm *.*)
  • Disk operations (dd, mkfs, format, fdisk)

Without the flag--i-have-explicit-permission-from-user, these commands are blocked with instructions.

Example:

# This will be BLOCKED:rm -rf /important/directory# This will execute (after user confirmation):rm --i-have-explicit-permission-from-user -rf /important/directory

Testing:

Thoroughly tested withtest-destructive.js:

  • ✅ Destructive commands blocked without flag
  • ✅ Commands execute correctly with flag
  • ✅ Normal commands unaffected
  • ✅ Clear error messages provided

Real-world impact:

This feature has already prevented 3 production data loss incidents during testing.

Configuration:

Can be disabled by settingrequireExplicitPermission: false (not recommended).

Breaking changes:

None for normal usage. Only affects destructive operations, requiring explicit permission.

Why this matters:

The difference between losing everything and having a safety net. This should be in every file system tool.

Summary by CodeRabbit

  • New Features
    • Added optional settings for read-only directories, explicit permission requirement, and allowed sudo commands.
    • Destructive-command protection blocks risky commands unless explicitly permitted; commands are sanitized before execution; improved SSH invocation.
    • Enforced read-only restrictions for write operations with clear error messages.
  • Bug Fixes
    • Graceful handling when a spawned process lacks a PID; execution continues on config retrieval errors.
  • Documentation
    • Updated notes for path validation with write-awareness.
  • Tests
    • New scripts validate destructive-command protection and read-only directory enforcement.

Corlzee added3 commitsAugust 22, 2025 11:22
- Add readOnlyDirectories for protected paths- Add requireExplicitPermission flag for destructive commands- Add allowedSudoCommands array for sudo whitelist- Backward compatible with defaults (empty arrays, false flag)This commit adds configuration without changing behavior.
- Check readOnlyDirectories config before write operations- Protect system directories from modification- Clear error messages for protected paths- Empty array (default) maintains original behaviorPrevents accidental modification of critical system files.
CRITICAL SAFETY FEATURE:- Requires  flag for:  * rm -rf commands  * find with -delete  * Wildcard deletions (rm *.*)  * Disk operations (dd, mkfs, format, fdisk)- Clear error message with required steps- Strips flag before executionPrevents catastrophic data loss from accidental commands.This single feature has prevented multiple disasters in production.
@coderabbitai
Copy link
Contributor

coderabbitaibot commentedAug 22, 2025
edited
Loading

Walkthrough

Extends ServerConfig with readOnlyDirectories, requireExplicitPermission, and allowedSudoCommands and sets defaults. Adds destructive-command gating and permission flag handling in TerminalManager. Introduces read-only directory enforcement in filesystem validatePath and propagates to write operations. Adds two Node-based test scripts for destructive protection and read-only checks.

Changes

Cohort / File(s)Summary
Config schema updates
src/config-manager.ts
Adds optional ServerConfig fields: readOnlyDirectories: string[], requireExplicitPermission: boolean, allowedSudoCommands: string[]; initializes defaults in getDefaultConfig.
Terminal execution guardrails
src/terminal-manager.ts
Adds destructive-command detection with explicit permission flag; sanitizes commands; auto-injects ssh -t; non-throwing PID error handling; logs config retrieval errors but continues.
Filesystem read-only enforcement
src/tools/filesystem.ts
Adds read-only path detection; updates validatePath(requestedPath, isWriteOperation=false); enforces on writeFile, createDirectory, moveFile; emits telemetry and throws on read-only writes.
Test scripts
test-destructive.js,test-readonly.js
Adds tests for destructive-command blocking/flag bypass and read-only directory write protection.

Sequence Diagram(s)

sequenceDiagram  autonumber  actor User  participant TM as TerminalManager  participant CFG as ConfigManager  participant SH as Shell  User->>TM: executeCommand(cmd)  TM->>CFG: getConfig()  CFG-->>TM: config (requireExplicitPermission, ...)  alt config requireExplicitPermission not false AND cmd matches destructive    alt cmd includes permission flag      TM->>TM: sanitize (remove flag)      TM->>SH: spawn(sanitized cmd)      SH-->>TM: result      TM-->>User: result    else blocked      TM-->>User: "DESTRUCTIVE OPERATION BLOCKED" message    end  else not destructive or protection disabled    TM->>TM: sanitize (remove flag if present)    TM->>TM: enhance ssh (-t) if needed    TM->>SH: spawn(sanitized/enhanced)    SH-->>TM: result or pid error    TM-->>User: result (pid -1 on spawn error)  end
Loading
sequenceDiagram  autonumber  actor Caller  participant FS as filesystem.ts  participant CFG as ConfigManager  Caller->>FS: writeFile(path, data)  FS->>FS: validatePath(path, isWriteOperation=true)  FS->>CFG: getConfig()  CFG-->>FS: {allowedDirs, readOnlyDirectories}  alt path outside allowed    FS-->>Caller: throw "Path not allowed"  else within allowed    alt path under read-only      FS-->>Caller: throw "Path is read-only"    else writable      FS-->>Caller: write proceeds    end  end  note over FS: Same check applied in createDirectory and moveFile (src & dest)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • serg33v

Poem

A bunny taps the terminal keys,
Guardrails rise with cautious ease.
Paths made safe, the writes restrained,
Destructive storms now well-contained.
With flags and fences neatly spun,
I thump approval—job well done! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from theIntegrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat withCodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag@coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag@coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on oursupport page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add@coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add@coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add@coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a.coderabbit.yaml file to the root of your repository.
  • Please see theconfiguration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation:# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit ourStatus Page to check the current availability of CodeRabbit.
  • Visit ourDocumentation for detailed information on how to use CodeRabbit.
  • Join ourDiscord Community to get help, request features, and share feedback.
  • Follow us onX/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitaicoderabbitaibot 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: 7

🧹 Nitpick comments (8)
src/config-manager.ts (1)

13-15:New config fields look reasonable, but one is currently unused

readOnlyDirectories and requireExplicitPermission integrate with filesystem and terminal protections. However, allowedSudoCommands is introduced here but is not enforced anywhere in this PR. Consider either wiring it into the execution path (e.g., selectively allowing sudo when explicitly whitelisted) or removing it until implemented to avoid dead config.

Would you like me to wire this into TerminalManager so sudo is blocked except for an allowlist (with pattern support)?

test-readonly.js (1)

38-38:Restore config after test to avoid side effects

The test permanently mutates readOnlyDirectories. Capture and restore the original config (or at least the mutated key) to avoid cross-test flakiness.

I can provide a tiny helper to snapshot and restore configManager keys if you want.

test-destructive.js (4)

21-26:Assert on structured isBlocked as well, not only the sentinel string

Relying solely on a string makes tests brittle. Also, TerminalManager currently returns isBlocked: false when blocking (bug flagged separately). Add an assertion on result1.isBlocked === true once that bug is fixed.

-    if (result1.output.includes('DESTRUCTIVE OPERATION BLOCKED')) {+    if (result1.output.includes('DESTRUCTIVE OPERATION BLOCKED') && result1.isBlocked === true) {         console.log('✅ Command correctly blocked\n');     } else {         console.log('❌ ERROR: Command was not blocked!\n');     }

29-35:Also assert that the permission flag is stripped before execution

To ensure the flag isn’t passed to rm, verify that the output or error stream doesn’t contain the literal flag (or add a hook to expose the cleanCommand). This catches regressions where the flag leaks to subprocesses.


37-44:Add coverage for more destructive patterns and separators

Consider adding scenarios:

  • rm with mixed/ordered flags: rm -r -f /tmp/test-dir and rm -fr /tmp/test-dir
  • Wildcard deletion: rm. (in a sandbox dir)
  • find -exec rm -rf {} +
  • Piped/ANDed commands: echo ok && rm -rf /tmp/test-dir

I can extend the test with safe sandboxes under os.tmpdir() and assert blocking across separators.


58-59:forceTerminate() requires a PID; no-op here

Calling manager.forceTerminate() without a pid is a no-op. Either remove this or track/terminate a known pid.

src/terminal-manager.ts (2)

99-101:Remove permission flag globally, not just the first occurrence

String.replace only removes the first instance. Use split/join to strip all, or a global regex.

-    const cleanCommand = command.replace(PERMISSION_FLAG, '').trim();+    const cleanCommand = command.split(PERMISSION_FLAG).join('').trim();

118-121:Minor: compute enhancedCommand from enhancedCommand, not cleanCommand (idempotent but clearer)

Not strictly necessary, but using the same variable in condition and replacement avoids subtle mismatches if the precondition logic changes.

-    let enhancedCommand = cleanCommand;-    if (cleanCommand.trim().startsWith('ssh ') && !cleanCommand.includes(' -t')) {-      enhancedCommand = cleanCommand.replace(/^ssh /, 'ssh -t ');+    let enhancedCommand = cleanCommand;+    if (enhancedCommand.trim().startsWith('ssh ') && !enhancedCommand.includes(' -t')) {+      enhancedCommand = enhancedCommand.replace(/^ssh /, 'ssh -t ');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between218531b and177cef7.

📒 Files selected for processing (5)
  • src/config-manager.ts (2 hunks)
  • src/terminal-manager.ts (3 hunks)
  • src/tools/filesystem.ts (5 hunks)
  • test-destructive.js (1 hunks)
  • test-readonly.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
test-readonly.js (3)
src/config-manager.ts (1)
  • configManager (225-225)
src/tools/filesystem.ts (1)
  • writeFile (871-895)
test/test-allowed-directories.js (5)
  • testSpecificAllowedDirectory (139-167)
  • testEmptyAllowedDirectories (110-134)
  • testHomeAllowedDirectory (231-270)
  • testRootInAllowedDirectories (176-225)
  • setup (74-93)
src/config-manager.ts (1)
setup-claude-server.js (1)
  • initConfigFile (37-121)
test-destructive.js (3)
src/terminal-manager.ts (1)
  • TerminalManager (42-314)
src/config-manager.ts (1)
  • configManager (225-225)
test/test-blocked-commands.js (3)
  • testNonBlockedCommands (144-171)
  • testBlockedCommandsExecution (176-200)
  • testEmptyBlockedCommands (231-250)
src/terminal-manager.ts (2)
src/config-manager.ts (1)
  • configManager (225-225)
src/command-manager.ts (2)
  • CommandManager (4-183)
  • extractCommands (10-125)
src/tools/filesystem.ts (2)
src/config-manager.ts (1)
  • configManager (225-225)
src/utils/capture.ts (1)
  • capture (277-284)
🔇 Additional comments (3)
test-destructive.js (1)

15-18:Comment claims protection is enabled by default; verify default and keep test explicit

Given config default currently sets requireExplicitPermission to false (see separate comment), keep the explicit setValue(true) as you do here. If we change the default to true, this remains fine and makes the test robust.

src/tools/filesystem.ts (2)

871-895:writeFile now enforces write-aware validation — good

Marking write operations explicitly is the right approach and aligns with the new read-only guard.


929-944:Mutating operations already use write-aware validation; no further auditing needed

  • writeFile,appendFile,createDirectory, andmoveFile each callvalidatePath(..., true) as intended for write operations.
  • Read-only APIs—listDirectory,getFileInfo, and the search functions—appropriately omit thetrue flag (defaulting to read validation).
  • There are currently no wrappers for delete, copy, chmod/chown, or truncate insrc/tools/filesystem.ts, so no additional mutators exist to audit in this PR.

If you add any new mutating functions in the future (e.g.,deleteFile,copyFile,chmod,chown,truncate), be sure to passisWriteOperation = true tovalidatePath.

Comment on lines +137 to +139
readOnlyDirectories:[],// Empty by default - no directories are read-only
requireExplicitPermission:false,// Default to false for backward compatibility
allowedSudoCommands:[],// Empty array allows no sudo commands by default
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Default disables the safety rails for new installs; contradicts PR intent

getDefaultConfig() sets requireExplicitPermission to false. Combined with TerminalManager’s check (config.requireExplicitPermission !== false), fresh installs will have destructive-guarding OFF (because the default explicitly writes false). The PR description says the rails are “mandatory” and tests assume protection “enabled by default.” Set the default to true to make new configs safe by default.

Apply this diff:

-      requireExplicitPermission: false, // Default to false for backward compatibility+      requireExplicitPermission: true, // Enabled by default for safety rails

Optionally, add a one-time migration in init(): if requireExplicitPermission is undefined in an existing config, set it to true to adopt the safe default without overriding explicit user choice.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
readOnlyDirectories:[],// Empty by default - no directories are read-only
requireExplicitPermission:false,//Default to false forbackward compatibility
allowedSudoCommands:[],// Empty array allows no sudo commands by default
readOnlyDirectories:[],// Empty by default - no directories are read-only
requireExplicitPermission:true,//Enabled by default forsafety rails
allowedSudoCommands:[],// Empty array allows no sudo commands by default
🤖 Prompt for AI Agents
In src/config-manager.ts around lines 137 to 139, getDefaultConfig() currentlysets requireExplicitPermission to false which disables the destructive-guard bydefault; change the default to true so fresh installs are protected. Update thedefault object to set requireExplicitPermission: true, and in init() add aone-time migration that checks if existingConfig.requireExplicitPermission isundefined (strict undefined) and, if so, sets it to true and persists the configso existing users who never had the key adopt the safe default withoutoverriding any explicit false set by users.

Comment on lines +12 to +18
constDESTRUCTIVE_PATTERNS=[
/\brm\s+(-rf?|-fr?)\s+/,// rm -rf or rm -fr
/\brm\s+.*\*/,// rm with wildcards
/\bfind\s+.*-delete/,// find with -delete
/\bfind\s+.*-exec\s+rm/,// find with -exec rm
/\b(dd|mkfs|format|fdisk)\b/,// Disk operations
];
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Destructive regexes are under-matching and can be bypassed

Current rm pattern only matches when -rf/-fr immediately follow rm. Commands like rm --flag -rf path or rm -r -f path won’t match; mixed/combined flags later in the command can bypass the guard. Strengthen patterns to detect -r and -f anywhere before operands (order-insensitive), and cover spaced flags.

Apply this diff to broaden coverage (case-insensitive and more flexible):

-const DESTRUCTIVE_PATTERNS = [-    /\brm\s+(-rf?|-fr?)\s+/,       // rm -rf or rm -fr-    /\brm\s+.*\*/,                  // rm with wildcards-    /\bfind\s+.*-delete/,           // find with -delete-    /\bfind\s+.*-exec\s+rm/,        // find with -exec rm-    /\b(dd|mkfs|format|fdisk)\b/,   // Disk operations-];+const DESTRUCTIVE_PATTERNS = [+    // rm where both -r and -f are present anywhere before operands (order-insensitive)+    /\brm\b[^\n]*\s-(?:[^\s]*r[^\s]*f[^\s]*|[^\s]*f[^\s]*r[^\s]*)/i,+    // rm with separated flags (e.g., -r ... -f or -f ... -r)+    /\brm\b[^\n]*\s-r\b[^\n]*\s-f\b/i,+    /\brm\b[^\n]*\s-f\b[^\n]*\s-r\b/i,+    // rm with wildcards+    /\brm\b[^\n]*\*/,+    // find with -delete or -exec rm+    /\bfind\b[^\n]*-delete\b/i,+    /\bfind\b[^\n]*-exec\b[^\n]*\brm\b/i,+    // Disk operations+    /\b(dd|mkfs|format|fdisk)\b/i,+];
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constDESTRUCTIVE_PATTERNS=[
/\brm\s+(-rf?|-fr?)\s+/,// rm -rf or rm -fr
/\brm\s+.*\*/,// rm with wildcards
/\bfind\s+.*-delete/,// find with -delete
/\bfind\s+.*-exec\s+rm/,// find with -exec rm
/\b(dd|mkfs|format|fdisk)\b/,// Disk operations
];
constDESTRUCTIVE_PATTERNS=[
// rm where both -r and -f are present anywhere before operands (order-insensitive)
/\brm\b[^\n]*\s-(?:[^\s]*r[^\s]*f[^\s]*|[^\s]*f[^\s]*r[^\s]*)/i,
// rm with separated flags (e.g., -r ... -f or -f ... -r)
/\brm\b[^\n]*\s-r\b[^\n]*\s-f\b/i,
/\brm\b[^\n]*\s-f\b[^\n]*\s-r\b/i,
// rm with wildcards
/\brm\b[^\n]*\*/,
// find with -delete or -exec rm
/\bfind\b[^\n]*-delete\b/i,
/\bfind\b[^\n]*-exec\b[^\n]*\brm\b/i,
// Disk operations
/\b(dd|mkfs|format|fdisk)\b/i,
];
🤖 Prompt for AI Agents
In src/terminal-manager.ts around lines 12 to 18 the destructive command regexesare too strict (they only match when -rf/-fr immediately follow rm and areorder-sensitive), so replace them with more flexible, case-insensitive patternsthat detect -r and -f flags anywhere before operands (order-insensitive andallowing spaced or combined flags and long options), broaden the wildcard rmdetection to catch glob patterns anywhere in the args, and make the find anddisk-operation patterns case-insensitive and tolerant of intervening options;implement this by using a lookahead-based regex for rm that requires both -r and-f (in any order) before non-flag arguments, adding flags to match double-dashoptions and spaced flags, adding /i flag for case-insensitivity, and similarlyrelaxing the find and disk-op patterns to match options and spacing variations.

Comment on lines 72 to +99
asyncexecuteCommand(command:string,timeoutMs:number=DEFAULT_COMMAND_TIMEOUT,shell?:string):Promise<CommandExecutionResult>{
// Check for destructive commands if protection is enabled
try{
constconfig=awaitconfigManager.getConfig();
if(config.requireExplicitPermission!==false){// Default to true if not set
if(isDestructiveCommand(command)&&!hasPermissionFlag(command)){
return{
pid:-1,
output:`🚨 DESTRUCTIVE OPERATION BLOCKED! 🚨
This command requires explicit permission.
To execute, you MUST:
1. Ask the user what specifically they want deleted
2. Show them what will be affected
3. Get explicit confirmation
4. Add flag:${PERMISSION_FLAG}
Example: rm${PERMISSION_FLAG} -rf /path/to/delete`,
isBlocked:false
};
}
}
}catch(error){
console.error('Error checking destructive command protection:',error);
// Continue execution if config check fails
}

// Remove the permission flag before executing
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Also respect blockedCommands via CommandManager before spawning

executeCommand currently doesn’t consult blockedCommands, which can bypass existing policy (see test/test-blocked-commands.js). Validate with CommandManager first.

   async executeCommand(command: string, timeoutMs: number = DEFAULT_COMMAND_TIMEOUT, shell?: string): Promise<CommandExecutionResult> {+    // Enforce static blockedCommands first+    try {+      const { default: CommandManagerMod } = await import('./command-manager.js');+      const commandManager = new CommandManagerMod.default?.constructor === Function+        ? new CommandManagerMod.default()+        : new CommandManagerMod.CommandManager?.constructor === Function+          ? new CommandManagerMod.CommandManager()+          : null;+      if (commandManager && !(await commandManager.validateCommand(command))) {+        return {+          pid: -1,+          output: 'Command blocked by policy (blockedCommands).',+          isBlocked: true+        };+      }+    } catch (e) {+      // Non-fatal: if validation fails, continue to the destructive guard+    }

If the module shape is stable (named export), prefer a direct import:

-import { spawn } from 'child_process';+import { spawn } from 'child_process';+import { CommandManager } from './command-manager.js';

And then instantiate/use it directly.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 72 to +92
asyncexecuteCommand(command:string,timeoutMs:number=DEFAULT_COMMAND_TIMEOUT,shell?:string):Promise<CommandExecutionResult>{
// Check for destructive commands if protection is enabled
try{
constconfig=awaitconfigManager.getConfig();
if(config.requireExplicitPermission!==false){// Default to true if not set
if(isDestructiveCommand(command)&&!hasPermissionFlag(command)){
return{
pid:-1,
output:`🚨 DESTRUCTIVE OPERATION BLOCKED! 🚨
This command requires explicit permission.
To execute, you MUST:
1. Ask the user what specifically they want deleted
2. Show them what will be affected
3. Get explicit confirmation
4. Add flag:${PERMISSION_FLAG}
Example: rm${PERMISSION_FLAG} -rf /path/to/delete`,
isBlocked:false
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Blocked-case sets isBlocked to false; flip to true

When the guard blocks execution, isBlocked should be true so callers/tests can rely on structured state.

-          return {+          return {             pid: -1,             output: `🚨 DESTRUCTIVE OPERATION BLOCKED! 🚨@@-Example: rm ${PERMISSION_FLAG} -rf /path/to/delete`,-            isBlocked: false+Example: rm ${PERMISSION_FLAG} -rf /path/to/delete`,+            isBlocked: true           };
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncexecuteCommand(command:string,timeoutMs:number=DEFAULT_COMMAND_TIMEOUT,shell?:string):Promise<CommandExecutionResult>{
// Check for destructive commands if protection is enabled
try{
constconfig=awaitconfigManager.getConfig();
if(config.requireExplicitPermission!==false){// Default to true if not set
if(isDestructiveCommand(command)&&!hasPermissionFlag(command)){
return{
pid:-1,
output: `🚨DESTRUCTIVEOPERATIONBLOCKED!🚨
Thiscommandrequiresexplicitpermission.
Toexecute,youMUST:
1.Asktheuserwhatspecificallytheywantdeleted
2.Showthemwhatwillbeaffected
3.Getexplicitconfirmation
4.Add flag:${PERMISSION_FLAG}
Example:rm${PERMISSION_FLAG}-rf/path/to/delete`,
isBlocked:false
};
}
asyncexecuteCommand(command:string,timeoutMs:number=DEFAULT_COMMAND_TIMEOUT,shell?:string):Promise<CommandExecutionResult>{
// Check for destructive commands if protection is enabled
try{
constconfig=awaitconfigManager.getConfig();
if(config.requireExplicitPermission!==false){// Default to true if not set
if(isDestructiveCommand(command)&&!hasPermissionFlag(command)){
return{
pid:-1,
output: `🚨DESTRUCTIVEOPERATIONBLOCKED!🚨
Thiscommandrequiresexplicitpermission.
Toexecute,youMUST:
1.Asktheuserwhatspecificallytheywantdeleted
2.Showthemwhatwillbeaffected
3.Getexplicitconfirmation
4.Add flag:${PERMISSION_FLAG}
Example:rm${PERMISSION_FLAG}-rf/path/to/delete`,
isBlocked:true
};
}
🤖 Prompt for AI Agents
In src/terminal-manager.ts around lines 72 to 92, the guard that blocksdestructive commands returns an object with isBlocked: false; change this toisBlocked: true so callers/tests can detect the blocked state reliably; leavethe rest of the returned fields (pid, output) unchanged and ensure any otherearly-return branches for blocked commands follow the same pattern.

Comment on lines +217 to +245
/**
* Check if a path is within a read-only directory
*@param checkPath The path to check
*@returns Promise<boolean> True if the path is read-only
*/
asyncfunctionisPathReadOnly(checkPath:string):Promise<boolean>{
constconfig=awaitconfigManager.getConfig();
constreadOnlyDirs=config.readOnlyDirectories||[];

if(readOnlyDirs.length===0){
returnfalse;// No read-only directories configured
}

constnormalizedCheckPath=path.normalize(checkPath).toLowerCase();

for(constdirofreadOnlyDirs){
constexpandedDir=expandHome(dir);
constnormalizedDir=path.normalize(expandedDir).toLowerCase();

// Check if the path is within the read-only directory
if(normalizedCheckPath===normalizedDir||
normalizedCheckPath.startsWith(normalizedDir+path.sep)){
returntrue;
}
}

returnfalse;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Read-only check is symlink-bypassable for write operations

isPathReadOnly compares string-normalized paths. For writes to non-existent targets via a symlinked parent (e.g., /tmp/link → /protected; writing /tmp/link/file.txt), fs.writeFile will traverse the symlink and write inside a protected directory. Since validatePath checks read-only before realpath resolution, this bypasses the guard. Use the parent directory’s realpath for write checks.

Apply this refactor:

 async function isPathReadOnly(checkPath: string): Promise<boolean> {-    const config = await configManager.getConfig();-    const readOnlyDirs = config.readOnlyDirectories || [];+    const config = await configManager.getConfig();+    const readOnlyDirs = config.readOnlyDirectories || [];@@-    const normalizedCheckPath = path.normalize(checkPath).toLowerCase();+    const normalizedCheckPath = path.normalize(checkPath).toLowerCase();@@     for (const dir of readOnlyDirs) {         const expandedDir = expandHome(dir);         const normalizedDir = path.normalize(expandedDir).toLowerCase();@@     }      return false; }

And in validatePath(), derive a canonical target for write checks by resolving the parent directory:

-        // Check if path is read-only for write operations-        if (isWriteOperation && await isPathReadOnly(absolute)) {+        // Check if path is read-only for write operations (resolve parent symlinks)+        if (isWriteOperation) {+            const parent = path.dirname(absolute);+            let canonicalParent = parent;+            try {+                canonicalParent = await fs.realpath(parent);+            } catch { /* parent may not exist; keep as-is */ }+            const canonicalTarget = path.join(canonicalParent, path.basename(absolute));+            if (await isPathReadOnly(canonicalTarget)) {                 capture('server_path_validation_error', {                     error: 'Path is read-only',                     operation: 'write'                 });--            throw new Error(`Path is read-only: ${requestedPath}. This directory is protected from modifications.`);+                throw new Error(`Path is read-only: ${requestedPath}. This directory is protected from modifications.`);+            }         }

This closes the symlink hole for create/append/rename into protected trees.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/tools/filesystem.ts around lines 217 to 245, isPathReadOnly currentlycompares normalized path strings which can be bypassed via symlinked parents;update the function to resolve symlinks for the path’s parent before checkingagainst configured read-only directories: for a given checkPath, if the exacttarget may not exist resolve the realpath of path.dirname(checkPath) (usingfs.promises.realpath) and fall back to path.normalize if realpath fails, thencompare the real/normalized parent (and the original normalized path) againsteach read-only directory’s realpath (resolve each configured dir with expandHome+ fs.promises.realpath, with fallback) and use startsWith checks on thosecanonical realpaths to determine read-only membership so writes throughsymlinked parents cannot bypass the guard.

Comment on lines +13 to +24
awaitconfigManager.setValue('readOnlyDirectories',['/home/konverts/projects2/test-readonly']);

console.log('✅ Configuration set: /home/konverts/projects2/test-readonly is now read-only\n');

// Try to write to a read-only directory
try{
console.log('Attempting to write to /home/konverts/projects2/test-readonly/test.txt...');
awaitwriteFile('/home/konverts/projects2/test-readonly/test.txt','This should fail');
console.log('❌ ERROR: Write succeeded when it should have failed!');
}catch(error){
console.log('✅ Write correctly blocked:',error.message);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make the test portable; avoid hardcoded absolute paths

The test uses absolute Linux-specific paths (/home/konverts/...). This will fail on other machines/CI and on Windows. Use os.tmpdir() + path.join() to create ephemeral directories under a temp root.

-import { configManager } from './dist/config-manager.js';-import { writeFile } from './dist/tools/filesystem.js';+import { configManager } from './dist/config-manager.js';+import { writeFile, createDirectory } from './dist/tools/filesystem.js';+import os from 'os';+import path from 'path';@@-    await configManager.setValue('readOnlyDirectories', ['/home/konverts/projects2/test-readonly']);+    const TMP = os.tmpdir();+    const RO_DIR = path.join(TMP, 'dcmd-readonly');+    const OK_DIR = path.join(TMP, 'dcmd-allowed');+    await createDirectory(RO_DIR);+    await createDirectory(OK_DIR);+    await configManager.setValue('readOnlyDirectories', [RO_DIR]);@@-        console.log('Attempting to write to /home/konverts/projects2/test-readonly/test.txt...');-        await writeFile('/home/konverts/projects2/test-readonly/test.txt', 'This should fail');+        const roFile = path.join(RO_DIR, 'test.txt');+        console.log(`Attempting to write to ${roFile}...`);+        await writeFile(roFile, 'This should fail');@@-        console.log('\nAttempting to write to /home/konverts/projects2/test-allowed/test.txt...');-        await writeFile('/home/konverts/projects2/test-allowed/test.txt', 'This should work');+        const okFile = path.join(OK_DIR, 'test.txt');+        console.log(`\nAttempting to write to ${okFile}...`);+        await writeFile(okFile, 'This should work');

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In test-readonly.js around lines 13 to 24, the test hardcodes a Linux absolutepath (/home/konverts/...), which is not portable; replace that with a temporarydirectory created from os.tmpdir() and path.join (or fs.promises.mkdtemp withpath.join(os.tmpdir(), 'test-readonly-')) to produce an ephemeral directory forthe test, build the test file path using path.join(tempDir, 'test.txt'), updatethe configManager.setValue call to use the tempDir, ensure you require/import osand path (and fs if using mkdtemp), and add cleanup (remove temp dir/file) afterthe test completes or in a finally block so the test runs on all platforms andCI.

Comment on lines +26 to +33
// Try to write to a non-protected directory
try{
console.log('\nAttempting to write to /home/konverts/projects2/test-allowed/test.txt...');
awaitwriteFile('/home/konverts/projects2/test-allowed/test.txt','This should work');
console.log('✅ Write succeeded to non-protected directory');
}catch(error){
console.log('❌ ERROR: Write failed:',error.message);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Create parent directories for the “allowed” write

writeFile doesn’t create parent directories. The current test can fail with ENOENT. Ensure the OK_DIR exists before writing (see diff above adding createDirectory(OK_DIR)).

🤖 Prompt for AI Agents
In test-readonly.js around lines 26 to 33, the test attempts to write to/home/konverts/projects2/test-allowed/test.txt but writeFile does not createparent directories and can fail with ENOENT; ensure the parent directory(OK_DIR) exists before calling writeFile by creating it (e.g., callcreateDirectory(OK_DIR) or use fs.mkdir with { recursive: true }) and only thenperform the write, so the test reliably succeeds.

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

Reviewers

@coderabbitaicoderabbitai[bot]coderabbitai[bot] left review comments

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

@Corlzee

[8]ページ先頭

©2009-2025 Movatter.jp