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.0-rc.7(commit99f6f02) — 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 4 minimal patches + one-click scripts that let you bind a specific LAN IP and run the dsh Web UI safely.
apply.sh / revert.sh) — auto-detection, duplicate-application guard0.0.0.0 wildcard is still rejected; the --trusted-host explicit trust mechanism is preservedunrun) 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 current master branch is the target version
0.1.0-rc.7(commit99f6f02). If upstream has moved on (apply.shwill warn about a version mismatch), pin the version first:git checkout 99f6f02 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
⚠️ 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) | ✅ 4 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.
dsh officially blocks LAN access via three gates. The project's 4 patches break through them one by one:
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 |
| 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:
// 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()
// packages/bundle/web-app/src/startup.ts
- if (options.host === '0.0.0.0') {
- program.error('...intentionally not supported yet for safety...')
- }
+ // only reject the 0.0.0.0 wildcard; specific LAN IPs are allowed
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 (packages/client/connection/src/index.ts):
- && !isTrustedApiRequest(request, [])) { // empty list: loopback only
+ && !isTrustedApiRequest(request, trustedHosts)) { // explicit trust takes effect
Design trade-off: --trusted-host (the admin-configured explicit trust entry) now 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.0-rc.7 (commit 99f6f02).
pnpm run build build → 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.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 4 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,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。