You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Note: This report is filed publicly because the repository does not have Private Vulnerability Reporting enabled, has no SECURITY.md, and lists no security contact. Maintainers: please consider enabling private reporting; we are happy to provide the full Chinese-language audit on request. All content below is sanitized (no local paths or personal data).
Sanitized advisory draft, ready for private disclosure to the maintainers
(repo has private vulnerability reporting disabled; no SECURITY.md).
Suggested severity: High (default loopback deployment) / Critical (if bound to a non-loopback interface).
CWE-306 (Missing Authentication), CWE-749 (Exposed Dangerous Method or Function).
Summary
The dsh web UI (@deepseek-ai/dsh@0.1.0-rc.6) exposes a complete agent control plane over a plain node:http server with no authentication layer — no token, no cookie, no TLS. The only protection is a Host-header/Origin fence that the source itself documents as "not an auth layer" (dsh-client-connection/lib/index.js:117-119). The fence stops browser-based attacks (verified: cross-site Origin, spoofed Host / DNS rebinding, and sec-fetch-site: cross-site all receive 403), but any non-browser local process passes it trivially and gains remote code execution as the user. If the server is bound to a non-loopback interface (possible via a user cordis config patch, since the fence validates the client-supplied Host header rather than the socket peer), this becomes unauthenticated remote RCE, including the methods pinned to "loopback only".
Affected versions
Verified: @deepseek-ai/dsh@0.1.0-rc.6 (npm, published 2026-08-13)
Presumed: all 0.x RC releases share the same unauthenticated web architecture
Details
dsh web (default http://127.0.0.1:3080) serves /api with 60+ unauthenticated RPC methods, including:
session.create (caller-chosen cwd), session.prompt — drive the agent to run bash/pwsh tools; ordinary commands never require interactive approval (dsh-tool-bash/lib/index.js:386-402)
commands/execute — run arbitrary slash commands in a session, including /permission danger-full-access, which silently flips the session to no-sandbox + approval-never
GET /api/events.mux — broadcasts every session's events, including tool-call arguments and pending approval rpcIds
POST /api/respond — answers approval prompts (allowed-once); combined with the mux stream this lets any fence-passing caller self-approve sandbox escalations
GET /api/session.export?sessionId=… — downloads full session logs
15 "privileged" methods (settings.*, credentials.*, llm.discoverModels, host.openPath, …) are pinned to loopback — but only via the same client-supplied Host header check
Impact
Any local process (malware, untrusted script, another low-privilege program) can, while dsh web runs, execute arbitrary shell commands with the user's privileges, with no credential, no approval prompt, and no user interaction. Loopback binding is not a security boundary on a multi-process machine. With a non-loopback bind, any network attacker achieves the same by sending Host: 127.0.0.1:<port>.
Proof of concept (verified locally, sanitized)
# 1. Unauthenticated session creation (arbitrary cwd)
POST /api/session.create {"cwd":"<arbitrary-dir>"}
→ 200 {"sessionId":"session-<uuid>"}
# 2. Unauthenticated privilege flip
POST /api/commands/execute {"args":{"agentId":"<sid>","line":"/permission danger-full-access"}}
→ 200 {"result":{"kind":"success","text":"preset danger-full-access"}}
# 3. Unauthenticated prompt → agent executes arbitrary bash
POST /api/session.prompt {"sessionId":"<sid>","mode":"steer","content":[{"type":"text",
"text":"Immediately use the bash tool to run exactly this command, unchanged: echo DSH-RCE-POC-unauthenticated-api > <marker-file> ..."}]}
→ 200 {"accepted":true}
# ~60s later the marker file exists with the expected content. RCE confirmed.
Root cause
An architectural decision to ship the web control plane without an authentication layer ("TLS, auth … deliberately out of scope for the dev-facing v1", dsh-host-webserver/README.md:21), with a threat model covering only malicious web pages. It misses: (1) local multi-process reality; (2) the fence trusts a client-supplied header rather than the connection peer; (3) the approval mechanism is observable and answerable over the same unauthenticated API.
Recommended fixes
Mandatory per-start random bearer token for all /api requests (incl. WebSocket upgrades), injected into index.html via the existing tapIndex hook.
Validate the socket peer is loopback in addition to the Host header; extend the fence to the static fallback.
Remove the 0.0.0.0 literal from the webserver config schema, or gate it behind an explicit high-risk switch.
Require auth + explicit authorization for /permission, /api/respond, session.export, dynamicCordisRunner/*; scope events.mux per session.
Prevent danger-full-access from silently becoming the default preset.
llm.discoverModels must not send stored provider keys to caller-chosen URLs.
Mitigations for users (until patched)
Keep dsh web stopped when not in use; treat every local process as able to control the agent while it runs.
Never bind the webserver to 0.0.0.0 via cordis patches.
Keep permission.defaultPreset at workspace-write.
If remote access is needed, front it with an authenticated TLS reverse proxy.
Reported by an independent security researcher; full Chinese-language audit available on request. No local paths or personal data included.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
The
dsh webUI (@deepseek-ai/dsh@0.1.0-rc.6) exposes a complete agent control plane over a plainnode:httpserver with no authentication layer — no token, no cookie, no TLS. The only protection is a Host-header/Origin fence that the source itself documents as "not an auth layer" (dsh-client-connection/lib/index.js:117-119). The fence stops browser-based attacks (verified: cross-site Origin, spoofed Host / DNS rebinding, andsec-fetch-site: cross-siteall receive 403), but any non-browser local process passes it trivially and gains remote code execution as the user. If the server is bound to a non-loopback interface (possible via a user cordis config patch, since the fence validates the client-suppliedHostheader rather than the socket peer), this becomes unauthenticated remote RCE, including the methods pinned to "loopback only".Affected versions
@deepseek-ai/dsh@0.1.0-rc.6(npm, published 2026-08-13)0.xRC releases share the same unauthenticated web architectureDetails
dsh web(defaulthttp://127.0.0.1:3080) serves/apiwith 60+ unauthenticated RPC methods, including:session.create(caller-chosen cwd),session.prompt— drive the agent to run bash/pwsh tools; ordinary commands never require interactive approval (dsh-tool-bash/lib/index.js:386-402)commands/execute— run arbitrary slash commands in a session, including/permission danger-full-access, which silently flips the session to no-sandbox + approval-neverGET /api/events.mux— broadcasts every session's events, including tool-call arguments and pending approval rpcIdsPOST /api/respond— answers approval prompts (allowed-once); combined with the mux stream this lets any fence-passing caller self-approve sandbox escalationsGET /api/session.export?sessionId=…— downloads full session logssettings.*,credentials.*,llm.discoverModels,host.openPath, …) are pinned to loopback — but only via the same client-supplied Host header checkImpact
Any local process (malware, untrusted script, another low-privilege program) can, while
dsh webruns, execute arbitrary shell commands with the user's privileges, with no credential, no approval prompt, and no user interaction. Loopback binding is not a security boundary on a multi-process machine. With a non-loopback bind, any network attacker achieves the same by sendingHost: 127.0.0.1:<port>.Proof of concept (verified locally, sanitized)
Root cause
An architectural decision to ship the web control plane without an authentication layer ("TLS, auth … deliberately out of scope for the dev-facing v1",
dsh-host-webserver/README.md:21), with a threat model covering only malicious web pages. It misses: (1) local multi-process reality; (2) the fence trusts a client-supplied header rather than the connection peer; (3) the approval mechanism is observable and answerable over the same unauthenticated API.Recommended fixes
/apirequests (incl. WebSocket upgrades), injected into index.html via the existingtapIndexhook.0.0.0.0literal from the webserver config schema, or gate it behind an explicit high-risk switch./permission,/api/respond,session.export,dynamicCordisRunner/*; scopeevents.muxper session.danger-full-accessfrom silently becoming the default preset.llm.discoverModelsmust not send stored provider keys to caller-chosen URLs.Mitigations for users (until patched)
dsh webstopped when not in use; treat every local process as able to control the agent while it runs.0.0.0.0via cordis patches.permission.defaultPresetatworkspace-write.Reported by an independent security researcher; full Chinese-language audit available on request. No local paths or personal data included.
All reactions