Skip to content

VTCode executes workspace lifecycle hook commands without per-command approval

High
vinhnx published GHSA-wqgw-crr5-cr2p Aug 14, 2026

Package

cargo vtcode (Rust)

Affected versions

< 0.145.0

Patched versions

>= 0.145.0

Description

Summary

VTCode automatically loads a workspace-root vtcode.toml. In the latest upstream main revision 8fef6b4d24fa42088203c999136000cbdfe62bb7 (the tested build reports version 0.143.4), and in the separately cross-checked 0.143.4 release source, the file can define hooks.lifecycle.session_start[].hooks[].command. When a user starts or resumes a normal VT Code Agent session, VT Code passes that value to sh -c with the workspace as the child current directory. The path does not require a model-generated tool call and does not perform a lifecycle-specific first-use approval or effective-command/content-change check.

This was dynamically reproduced with a harmless marker command in an isolated workspace. The configuration group created the marker; an otherwise identical workspace without vtcode.toml did not. The exact trigger is starting or resuming a VT Code Agent session in the workspace. This report does not claim that merely opening a directory without running VT Code is sufficient, and it does not include the separate status-line, custom-provider, Copilot, or Codex static paths as dynamically confirmed findings.

Impact

An attacker who can place or modify the effective workspace vtcode.toml can cause an arbitrary shell command to run with the privileges and inherited environment of the VT Code process when the victim starts or resumes a session in that workspace. The command runs from the workspace and can read or modify files available to the current user, access any permitted local resources, and launch further processes. The impact is local command execution, not privilege escalation.

Preconditions and Trust Boundary

The victim must run VT Code against the workspace and reach normal Agent session setup. The workspace configuration must contain a non-empty session_start hook command; lifecycle hooks are empty by default. A user who explicitly creates a hook has intentionally enabled this product feature. The boundary issue is that later repository-controlled configuration content can select or change the command without a new, understandable approval bound to the final command and referenced files.

Reproduction

Use a fresh temporary directory and build VT Code from main@8fef6b4d (the tested build reports version 0.143.4). Create a file named vtcode.toml with the following content:

[agent]
provider = "ollama"

[hooks.lifecycle]
quiet_success_output = true

[[hooks.lifecycle.session_start]]

[[hooks.lifecycle.session_start.hooks]]
command = "printf 'vtcode-session-start-marker\\n' > .vtcode-session-start-marker"

Start a normal session from that workspace:

vtcode --workspace /path/to/poc-workspace

After the session UI initializes, interrupt the session if no local provider is available, then inspect the marker:

test -f /path/to/poc-workspace/.vtcode-session-start-marker
test "$(cat /path/to/poc-workspace/.vtcode-session-start-marker)" = "vtcode-session-start-marker"

For the control group, use a second empty workspace without vtcode.toml and run the same binary and session startup, adding --provider ollama if needed to bypass first-run provider selection:

vtcode --workspace /path/to/control-workspace --provider ollama
test ! -e /path/to/control-workspace/.vtcode-session-start-marker

The marker command is intentionally limited to writing a fixed file inside the PoC workspace. It does not read credentials, contact a remote endpoint, alter shell startup files, or delete data.

Observed Result

On 0.143.4 built from 8fef6b4d24fa42088203c999136000cbdfe62bb7, the configured workspace created .vtcode-session-start-marker with the expected content during session setup. The no-configuration control did not create the marker. Both runs used fresh temporary home/config directories with no VTCODE_TRUST_WORKSPACE override or pre-existing trust record.

The test harness did not answer VT Code's terminal cursor-position query, so interrupting the TUI reported a cleanup error after startup. The marker was created before that error, and the same terminal limitation affected the control group.

Source Evidence

  1. Workspace discovery and configuration loading: src/startup/config_loading.rs:20-66 resolves the workspace and builds the configuration; crates/codegen/vtcode-config/src/loader/manager.rs:128-168 adds the workspace-root vtcode.toml layer and :248-265 merges and validates the effective configuration.
  2. Hook configuration: crates/codegen/vtcode-config/src/hooks.rs:18-32,132-145 defines session_start and accepts a command string with validation limited to type, non-empty content, matcher syntax, and timeout bounds.
  3. Session trigger: src/agent/runloop/unified/session_setup/ui.rs:104-110 constructs LifecycleHookEngine from the effective hooks; :380-394 invokes run_session_start() during session UI setup.
  4. Process sink: crates/codegen/vtcode-core/src/hooks/lifecycle/engine.rs:69-98 dispatches each matching command; :518-546 executes sh -c <configured command> in the workspace and calls spawn().
  5. Trust separation: src/agent/runloop/unified/session_setup/init.rs:183-190,691-698 applies workspace trust to ToolRegistry prompt policy, while the lifecycle engine is constructed and executed through the separate path above.

Recommended Remediation

Before running a workspace-controlled lifecycle command, require an explicit per-workspace/per-launcher approval that displays the resolved shell command, working directory, and relevant environment policy. Bind the approval to the canonical workspace, repository revision or effective configuration digest, and the identity/content digest of referenced scripts. Revalidate those values immediately before every lifecycle spawn and after configuration reload. Do not reuse a tool-policy or full-auto trust bit as blanket approval for executable workspace configuration. Consider disabling workspace executable hooks by default or requiring a user-created local trust receipt before enabling them.

Acknowledgement

Thank you to @glmgbj233 for the detailed report, the careful dynamic reproduction, and the remediation suggestions that shaped the fix. Workspace-controlled lifecycle hooks now require explicit per-workspace approval of the exact command set before any command can run, and approvals are revalidated against the current configuration digest before every lifecycle spawn.

Severity

High

CVE ID

No known CVE

Weaknesses

Inclusion of Functionality from Untrusted Control Sphere

The product imports, requires, or includes executable functionality (such as a library) from a source that is outside of the intended control sphere. Learn more on MITRE.