Skip to content

fix(security): disable filesystem edits by default for clink CLI#418

Open
lilmingwa13 wants to merge 5 commits intoBeehiveInnovations:mainfrom
lilmingwa13:fix/clink-safe-default
Open

fix(security): disable filesystem edits by default for clink CLI#418
lilmingwa13 wants to merge 5 commits intoBeehiveInnovations:mainfrom
lilmingwa13:fix/clink-safe-default

Conversation

@lilmingwa13
Copy link
Copy Markdown

Summary

This PR improves the security posture of the clink tool by enforcing safe-by-default behavior when executing external AI CLIs.

Previously, untrusted prompt input could be forwarded directly to a CLI configured with automatic file edit permissions (e.g. --permission-mode acceptEdits), allowing unintended filesystem modifications.

Changes

  • Disable filesystem edits by default
  • Introduce explicit opt-in (allow_edits) for write-capable CLI execution
  • Enforce permission mode at runtime (not only via configuration)
  • Add optional editable path allow-list (editable_paths)

Security Impact

This change prevents untrusted remote input from triggering filesystem modifications without explicit authorization, enforcing a clear trust boundary between:

  • Remote MCP client input
  • Local CLI execution context

Backward Compatibility

  • Existing workflows remain functional when allow_edits=true
  • Default behavior is now safer for remote/untrusted use cases

Related Issue

Fixes #417

Disclosure

This issue has been submitted to MITRE for CVE consideration.
Full PoC and reproduction steps can be shared privately upon request.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security of the clink tool by implementing a safe-by-default approach for external AI CLI executions. It addresses a potential vulnerability where untrusted prompt input could lead to unauthorized filesystem modifications, establishing a clear trust boundary between remote input and local execution contexts.

Highlights

  • Enhanced Security Posture: The clink tool now defaults to disabling filesystem modifications when executing external AI CLIs, preventing unintended changes from untrusted input.
  • Explicit Opt-in for Edits: Introduced allow_edits and editable_paths parameters, requiring explicit authorization for write-capable CLI execution and allowing an optional allow-list for editable paths.
  • Runtime Permission Enforcement: Permission modes are now enforced at runtime, not solely through configuration, ensuring consistent security.
  • Updated Default Configuration: The claude.json client configuration was updated to set the default --permission-mode to default instead of acceptEdits.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces granular control over filesystem editing permissions for CLI agents, specifically for the Claude agent. It adds allow_edits and editable_paths parameters to the agent's run method and the CLinkRequest model, enabling explicit control over which paths an agent can modify. The changes include updating method signatures, implementing validation for editable paths (ensuring they are absolute and allow_edits is enabled), and modifying the command construction logic to pass these permissions to the underlying CLI. The default permission mode for the Claude client is also updated to 'default' (no edits). Additionally, the prompt now includes an explicit execution policy regarding filesystem modifications. Feedback indicates a bug in the _sanitize_permission_args method in clink/agents/claude.py where --permission-mode might be malformed if it's the last argument, and suggests simplifying its loop logic. There's also a recommendation to narrow the except Exception clause in _validate_editable_paths in tools/clink.py to more specific exceptions like TypeError or ValueError for improved error handling.

Comment thread clink/agents/claude.py Outdated
Comment on lines +68 to +96
def _sanitize_permission_args(self, args: list[str], *, allow_edits: bool) -> list[str]:
sanitized: list[str] = []
i = 0

while i < len(args):
arg = args[i]

if arg == "--permission-mode":
if i + 1 < len(args):
mode = args[i + 1]

if allow_edits:
sanitized.extend([arg, mode])
else:
sanitized.extend([arg, "default"])

i += 2
continue

sanitized.append(arg)
i += 1

if "--permission-mode" not in sanitized:
sanitized.extend([
"--permission-mode",
"acceptEdits" if allow_edits else "default",
])

return sanitized No newline at end of file
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.

high

The current implementation of _sanitize_permission_args has a bug when "--permission-mode" is the last argument in the list. It will append the flag without its value, leading to a malformed command. Additionally, the while loop is complex and can be simplified using an iterator, which makes the logic easier to follow and less prone to off-by-one errors.

I suggest refactoring this method to use an iterator. This approach is more Pythonic and correctly handles all cases, including a dangling "--permission-mode" flag.

    def _sanitize_permission_args(self, args: list[str], *, allow_edits: bool) -> list[str]:
        sanitized: list[str] = []
        found_perm_mode = False
        args_iter = iter(args)
        for arg in args_iter:
            if arg == "--permission-mode":
                found_perm_mode = True
                sanitized.append(arg)
                try:
                    mode = next(args_iter)
                    if allow_edits:
                        sanitized.append(mode)
                    else:
                        sanitized.append("default")
                except StopIteration:
                    # Dangling flag, append a default value.
                    sanitized.append("acceptEdits" if allow_edits else "default")
            else:
                sanitized.append(arg)

        if not found_perm_mode:
            sanitized.extend([
                "--permission-mode",
                "acceptEdits" if allow_edits else "default",
            ])

        return sanitized

Comment thread tools/clink.py Outdated
for raw_path in request.editable_paths:
try:
path = Path(raw_path)
except Exception:
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.

medium

Catching a broad Exception can hide unexpected errors. It's better to catch more specific exceptions. pathlib.Path can raise TypeError if the input is not a string or bytes, or ValueError for certain invalid paths (e.g., containing null characters). Catching these specific exceptions makes the error handling more precise.

Suggested change
except Exception:
except (TypeError, ValueError):

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60da719c6f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread clink/agents/claude.py Outdated
Comment on lines +79 to +80
if allow_edits:
sanitized.extend([arg, mode])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Force acceptEdits when edit mode is requested

When allow_edits=true, _sanitize_permission_args keeps whatever --permission-mode value is already configured instead of switching to an edit-capable mode. Because this commit also changed conf/cli_clients/claude.json to --permission-mode default, an opt-in edit request still runs Claude in default mode, so write workflows that explicitly set allow_edits will not get the intended behavior. Override existing --permission-mode to acceptEdits whenever allow_edits is true.

Useful? React with 👍 / 👎.

Comment thread clink/agents/base.py
Comment on lines +205 to +206
allow_edits: bool = False,
editable_paths: Sequence[str] = (),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce allow_edits for non-Claude runners

The new allow_edits/editable_paths controls are not enforced in BaseCLIAgent: _build_command still unconditionally forwards self.client.config_args. I checked conf/cli_clients/gemini.json and conf/cli_clients/codex.json, which include --yolo and --dangerously-bypass-approvals-and-sandbox; those write-capable flags will still be active even when allow_edits=false, so the “disabled by default” guarantee does not hold for these runners.

Useful? React with 👍 / 👎.

@lilmingwa13 lilmingwa13 changed the title Harden clink CLI execution by disabling filesystem edits by default fix(security): disable filesystem edits by default for clink CLI Mar 25, 2026
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.

Security Issue: Untrusted Prompt Forwarding Enables Arbitrary File Modification via CLI Integration

1 participant