Skip to content

Support cascade and droid hooks#2

Merged
ashwin-corridor merged 7 commits into
mainfrom
cascade-droid
Jan 30, 2026
Merged

Support cascade and droid hooks#2
ashwin-corridor merged 7 commits into
mainfrom
cascade-droid

Conversation

@ashwin-corridor

@ashwin-corridor ashwin-corridor commented Jan 27, 2026

Copy link
Copy Markdown
Contributor

Support cascade and droid hooks

Testing instructions: first build the binary.

## Testing Hooks

Test your hooks from the command line by piping JSON input:

### Claude Code / Factory Droid

```bash
# Test PreToolUse - allow Read tool silently
echo '{"tool_name":"Read","tool_input":{"file_path":"/tmp/test.txt"}}' | ./my-hooks claude-pre-tool-use

# Test PreToolUse - block dangerous MCP server
echo '{"tool_name":"mcp__blocked__evil","tool_input":{}}' | ./my-hooks claude-pre-tool-use

# Test UserPromptSubmit - allow normal prompt
echo '{"prompt":"analyze this code"}' | ./my-hooks claude-user-prompt-submit

# Test UserPromptSubmit - block prompt with API key
echo '{"prompt":"use api_key=secret123"}' | ./my-hooks claude-user-prompt-submit

# Test Stop hook
echo '{"stop_hook_active":false}' | ./my-hooks claude-stop

Cursor

# Test beforeShellExecution - allow safe command
echo '{"command":"ls -la","cwd":"/tmp"}' | ./my-hooks cursor-before-shell

# Test beforeShellExecution - block dangerous command
echo '{"command":"rm -rf /","cwd":"/tmp"}' | ./my-hooks cursor-before-shell

# Test beforeReadFile - allow normal file
echo '{"file_path":"/tmp/test.txt"}' | ./my-hooks cursor-before-read-file

# Test beforeReadFile - block .env file
echo '{"file_path":"/tmp/.env"}' | ./my-hooks cursor-before-read-file

Windsurf Cascade

Cascade uses exit code 2 to block actions:

# Test pre_run_command - allow safe command (exit 0)
echo '{"tool_info":{"command_line":"ls -la","cwd":"/tmp"}}' | ./my-hooks cascade-pre-run-command
echo "Exit code: $?"

# Test pre_run_command - block dangerous command (exit 2)
echo '{"tool_info":{"command_line":"rm -rf /","cwd":"/tmp"}}' | ./my-hooks cascade-pre-run-command 2>&1
echo "Exit code: $?"

# Test pre_write_code - block .env file (exit 2)
echo '{"tool_info":{"file_path":"/tmp/.env"}}' | ./my-hooks cascade-pre-write-code 2>&1
echo "Exit code: $?"

# Test pre_user_prompt - block prompt with API key (exit 2)
echo '{"tool_info":{"prompt":"use api_key=secret"}}' | ./my-hooks cascade-pre-user-prompt 2>&1
echo "Exit code: $?"

Run All Tests

go test ./...

<!-- devin-review-badge-begin -->

---

<a href="https://app.devin.ai/review/corridorsecurity/hookshot/pull/2">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
    <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin">
  </picture>
</a>
<!-- devin-review-badge-end -->

@corridor-security corridor-security 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.

Security Issues

  • Command Injection Risk in written agent hook commands
    The install routines build a single command string using unvalidated user-controlled binaryPath and embed it into external agent config files (Factory Droid, Windsurf Cascade). If those agents execute the "command" field via a shell, filenames containing shell metacharacters (e.g., ; | ` $ " ) or spaces can inject arbitrary commands, leading to code execution whenever hooks run.

Summary: This PR adds Droid and Cascade platform support, including new install paths that write command strings into agent configs and new unified handlers that pass through execution metadata to hooks.

Risk: High. Writing unescaped, user-controlled command strings into third-party agent configs can result in command injection if the agents invoke them via a shell, enabling code execution under the user's context.

Recommendations:

  • Validate and restrict binaryPath to a safe character set (e.g., absolute path, no whitespace or shell metacharacters) before writing to config; reject unsafe inputs.
  • Prefer argument-array configuration if supported by the target agents; otherwise, safely quote/escape the path and arguments according to the agent's execution semantics and document that paths with special characters are disallowed.
  • Consider resolving to an absolute path and ensuring it points to an executable owned by the current user to reduce tampering risk.

Comment thread cmd/hookshot/main.go
}

// Build hooks config (same structure as Claude Code)
hooks := map[string]any{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unvalidated user-controlled binaryPath is concatenated into a single command string written to external agent configs. If those agents invoke the command via a shell, shell metacharacters in the path can lead to command injection and arbitrary code execution whenever the hook is triggered.

Examples:

"command": binaryPath + " droid-stop",
"command": binaryPath + " droid-pre-tool-use",
"command": binaryPath + " droid-after-file-edit",
"command": binaryPath + " droid-user-prompt-submit",

And for Cascade:

"pre_run_command":       []map[string]any{{"command": binaryPath + " cascade-pre-run-command"}},
"pre_mcp_tool_use":      []map[string]any{{"command": binaryPath + " cascade-pre-mcp-tool-use"}},
"pre_user_prompt":       []map[string]any{{"command": binaryPath + " cascade-pre-user-prompt"}},
"post_write_code":       []map[string]any{{"command": binaryPath + " cascade-post-write-code"}},
"post_cascade_response": []map[string]any{{"command": binaryPath + " cascade-post-cascade-response"}},

An attacker who influences the binary path (e.g., a filename with ; or backticks) could inject extra commands if the consumer uses sh -c or equivalent. This deviates from the project's guardrail of avoiding shell-constructed command strings.

Remediation: Validate binaryPath to disallow whitespace and shell metacharacters, require an absolute path, and/or use argument arrays if the agent config supports them. If only a single string is allowed, quote and escape per the agent's execution semantics and document constraints.

For more details, see the finding in Corridor.

Provide feedback: Reply with whether this is a valid vulnerability or false positive to help improve Corridor's accuracy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is fine, binaryPath is user controlled

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thank you for the clarification. I have marked this finding as a false positive based on your feedback.

@ashwin-corridor
ashwin-corridor marked this pull request as ready for review January 28, 2026 23:26

@devin-ai-integration devin-ai-integration 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional flags.

Open in Devin Review

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

View issue and 8 additional flags in Devin Review.

Open in Devin Review

Comment on lines +103 to +105
hookshot.Register("droid-stop", handleDroidStop)
hookshot.Register("droid-pre-tool-use", handleDroidPreToolUse)
hookshot.Register("droid-user-prompt-submit", handleDroidUserPromptSubmit)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Handler overwrite: unified handlers silently replaced by platform-specific handlers

In the example file, unified handlers (OnStop, OnBeforeExecution, OnPromptSubmit) are called first, which internally register handlers for droid-stop, droid-pre-tool-use, droid-user-prompt-submit, cascade-pre-run-command, and cascade-pre-user-prompt. Then the code explicitly registers platform-specific handlers for the same command names, which silently overwrites the unified handlers.

Click to expand

How this happens

The Register function at hookshot.go:89-91 simply overwrites existing handlers:

func Register(name string, handler Handler) {
    handlers[name] = handler
}

In the example:

  1. Line 75-78: hookshot.OnStop(handleStop) registers handlers including droid-stop (via unified.go:116)
  2. Line 103: hookshot.Register("droid-stop", handleDroidStop) overwrites that handler

Similar overwrites happen for:

  • droid-pre-tool-use (unified at line 76 → overwritten at line 104)
  • droid-user-prompt-submit (unified at line 78 → overwritten at line 105)
  • cascade-pre-run-command (unified at line 76 → overwritten at line 95)
  • cascade-pre-user-prompt (unified at line 78 → overwritten at line 97)

Impact

The unified handler logic (which provides consistent StopContext, ExecutionContext, PromptContext with platform detection and decision mapping) is completely lost. The platform-specific handlers bypass the unified API contract.

Recommendation: Remove the platform-specific handler registrations for droid and cascade (lines 95-97, 103-105) since they conflict with the unified handlers. Alternatively, if platform-specific behavior is needed, don't use the unified handlers for those platforms.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is fine, it's a sample doc

Comment thread unified.go
ToolName string // MCP tool name (e.g., "mcp__server__tool")
ToolInput json.RawMessage // Tool input parameters as JSON
ServerURL string // MCP server URL (Cursor only, for URL-based servers)
ServerURL string // MCP server URL (Cursor/Cascade only, for URL-based servers)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does the ServerURL follow the same format as Cursor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not certain! I'll test it out with the e2e extension flow and then update this as needed.

@tanuj-corridor tanuj-corridor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • i did not test installation via Corridor extension
  • the schemas look good
  • we should document fields that differ from existing hook parsing logic (how we'd whitelist/blacklist serverUrl, interface for raw payload, Session vs. Execution ID)

@ashwin-corridor
ashwin-corridor merged commit 81c893e into main Jan 30, 2026
2 checks passed
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.

3 participants