@falling-ts/dsh-local-no-auth 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.cn.md +89 -0
- package/README.md +97 -0
- package/cordis.patch.yml +13 -0
- package/index.js +82 -0
- package/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 the dsh-local-no-auth contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.cn.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# dsh-local-no-auth
|
|
2
|
+
|
|
3
|
+
一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的
|
|
4
|
+
Cordis 插件:让 `dsh web` 的**仅回环本地实例**免浏览器会话认证——不需要
|
|
5
|
+
launch token、不需要 cookie——而且**不改上游源码的一个字**。
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
|---|---|
|
|
9
|
+
| 包名 | `@falling-ts/dsh-local-no-auth` |
|
|
10
|
+
| License | MIT |
|
|
11
|
+
| 平台 | 纯 Host 插件(无客户端半部、无 UI) |
|
|
12
|
+
|
|
13
|
+
## 适用场景
|
|
14
|
+
|
|
15
|
+
`dsh web` 用"启动 URL 里的 `?token=...` 交换 + 签名 cookie"守卫全部访问。
|
|
16
|
+
这在远程/共享部署下是对的;但对只监听回环的本地实例纯属仪式——能连到这个
|
|
17
|
+
端口的主机,本来就是这台机器本身。
|
|
18
|
+
|
|
19
|
+
## 实现原理(零源码改动)
|
|
20
|
+
|
|
21
|
+
上游所有认证判定都经由 `ctx.connection` 服务**实例**的三个方法:
|
|
22
|
+
|
|
23
|
+
| 方法 | 管辖 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `requestRejection` | `/api` 面(RPC / 流 / 网关)的 401/403 判定 |
|
|
26
|
+
| `authorizeIndex` | 前端 index 请求的 token/cookie 认证 |
|
|
27
|
+
| `authenticatedUrl` | 打印的启动 URL(追加 launch token) |
|
|
28
|
+
|
|
29
|
+
本插件在自身 `apply` 里先读取实时 `webServer` 绑定地址:**只有回环字面量才
|
|
30
|
+
继续**,否则 fail-loud 抛错拒载;然后才把这三个**实例方法**在运行时替换掉:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
connection.requestRejection = () => undefined // 所有 /api 请求直接放行
|
|
34
|
+
connection.authorizeIndex = () => true // index 立即返回
|
|
35
|
+
connection.authenticatedUrl = (url) => url // 打印的 URL 保持干净
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
各消费点(`/api` 路由、frontend-static 兜底席位、API 网关、web-app 的 URL
|
|
39
|
+
公告)都是按实例引用调用这三个方法的,因此整个本地面一次性变为免 token、
|
|
40
|
+
免 cookie。插件卸载(`dispose`)时恢复原方法。
|
|
41
|
+
|
|
42
|
+
## 安全边界
|
|
43
|
+
|
|
44
|
+
- **绑定地址闸门**:`apply` 读取实时 `webServer` 主机并校验为
|
|
45
|
+
`127.0.0.1` / `localhost` 才放行,否则抛错拒载(fail-loud,无静默兜底)。
|
|
46
|
+
上游 webserver 的 Config schema 只接受 `'127.0.0.1' | '0.0.0.0'`,因此
|
|
47
|
+
`0.0.0.0`(所有网卡)必然被拒——插件绝不会在"其它主机可达"的监听上静默生效。
|
|
48
|
+
- `dsh web` 只绑回环,CLI **拒绝 `--host 0.0.0.0`**,所以本插件自身不会把
|
|
49
|
+
服务暴露给其它主机。
|
|
50
|
+
- **不要**与任何让该端口可达性超出本机的手段搭配(共享机器上的 SSH 转发、
|
|
51
|
+
VLAN 回环、NAT hairpin)。
|
|
52
|
+
- 若上游未来允许非回环绑定,本插件会拒绝启动,而不是静默放行。
|
|
53
|
+
|
|
54
|
+
## 安装
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
# 本地目录安装(支持目录路径):
|
|
58
|
+
pnpm dsh plugin --profile web add <本目录绝对路径>
|
|
59
|
+
|
|
60
|
+
# 发布后:
|
|
61
|
+
dsh plugin --profile web add github:falling-ts/dsh-local-no-auth
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`dsh web` 没有 `--patch` 命令行叠加;插件通过 profile 自身的
|
|
65
|
+
`cordis.patch.yml` 引用本包的 `dsh.bundle.patch` 层激活。装完重启
|
|
66
|
+
`dsh web`。
|
|
67
|
+
|
|
68
|
+
## 验证
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
curl -i http://127.0.0.1:<port>/ # 200 + index.html,无需 token
|
|
72
|
+
curl -i -X POST http://127.0.0.1:<port>/api/session/list \
|
|
73
|
+
-H 'content-type: application/json' \
|
|
74
|
+
-d '{"type":"client-request","rpcId":"probe","method":"session/list","payload":{}}'
|
|
75
|
+
# 返回业务信封(2xx,result.ok 为 true/false)—— 绝不是 401
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 卸载
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
dsh plugin --profile web remove dsh-local-no-auth
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 已知限制
|
|
85
|
+
|
|
86
|
+
- 放行是进程级的:同一组合里的所有插件都会看到免认证的 `/api`——这正是
|
|
87
|
+
本地开发机想要的。
|
|
88
|
+
- 启动日志仍打印 `dsh web: http://...`(干净 URL);配合
|
|
89
|
+
`--trusted-host` 的 LAN 候选地址同样以干净 URL 打印。
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# dsh-local-no-auth
|
|
2
|
+
|
|
3
|
+
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Cordis
|
|
4
|
+
plugin that bypasses the `dsh web` browser-session authentication for the
|
|
5
|
+
**loopback-local** instance — no launch token, no cookie — **without modifying
|
|
6
|
+
a single line of upstream source**.
|
|
7
|
+
|
|
8
|
+
| | |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Package | `@falling-ts/dsh-local-no-auth` |
|
|
11
|
+
| License | MIT |
|
|
12
|
+
| Platform | Host-only (no client half, no UI) |
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
`dsh web` guards its surface with a launch-token exchange (the `?token=...`
|
|
17
|
+
on the printed URL) plus a signed browser cookie. That is correct for remote
|
|
18
|
+
or shared deployments, but for a loopback-only local instance it is pure
|
|
19
|
+
ceremony: any host that can reach the port is a host already on the machine.
|
|
20
|
+
|
|
21
|
+
## How it works (no source changes)
|
|
22
|
+
|
|
23
|
+
Upstream routes every authentication decision through the live
|
|
24
|
+
`ctx.connection` service instance's methods:
|
|
25
|
+
|
|
26
|
+
| method | gate |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `requestRejection` | `/api` surface (RPC, streams, gateway) — 401/403 |
|
|
29
|
+
| `authorizeIndex` | frontend index request — token/cookie exchange |
|
|
30
|
+
| `authenticatedUrl` | printed startup URL — appends the launch token |
|
|
31
|
+
|
|
32
|
+
This plugin, in its own `apply`, first checks the live webServer bind host and
|
|
33
|
+
**refuses to start** unless it is a loopback literal, then replaces those three
|
|
34
|
+
**instance methods** at runtime:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
connection.requestRejection = () => undefined // every /api request proceeds
|
|
38
|
+
connection.authorizeIndex = () => true // index serves immediately
|
|
39
|
+
connection.authenticatedUrl = (url) => url // printed URL stays clean
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Every consumer (the `/api` route, the frontend-static fallback seat, the API
|
|
43
|
+
gateway, the web-app URL announcement) calls them by instance reference, so the
|
|
44
|
+
whole local surface becomes token- and cookie-free at once. On unload
|
|
45
|
+
(`dispose`) the original methods are restored.
|
|
46
|
+
|
|
47
|
+
## Safety boundary
|
|
48
|
+
|
|
49
|
+
- **Bind-host gate:** `apply` reads the live `webServer` bind host and throws
|
|
50
|
+
(fails loud, no timeout fallback) unless it is `127.0.0.1` or `localhost`.
|
|
51
|
+
The upstream webserver Config schema only accepts `'127.0.0.1' | '0.0.0.0'`,
|
|
52
|
+
so `0.0.0.0` (all interfaces) always fails and the bypass never silently
|
|
53
|
+
activates against a reachable-by-others listen.
|
|
54
|
+
- `dsh web` binds loopback and the CLI **rejects `--host 0.0.0.0`**, so this
|
|
55
|
+
bypass cannot, by itself, expose the server to other hosts.
|
|
56
|
+
- Never combine with anything that makes the reach accessible beyond the local
|
|
57
|
+
machine (SSH forwarders on shared boxes, VLAN loopback, NAT hairpin).
|
|
58
|
+
- Authorization of *remote* requests is unchanged only because there are none
|
|
59
|
+
on a loopback bind; if upstream ever allows non-loopback binds, this plugin
|
|
60
|
+
refuses to start rather than silently bypassing.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
# local development install (directory path is accepted):
|
|
66
|
+
pnpm dsh plugin --profile web add <absolute-path-to-this-directory>
|
|
67
|
+
|
|
68
|
+
# after publishing:
|
|
69
|
+
dsh plugin --profile web add github:falling-ts/dsh-local-no-auth
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`dsh web` has no `--patch` CLI overlay; the plugin activates through the
|
|
73
|
+
profile's own `cordis.patch.yml` referencing this package's `dsh.bundle.patch`
|
|
74
|
+
layer. Restart `dsh web` afterwards.
|
|
75
|
+
|
|
76
|
+
## Verify
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
curl -i http://127.0.0.1:<port>/ # 200 + index.html, no token needed
|
|
80
|
+
curl -i -X POST http://127.0.0.1:<port>/api/session/list \
|
|
81
|
+
-H 'content-type: application/json' \
|
|
82
|
+
-d '{"type":"client-request","rpcId":"probe","method":"session/list","payload":{}}'
|
|
83
|
+
# a business envelope arrives (2xx, result.ok true/false) — never 401
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Uninstall
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
dsh plugin --profile web remove dsh-local-no-auth
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Known limitations
|
|
93
|
+
|
|
94
|
+
- The bypass is process-wide: every plugin in the same composition sees
|
|
95
|
+
unauthenticated `/api`. That is the point for a local dev box.
|
|
96
|
+
- Startup log line still says `dsh web: http://...` (clean URL) and, with
|
|
97
|
+
`--trusted-host` LAN candidates, prints the LAN URL clean as well.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Opt-in layer: mount the dsh-local-no-auth function plugin over the current
|
|
2
|
+
# composition without changing shipped defaults or any upstream source.
|
|
3
|
+
#
|
|
4
|
+
# pnpm dsh plugin --profile web add <this-directory> # local dev install
|
|
5
|
+
# dsh plugin --profile web add github:falling-ts/dsh-local-no-auth # once published
|
|
6
|
+
#
|
|
7
|
+
# The plugin bypasses browser-session authentication for the loopback-local dsh
|
|
8
|
+
# web instance by replacing the live `connection` instance methods
|
|
9
|
+
# (requestRejection / authorizeIndex / authenticatedUrl); unload restores them.
|
|
10
|
+
|
|
11
|
+
- insert:
|
|
12
|
+
- id: dsh-local-no-auth
|
|
13
|
+
name: '@falling-ts/dsh-local-no-auth'
|
package/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-local-no-auth — a DSH Cordis function plugin.
|
|
3
|
+
*
|
|
4
|
+
* Bypasses the dsh web browser-session authentication for the loopback-local
|
|
5
|
+
* instance WITHOUT touching any upstream source. It replaces the three
|
|
6
|
+
* authentication methods on the live `ctx.connection` service instance:
|
|
7
|
+
*
|
|
8
|
+
* - `requestRejection` — the `/api` surface's 401/403 gate (RPC, streams, gateway)
|
|
9
|
+
* - `authorizeIndex` — the frontend index request's token/cookie exchange
|
|
10
|
+
* - `authenticatedUrl` — the startup URL line (drops the launch `?token=...`)
|
|
11
|
+
*
|
|
12
|
+
* Every authentication decision in the running app goes through these instance
|
|
13
|
+
* methods by instance reference (the `/api` route, the frontend-static fallback
|
|
14
|
+
* seat, the API gateway, and the web-app URL announcement), so replacing them
|
|
15
|
+
* here makes the whole local surface token- and cookie-free:
|
|
16
|
+
*
|
|
17
|
+
* http://127.0.0.1:<port>/ serves index.html immediately, no login round-trip
|
|
18
|
+
* /api/* answers RPC without a signed browser cookie
|
|
19
|
+
* the printed URL stays clean (no launch token appended)
|
|
20
|
+
*
|
|
21
|
+
* Safety boundary: `apply` refuses to start unless the live webServer bind host
|
|
22
|
+
* is a loopback literal. The upstream webserver Config schema only accepts
|
|
23
|
+
* `'127.0.0.1' | '0.0.0.0'`, so in practice `127.0.0.1` passes and `0.0.0.0`
|
|
24
|
+
* (all interfaces) fails; `localhost` is kept as a loopback alias for future
|
|
25
|
+
* schema latitude. Any other value fails loud — the bypass never silently
|
|
26
|
+
* activates against a reachable-by-others listen. Never enable the plugin when
|
|
27
|
+
* the reach in question can be accessed by other hosts anyway (SSH forwarders,
|
|
28
|
+
* VLAN-reachable loopback, NAT looping). Unload restores the original methods,
|
|
29
|
+
* so a disposed plugin leaves the instance identical to an unpatched run.
|
|
30
|
+
*
|
|
31
|
+
* @module @falling-ts/dsh-local-no-auth
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Stable Cordis plugin name. */
|
|
35
|
+
export const name = 'dsh-local-no-auth'
|
|
36
|
+
|
|
37
|
+
/** Connection and webServer must be active before their members can be read. */
|
|
38
|
+
export const inject = ['connection', 'webServer']
|
|
39
|
+
|
|
40
|
+
/** The connection instance methods that own every auth decision in this app. */
|
|
41
|
+
const AUTH_METHODS = ['requestRejection', 'authorizeIndex', 'authenticatedUrl']
|
|
42
|
+
|
|
43
|
+
/** Bind hosts this bypass may operate under: loopback literals only. */
|
|
44
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost'])
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Replace the connection instance's authentication methods for this process,
|
|
48
|
+
* but only while the instance listens on a loopback-only bind host.
|
|
49
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx - plugin context with `connection` and `webServer` active.
|
|
50
|
+
*/
|
|
51
|
+
export function apply(ctx) {
|
|
52
|
+
const connection = ctx.connection
|
|
53
|
+
if (!connection || typeof connection !== 'object') {
|
|
54
|
+
throw new Error('dsh-local-no-auth: connection service unavailable')
|
|
55
|
+
}
|
|
56
|
+
const webServer = ctx.webServer
|
|
57
|
+
const bindHost = webServer && typeof webServer.host === 'string' ? webServer.host : undefined
|
|
58
|
+
if (bindHost === undefined) {
|
|
59
|
+
throw new Error('dsh-local-no-auth: webServer bind host unavailable; refusing to start')
|
|
60
|
+
}
|
|
61
|
+
if (!LOOPBACK_HOSTS.has(bindHost)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`dsh-local-no-auth: refusing to start — webServer bind host "${bindHost}" is not loopback-only `
|
|
64
|
+
+ `(allowed: ${[...LOOPBACK_HOSTS].join(', ')}). The no-auth bypass is only safe when `
|
|
65
|
+
+ `no other host can reach the instance.`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
const originals = {}
|
|
69
|
+
for (const method of AUTH_METHODS) {
|
|
70
|
+
if (typeof connection[method] !== 'function') {
|
|
71
|
+
throw new Error(`dsh-local-no-auth: connection.${method} is not a function; refusing to start`)
|
|
72
|
+
}
|
|
73
|
+
originals[method] = connection[method]
|
|
74
|
+
}
|
|
75
|
+
connection.requestRejection = () => undefined
|
|
76
|
+
connection.authorizeIndex = () => true
|
|
77
|
+
connection.authenticatedUrl = (baseUrl) => baseUrl
|
|
78
|
+
ctx.on('dispose', () => {
|
|
79
|
+
for (const method of AUTH_METHODS) connection[method] = originals[method]
|
|
80
|
+
})
|
|
81
|
+
console.log(`[dsh-local-no-auth] active: browser token/cookie checks bypassed; URLs printed clean (bind host "${bindHost}", loopback-only)`)
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@falling-ts/dsh-local-no-auth",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "DSH Cordis plugin: loopback-local no-auth for the dsh web GUI. Replaces the live ctx.connection instance's three authentication methods (requestRejection / authorizeIndex / authenticatedUrl) at runtime so the loopback-bound dsh web instance serves the frontend and /api without a launch token or browser cookie, and prints clean URLs. Refuses to start unless the live webServer bind host is loopback-only (127.0.0.1 / localhost). Touches no upstream source; dispose restores the original methods.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./package.json": "./package.json"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"cordis.patch.yml"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dsh": {
|
|
23
|
+
"bundle": {
|
|
24
|
+
"patch": "./cordis.patch.yml"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|