Skip to content

[Security] Repository-provided pane.json setup command executes after project registration without command-level confirmation #376

Description

@glmgbj233

Summary

Pane automatically executes lifecycle commands from repository-controlled configuration files when creating a worktree session. In the latest checked release, v2.4.43 at commit 420590c439931e2b484d084ad4ab1e0ec418cc5d, a repository that already contains pane.json with scripts.setup causes the command to run after the user registers the repository and creates a Pane. There is no command-specific confirmation, workspace trust decision, configuration digest, or content-change check before execution.

This report does not claim that merely opening an arbitrary directory causes RCE. The user must explicitly register the repository in Pane and create a Pane/worktree session. The security concern is that repository registration is treated as sufficient authorization for every command-bearing lifecycle value in the repository, including an initial pane.json supplied by an external project.

Impact and Preconditions

The command executes with the Pane process user's privileges, from the new worktree, with an inherited environment and enhanced shell PATH. A malicious setup command could modify files, install software, access local services, or use available credentials.

Required preconditions are:

  1. The user clones or otherwise obtains a Git repository containing the attacker-controlled pane.json or a compatible supported configuration file.
  2. The user explicitly registers that repository as a Pane project.
  3. The user creates a new worktree Pane/session without a private Project Settings build_script override.

The product documentation describes repository lifecycle scripts as team-shared defaults and says setup runs automatically when a new worktree/session is created. This makes the behavior expected for trusted projects, but it does not provide a separate approval for the resolved command or establish that future command-bearing content is trusted.

Reproduction

The following PoC uses only a marker file and does not read credentials, access the network, start Claude, or modify shell profiles.

Create a fresh Git repository, add the following tracked pane.json before registering it in Pane, and commit it:

{
  "scripts": {
    "setup": "printf 'pane-v2.4.43-marker\\n' > .pane-security-marker"
  }
}

The following commands create that fixture. Keep the resulting POC_ROOT value for the second terminal:

export POC_ROOT="$(mktemp -d)"
export POC_REPO="$POC_ROOT/pane-security-repro"
mkdir -p "$POC_REPO"
git init "$POC_REPO"
git -C "$POC_REPO" config user.name "Pane Security PoC"
git -C "$POC_REPO" config user.email "[email protected]"
printf '# Pane security PoC\n' > "$POC_REPO/README.md"
cat > "$POC_REPO/pane.json" <<'EOF'
{
  "scripts": {
    "setup": "printf 'pane-v2.4.43-marker\\n' > .pane-security-marker"
  }
}
EOF
git -C "$POC_REPO" add README.md pane.json
git -C "$POC_REPO" commit -m "add repository setup configuration"

Build and start Pane from a clean v2.4.43 source checkout in an isolated headless environment:

pnpm install --frozen-lockfile
pnpm run build:main
pnpm --filter runpane build

export PANE_DIR="$POC_ROOT/app-data"
export RUNPANE_TELEMETRY_DISABLED=1
pnpm exec electron --no-sandbox --disable-gpu --ozone-platform=headless \
  main/dist/main/src/daemon/headless.js

In a second terminal, set POC_ROOT to the value from the first terminal, then register the already-configured repository and create a Pane:

export PANE_DIR="$POC_ROOT/app-data"
export POC_REPO="$POC_ROOT/pane-security-repro"
node packages/runpane/dist/cli.js repos add \
  --path "$POC_REPO" \
  --name pane-security-repro --yes --json
node packages/runpane/dist/cli.js panes create \
  --repo 1 --name initial-config-repro \
  --tool-command true --no-focus --yes --json

--yes only confirms the explicit Pane project/session operation. It is not a confirmation of the command contained in pane.json. The terminal tool is true so no Agent is started.

Expected secure behavior: Pane should either keep the setup disabled until the user approves the exact resolved command, or show a trust/confirmation decision bound to this repository revision and configuration content.

Actual result: panes create succeeds and creates $POC_REPO/worktrees/initial-config-repro/.pane-security-marker before the command returns. The marker contains pane-v2.4.43-marker. The daemon log contains entries equivalent to:

[TaskQueue] Running build script for session ...
[SessionManager] Executing build command: printf 'pane-v2.4.43-marker\n' > .pane-security-marker

Repeat the same test with a fresh repository and fresh PANE_DIR, but omit pane.json. The same panes create request succeeds and no marker is created.

Source-to-sink evidence

  • main/src/ipc/runpane.ts:153-215 implements repos add; it validates that the path exists and is a Git worktree, then stores it as a Pane project. main/src/ipc/runpane.ts:1969-1994 shows that validation is limited to filesystem and Git-repository checks; it does not inspect repository provenance or lifecycle configuration.
  • main/src/services/projectConfigDetector.ts:101-143 automatically checks pane.json, conductor.json, .gitpod.yml, and .devcontainer/devcontainer.json, parses the first configuration containing a lifecycle script, and returns the command-bearing fields.
  • main/src/services/taskQueue.ts:385-426 resolves setup from the newly created session worktree when the private build_script override is empty, then calls sessionManager.runBuildScript during session creation.
  • main/src/services/sessionManager.ts:1316-1350 iterates over the resolved commands, and main/src/services/sessionManager.ts:1393-1405 passes each command to Node child_process.exec with the worktree as cwd and inherited environment variables.
  • docs/CONFIG_FILES.md:39-45,70-82,118-122 documents automatic setup execution, the Project Settings fallback, and branch-local configuration behavior. The document presents source badges and previews, but does not describe a security approval for the command.

Why repository registration is not command approval

Registering a repository is a normal Pane operation needed to save its path, create worktrees, and manage sessions. A user may intentionally register an external open-source repository, so this report does not treat the registration action itself as an authorization bypass. However, the registration record does not identify the exact setup command, its arguments, referenced files, revision, or configuration digest. Consequently, an initial external repository can contain a command that the user never reviewed, while the next session creation executes it automatically.

The same missing command-level confirmation also affects scripts.run when the user presses Play and scripts.archive during session deletion, although those paths require their respective additional user actions. The minimal automatic path for this report is scripts.setup during new worktree session creation.

Suggested remediation

  1. Treat repository lifecycle configuration as executable configuration and keep it disabled by default for repositories without an explicit trust decision.
  2. Before the first execution, display the source file, revision, normalized command/arguments, cwd, referenced script identity, and environment policy; require explicit approval.
  3. Bind approval to the canonical repository/worktree identity, user, launcher surface, revision or configuration digest, and final command-bearing fields.
  4. Revalidate immediately before each process spawn and invalidate approval after branch switches, configuration changes, referenced script changes, or symlink target changes.
  5. Keep repository registration, Agent/session creation, and command approval as separate authorization events, and record a local receipt describing exactly what was allowed.

Metadata

Metadata

Assignees

No one assigned

    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