Skip to content

Untrusted CWD config.yaml can execute an arbitrary terminal command without confirmation #21

Description

@glmgbj233

Summary

CodeKanban automatically prefers config.yaml from its process current working directory and uses terminal.shell.linux as the command for newly created terminal sessions. A repository contributor can commit this file with a command and arguments that CodeKanban will execute when a user starts CodeKanban from that checkout and creates a terminal. The command runs with the CodeKanban process user's local privileges and no workspace-trust, first-use command confirmation, configuration fingerprint, or execution-time revalidation is applied.

This is deterministic configuration execution and does not depend on prompt injection, an LLM request, or the user typing a command inside the terminal. The minimum trigger is more specific than opening a directory: the victim must launch CodeKanban with the checkout as CWD and then create or restore a terminal for the project.

Affected Revision

Observed on v0.44.1, commit 7d4900acee04c6755982fb0251950f5ace62a182 (master as of 2026-08-10). The same source path was present in the previously reviewed v0.41.0 commit 7c63e5d.

Impact and Preconditions

Impact: an attacker-controlled existing executable and argument vector can be started as a PTY child with the user's CodeKanban privileges and with the selected worktree as its working directory. A shell or interpreter can also be selected if available on the host, so the practical effect is arbitrary local command execution subject to the host's existing binaries and the victim's permissions.

Preconditions: the victim must run CodeKanban from the malicious checkout, such as npx codekanban while the checkout is the current directory, and must create a terminal or restore one for that project. A project merely added through the UI while CodeKanban was started from another directory does not load that project's config.yaml through this specific path. A login may be required in deployments with authentication enabled; login does not approve the resolved shell command.

Reproduction

The following PoC uses only the host touch executable and creates a marker in the test workspace. It does not read credentials, make an external network request, or modify shell profiles.

  1. Build the affected revision and create an isolated checkout workspace:
git clone --branch v0.44.1 https://github.com/fy0/CodeKanban.git CodeKanban-poc
cd CodeKanban-poc
go build -o CodeKanban .
mkdir -p poc-workspace
cd poc-workspace
cat > config.yaml <<'YAML'
disableAutoOpenBrowser: true
terminal:
  shell:
    linux: "touch .codekanban-poc-marker"
YAML
export HOME="$PWD/.poc-home"
mkdir -p "$HOME"
../CodeKanban -H -b 127.0.0.1 -p 39071
  1. In another terminal, from the poc-workspace directory, create a project pointing at the current workspace and obtain its worktree ID:
WORKSPACE="$PWD"
PROJECT_JSON=$(curl -sS -X POST http://127.0.0.1:39071/api/v1/projects/create \
  -H 'Content-Type: application/json' \
  --data "{\"name\":\"CodeKanban config PoC\",\"path\":\"$WORKSPACE\",\"description\":\"isolated test\"}")
PROJECT_ID=$(printf '%s' "$PROJECT_JSON" | jq -r '.item.id')
WORKTREE_ID=$(curl -sS "http://127.0.0.1:39071/api/v1/projects/$PROJECT_ID/worktrees" | jq -r '.items[0].id')
  1. Create a terminal, which is the same backend action used by the terminal-create UI:
curl -i -X POST "http://127.0.0.1:39071/api/v1/projects/$PROJECT_ID/worktrees/$WORKTREE_ID/terminals" \
  -H 'Content-Type: application/json' \
  --data '{"workingDir":"","title":"config PoC","rows":24,"cols":80}'
test -f .codekanban-poc-marker

Expected result: the terminal-create request returns HTTP 201 and .codekanban-poc-marker exists. The terminal may already report closed because touch exits immediately. No confirmation dialog is shown before the configured command runs.

  1. Control test: repeat the same steps from a fresh workspace that has no config.yaml and a fresh HOME. The generated data-directory configuration selects the default /bin/bash; creating a terminal must not create .codekanban-poc-marker in that workspace.

Source-to-Sink Evidence

  • utils/app_config.go:263-274 selects config.yaml from the process CWD whenever it exists, before the data-directory configuration.
  • main.go:91-92 loads that configuration during normal startup.
  • api/terminal_routes.go:70-86 registers terminal creation, and api/terminal_routes.go:446-500 calls terminal.Manager.CreateSession after only project/worktree and working-directory validation.
  • service/terminal/manager.go:94-112 resolves the configured shell; utils/shell.go:54-115 tokenizes it and checks executable availability, but has no trust or approval check.
  • service/terminal/session.go:362-373 calls exec.CommandContext(sessionCtx, s.command[0], s.command[1:]...), sets the worktree working directory, and starts the PTY.

The repository .gitignore ignores config.yaml, but this does not prevent an attacker-controlled repository from tracking the file or removing that ignore entry.

Suggested Fix

Do not automatically load command-bearing terminal settings from the process CWD. If project-level terminal settings are required, keep them disabled by default or separate them from safe project configuration and require explicit approval that displays the canonical workspace, exact executable, and arguments. Bind approval to the user, workspace/revision or configuration digest, launcher surface, and the resolved executable or referenced script. Recompute and revalidate the effective command immediately before the PTY spawn and repeat approval after configuration or referenced-content changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions