deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:oitsukiii/deepseek-harness-lan
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
English | 简体中文
Run DeepSeek Harness Web UI on your home LAN — bind a specific IP, trust it, and open it from any device.
🎯 Target version: dsh
0.1.2-rc.1(commita66e470204) — the patch is written and verified against this version; other versions may break (see 🧩 Compatibility).
dsh's Web UI is officially restricted to localhost access only. --host 0.0.0.0 fails immediately:
error: --host 0.0.0.0 is intentionally not supported yet for safety:
it would expose remote code execution to the network; use 127.0.0.1 instead
This is a deliberate security design — the dsh Web UI sits on top of an agent that can execute arbitrary commands, and the official project does not want it exposed to the network.
But what if you want to open dsh from Windows, phones, or tablets on your home/office LAN? This repository provides 3 minimal patches + one-click scripts that let you bind a specific LAN IP and run the dsh Web UI safely.
--trusted-host CLI + trustedHosts fence in rpc-host.ts, so those files are no longer patched)apply.sh / revert.sh) — auto-detection, duplicate-application guard0.0.0.0 wildcard is still rejected; the --trusted-host explicit trust mechanism is preservedpnpm run build, page load, API calls, and privileged endpoints all passunrun) so pnpm run build doesn't fail mysteriouslyThe core order in one sentence: install the dsh source first, then run apply.sh — apply.sh is a patching tool, not a dsh installer.
node -v)pnpm -v)git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
⚠️ Must be a
git clonecheckout — the packagednpm installbuild won't work, because the patch modifies source files. ⚠️ Do not use a shallow clone (--depth 1) — the version pinning below needs the full history.The pinned target version is
0.1.2-rc.1(commita66e470204). If upstream has moved on (apply.shwill warn about a version mismatch), pin the version first:git checkout a66e470204 pnpm install
git clone https://github.com/oitsukiii/deepseek-harness-lan.git
cd deepseek-harness-lan
./apply.sh /path/to/deepseek-harness
apply.sh automatically: version check → verify the patch applies cleanly → apply → install the missing build dependency unrun. You're done when you see ✓ 补丁应用完成 (patch applied).
💡 Directory-independent:
deepseek-harness-lananddeepseek-harnesscan live in any directories (any combination) — the script locates its own patch and the dsh checkout by argument, with no hardcoded paths. Three ways to run: pass the dsh path as an argument,cdinto the dsh directory and run, or put deepseek-harness-lan next to dsh.
cd /path/to/deepseek-harness
pnpm run build
The randomUUID polyfill lives in the web bundle, you must rebuild for it to take effect.
pnpm dsh web --host 192.168.1.100 --trusted-host 192.168.1.100
Replace 192.168.1.100 with your LAN IP (check ip addr or your router's admin page).
On another device (Windows / phone / tablet), open in a browser:
http://192.168.1.100:3080
🔑 0.1.2-rc.1+ generates a per-launch session token. dsh prints the full URL including the token on startup, e.g.
dsh web: http://127.0.0.1:3080/?token=QvlADRxW9exunDHD7Wq6SiV53P7qsr-ERC_RT3PFW4AReplace
127.0.0.1with the LAN IP and open that URL on other devices. A new token is minted on every restart — grab it from the startup log again. (Without the token the server answers401.)
⚠️ If you opened it before, do a Ctrl + F5 hard refresh (the browser may cache the old JS).
Verification checklist:
crypto.randomUUID is not a function./revert.sh /path/to/deepseek-harness
Removes the patches with one command; afterwards you can git pull to update dsh normally.
If you don't want to modify the dsh source (keeping it pristine for zero-maintenance official upgrades), an SSH tunnel is the only clean LAN access method — zero patches, all three gates pass naturally.
dsh's trust check (browser-trust) looks at the request's Host header, not the source IP (DNS rebinding defense); and crypto.randomUUID is only available in a secure context (HTTPS or localhost). An SSH tunnel makes the browser always access dsh via 127.0.0.1, so:
| Three gates | Under SSH tunnel | Why |
|---|---|---|
| Gate 1 (CLI/schema) | ✅ not triggered | dsh listens on 127.0.0.1 = official default, no changes needed |
| Gate 2 (browser-trust) | ✅ includes privileged endpoints | Host header = 127.0.0.1 → loopback exemption (the original privileged endpoints only allow loopback anyway) |
| Gate 3 (randomUUID) | ✅ available | 127.0.0.1 is a secure context; the browser provides it natively |
# 1. Start dsh as official (no patch, listening on 127.0.0.1:3080)
cd /path/to/deepseek-harness
pnpm dsh web
On the computer you want to access from, open the tunnel (Windows 10/11 ships OpenSSH):
ssh -N -L 3080:127.0.0.1:3080 <user>@<NAS-IP>
Open in the browser:
http://127.0.0.1:3080
API configuration, privileged endpoints, and all features work normally.
| Option | Source changes | Computer | Phone/tablet | Official upgrades |
|---|---|---|---|---|
| SSH tunnel (this option) | ❌ zero changes | ✅ | ⚠️ configure a tunnel per device | direct git pull, seamless |
| deepseek-harness-lan patch (main option) | ✅ 3 minimal diffs | ✅ | ✅ open the URL directly | need to re-apply the patch |
After a reverse proxy (HTTP or HTTPS), the Host header becomes the LAN IP, while the original privileged endpoints (settings.describe, llm.providers, etc. — required for configuring model APIs) use isTrustedApiRequest(request, []) which only allows loopback, so the configuration API step necessarily returns 403. The SSH tunnel is the only "no source changes + full functionality" path; if you want every device to open a URL directly, use the main patch option.
📌 0.1.2-rc.1+ note: privileged endpoints now honor
--trusted-host(upstream adopted gate 2). If your reverse proxy forwards a Host/Origin that you pass via--trusted-host, the config API works too — but dsh still rejects binding0.0.0.0, so the reverse proxy must connect to a specific bind address (e.g.--host 127.0.0.1proxied by nginx on the same machine).
dsh officially blocks LAN access via three gates. The project's patches break through them one by one (gate 2 is now upstream):
0.0.0.0, schema only accepts two literalsSymptom: --host 0.0.0.0 fails immediately; binding a specific IP (e.g. 192.168.1.100) also fails to start:
ValidationError: invalid config:
- $.host expected "127.0.0.1" | "0.0.0.0" but got "192.168.1.100" (at host)
Cause (two layers of restrictions):
| Layer | Location | Restriction |
|---|---|---|
| CLI layer | packages/bundle/web-app/src/startup.ts |
options.host === '0.0.0.0' triggers program.error() and refuses to start (left as-is: the wildcard stays rejected) |
| Schema layer | packages/host/webserver/src/index.ts |
zod validates z.union([z.const('127.0.0.1'), z.const('0.0.0.0')]), and the type definition only allows those two values |
Patch (only the schema layer — the CLI layer already keeps the 0.0.0.0 rejection):
// packages/host/webserver/src/index.ts
- host: '127.0.0.1' | '0.0.0.0' // type
+ host: string
- host: z.union([z.const('127.0.0.1'), z.const('0.0.0.0')]).required() // zod
+ host: z.string().required()
Design trade-off: the 0.0.0.0 wildcard binds to every interface (including public/WG), which is dangerous; binding a specific IP exposes only one interface and stays controllable. So only specific IPs are unlocked — the wildcard stays rejected.
Symptom: the page opens, but every /api/* request returns HTTP 403, e.g.:
加载提供方目录失败: transport failure for /api/llm.providers: HTTP 403
Cause: there is a browser-trust fence in front of dsh's /api gateway (anti DNS-rebinding / cross-site attack) in packages/client/connection/src/index.ts:
// trust list: only when binding 0.0.0.0 does it collect all LAN IPs
// binding a specific IP → empty list → all non-localhost requests 403
const lanAddresses = bindHost === ALL_INTERFACES_HOST ? collectLanIps() : []
return { lanAddresses, trustedHosts: [...lanAddresses, ...extra] }
Additionally, privileged methods (settings.*, credentials.*, agentPreset.*, etc.) are checked against an empty trust list — meaning even with --trusted-host, those endpoints still only allow localhost:
if (PRIVILEGED_METHODS.has(method) && !isTrustedApiRequest(request, [])) {
return new Response('forbidden', { status: 403 })
}
Patch: none needed since 0.1.2-rc.1 — this gate was adopted upstream. packages/client/connection/src/rpc-host.ts now checks this.trustedHosts (configured from the CLI) instead of an empty list, so --trusted-host already applies to privileged endpoints. This repository therefore no longer patches packages/client/connection/src/index.ts; the patch file only covers the remaining 3 files.
Design trade-off: --trusted-host (the admin-configured explicit trust entry) also applies to privileged endpoints. Without --trusted-host, behavior is unchanged — still loopback-only, so the official security semantics are preserved.
crypto.randomUUID is not a functionSymptom: error when configuring APIs:
加载提供方目录失败: crypto.randomUUID is not a function
Cause: the browser Web Crypto API's crypto.randomUUID() is only available in a secure context (HTTPS or localhost). When accessing http://192.168.1.100:3080 (plaintext HTTP on the LAN), the function doesn't exist.
Patch (apps/web/src/main.ts, at the very top of the web entry):
// crypto.randomUUID is only available in secure contexts; add a UUIDv4
// implementation for plain-HTTP LAN environments
if (typeof globalThis.crypto === 'object' && typeof globalThis.crypto.randomUUID !== 'function') {
try {
globalThis.crypto.randomUUID = () =>
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
} catch { /* keep as-is in read-only crypto environments */ }
}
💡 The UUID is only used for local identifiers (attachment drafts, session ids, etc.) and is not involved in security checks, so the polyfill has no security risk.
--host 0.0.0.0 (still rejected after patching — a deliberately kept line of defense).--trusted-host is the official browser-trust mechanism's explicit trust entry — only fill in your own LAN IP.This patch is written and verified against dsh 0.1.2-rc.1 (commit a66e470204).
✅ Upstream note (0.1.2-rc.1): two of the original four LAN changes were adopted upstream — the CLI now has
--trusted-hostandpackages/client/connection/src/rpc-host.tschecksthis.trustedHostsinstead of an empty list — so the current patch set is down to 3 files:
packages/host/webserver/src/index.ts— widen the bind-host schema from two literals (127.0.0.1/0.0.0.0) tostring(LAN IPs);apps/web/src/main.ts—crypto.randomUUIDpolyfill for plain-HTTP LAN serving;packages/client/tsdown.client.ts—REPOSITORY_ROOTviaprocess.cwd()instead ofimport.meta.url(fixes the workspace manifest lookup when tsdown compiles this preset withunrunintonode_modules/.unrun, which otherwise breaks the build withno packages/*/*/package.json declares the name …).
⚠️ Known upstream build quirk (0.1.2-rc.1): on a clean checkout, the full
pnpm run buildmay fail withCannot find entry: ["lib/types/{index,invariant,startup}.js"]attributed to@deepseek-ai/dsh-root. Cause: leftover directories from older dsh versions (e.g.packages/client/runtime,packages/host/apiproxy) that were removed upstream but survivegit checkoutbecause they contain only ignored files (node_modules). Fix: remove the stale directories (git clean -ndx <dir>to list, then delete), then rebuild.
git apply --check) → full pnpm run build succeeds → Web UI page load → /api calls → privileged endpoints (settings.describe, etc.) all pass.apply.sh's git apply --check fails and aborts safely without dirtying your repo (this is a protection mechanism, not a bug).apply.sh on the new version and note the conflicting files when it fails;revert.sh removes the patches in one command; afterwards you can git pull to update dsh normally.One patch targets one dsh version line; earlier/different versions may fail git apply --check (that's the safety mechanism — it aborts without dirtying your repo).
| dsh version | dsh Released | Which patch should I apply? |
|---|---|---|
0.0.1-rc.1 (npm) |
2026-08-10 | — |
0.0.1-rc.2 (npm) |
2026-08-11 | — |
0.0.1-rc.5 (npm) |
2026-08-12 | — |
0.1.0-rc.2 (npm) |
2026-08-13 | — |
0.1.0-rc.3 (npm) |
2026-08-13 | — |
0.1.0-rc.6 (npm) |
2026-08-13 | — |
0.1.0-rc.7 (release) |
2026-08-17 | v0.1.1 |
0.1.0-rc.8 (release) |
2026-08-19 | v0.2.0 |
0.1.1-rc.1 (release) |
2026-08-21 | v0.2.0 |
0.1.1-rc.2 (release) |
2026-08-21 | v0.2.0 |
0.1.2-rc.1 (release) |
2026-08-29 | v0.3.0 ⭐ current (3-file patch; 2 of 4 LAN changes now upstream) |
💡 Rule of thumb: versions rc.8 and newer are covered by the current patch; rc.7 and older need the older patch or an upgrade. Early releases (
0.0.1-rc.x,0.1.0-rc.2/3/6) were transitional within 3 days of the public launch — nobody should still be running them.
Q: Can I access it from my phone?
A: Yes. As long as the phone is on the same LAN, open http://<NAS-IP>:3080 in the browser.
Q: What happens when dsh updates officially after patching?
A: apply.sh runs git apply --check first; if the upstream code changed, it fails and aborts without dirtying your repo. Revert with revert.sh, then git pull normally.
Q: Why not run it with Docker? A: dsh is officially distributed as a Node.js / npm package with no official Docker image; this project only modifies source, so following the official install path is the most stable.
Q: Will the official project accept this patch? A: The official project deliberately blocks wildcard binding and is unlikely to relax it soon; but the "bind a specific IP + explicit trust" idea is worth an issue/discussion upstream.
| File | Addresses |
|---|---|
patches/deepseek-harness-lan.patch |
unified diff of all 3 changes (use directly with git apply) |
MIT License — an unofficial community project unrelated to DeepSeek Harness, for learning and personal reference only.
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。