Skip to content

[FEATURE]: allow external symlink targets via external_directory consent #30788

Description

@danielxxomg
  • I have searched existing issues and confirmed this is not a duplicate

Problem

OpenCode currently has two related but separate symlink issues:

  1. Symlinked/junction directories need to be visible as directories in pickers and file listing.
  2. Symlinks inside the project that point outside the project need a safe user-consent path.

The first problem is being discussed in #28526 / #28532. This issue is for the second problem.

A common workflow is to link external project context into a repo, for example:

ln -s ~/Obsidian/projects/my-app ./notes

The desired behavior is that OpenCode can read/write ./notes only after the user explicitly approves access to the resolved external target. Without that consent, following the symlink would be a symlink escape vulnerability: a repository could contain a link to /, ~/.ssh, or another sensitive directory.

Proposed behavior

When OpenCode encounters a symlink or junction inside the project whose resolved target is outside the active project/worktree:

  1. Detect that the canonical target escapes the project boundary.
  2. Prompt with the existing external_directory permission instead of silently treating the path as internal.
  3. Show both the link path and target path in the prompt, for example:
Permission required
Access external directory ~/Obsidian/projects/my-app

Link: ./notes
Target: ~/Obsidian/projects/my-app
Pattern: ~/Obsidian/projects/my-app/**
  1. Allow once permits only the current operation.
  2. Allow always permits matching operations until OpenCode restarts, consistent with current permission behavior.
  3. Persistent approval can use existing config:
{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "external_directory": {
      "~/Obsidian/projects/my-app/**": "allow"
    }
  }
}
  1. Tool-specific permissions still apply. For read-only external symlinks:
{
  "permission": {
    "external_directory": {
      "~/Obsidian/projects/my-app/**": "allow"
    },
    "edit": {
      "~/Obsidian/projects/my-app/**": "deny"
    }
  }
}

Use cases

  • Notes/memory layer: Linking an Obsidian vault into a project so the agent can read/write notes while working on code
  • Virtual workspace: Symlinking multiple projects into one place for agent to work across them
  • Shared resources: Linking shared configs, libraries, or documentation across projects

Security requirements

  • Do not treat external symlink targets as part of the project without approval.
  • Bind approval to the resolved target boundary, not only the symlink path.
  • For write/edit/apply_patch flows, revalidate immediately before mutation so a symlink target swap after approval is rejected.
  • Broken symlinks and symlink loops must fail safely.
  • Windows junctions/reparse points should follow the same rules.
  • Existing external_directory deny rules must still win.
  • Existing config ordering and wildcard semantics should remain unchanged.

Why use external_directory

OpenCode already has the right permission primitive:

  • external_directory prompts when tools touch paths outside the project.
  • The UI already supports Allow once, Allow always, and Reject.
  • Allow always is already documented as session-scoped.
  • Persistent grants already exist through permission.external_directory in opencode.json.

This keeps the implementation focused and avoids introducing a new trust store before the product needs one.

Non-goals

Related issues / PRs

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions