@michengai/dsh-skills-manager 0.1.28 → 0.1.30
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/CHANGELOG.md +14 -16
- package/CHANGELOG.zh-CN.md +14 -16
- package/README.md +4 -7
- package/README.zh-CN.md +4 -7
- package/lib/index.js +75 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
The five most recent published versions are listed below.
|
|
6
6
|
|
|
7
|
+
## 0.1.30 — 2026-08-28
|
|
8
|
+
|
|
9
|
+
- Fixed reverse-proxy domains and LAN IPs already trusted by DSH Web through `--trusted-host` still being rejected by Skills Manager's own loopback-only Host fence.
|
|
10
|
+
- Reused `webRuntime.trustedHosts` with DSH-compatible authority semantics: port-less entries match any port, explicit ports match exactly, and unknown Hosts, non-canonical authorities, foreign Origins, and explicit cross-site requests remain blocked.
|
|
11
|
+
- Added isolated real-DSH startup verification and regression coverage for domains, LAN IPs, ports, Origins, cross-site requests, and mutation markers.
|
|
12
|
+
|
|
13
|
+
Published package: [`@michengai/dsh-skills-manager@0.1.30`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.30).
|
|
14
|
+
|
|
15
|
+
## 0.1.29 — 2026-08-28
|
|
16
|
+
|
|
17
|
+
- Restored the standard DSH README header navigation: the Changelog link now sits between the language switch and the Apache-2.0 license link.
|
|
18
|
+
|
|
19
|
+
Published package: [`@michengai/dsh-skills-manager@0.1.29`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.29).
|
|
20
|
+
|
|
7
21
|
## 0.1.28 — 2026-08-28
|
|
8
22
|
|
|
9
23
|
- Moved the release-history entry point to the top of the README so package updates are immediately discoverable.
|
|
@@ -25,19 +39,3 @@ Published package: [`@michengai/dsh-skills-manager@0.1.27`](https://www.npmjs.co
|
|
|
25
39
|
- Replaced the large-string Base64 validation regex with stack-safe validation to prevent multi-MiB files from exhausting the JavaScript call stack.
|
|
26
40
|
|
|
27
41
|
Published package: [`@michengai/dsh-skills-manager@0.1.26`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.26).
|
|
28
|
-
|
|
29
|
-
## 0.1.25 — 2026-08-27
|
|
30
|
-
|
|
31
|
-
- Added provider-backed loading for public Agents, Codex, Claude, Gemini, and OpenCode while keeping external source files read-only and persisting toggles in manager state.
|
|
32
|
-
- Added Markdown details, diagnostics, conversational creation, recoverable Trash, and second-stage permanent deletion.
|
|
33
|
-
- Replaced chained importing with one upload dialog for `.zip`, skill folders, and a single `SKILL.md`, with path, size, count, and archive safety limits.
|
|
34
|
-
- Fixed corrupted-state overwrite, trash rollback data loss, Windows `EPERM` publication failures, and the missing Repair-and-enable payload field.
|
|
35
|
-
|
|
36
|
-
Published package: [`@michengai/dsh-skills-manager@0.1.25`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.25).
|
|
37
|
-
|
|
38
|
-
## 0.1.24 — 2026-08-23
|
|
39
|
-
|
|
40
|
-
- Added bilingual changelogs covering the five most recent releases.
|
|
41
|
-
- Linked the release history from both README editions and included it in the npm package.
|
|
42
|
-
|
|
43
|
-
Published package: [`@michengai/dsh-skills-manager@0.1.24`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.24).
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
以下记录最近发布的五个版本。
|
|
6
6
|
|
|
7
|
+
## 0.1.30 — 2026-08-28
|
|
8
|
+
|
|
9
|
+
- 修复 DSH Web 已通过 `--trusted-host` 信任的反向代理域名或局域网 IP,仍被 Skills Manager 自身 loopback-only Host 校验拒绝的问题。
|
|
10
|
+
- 复用 `webRuntime.trustedHosts`,按 DSH 的 authority 语义支持无端口任意端口匹配和带端口精确匹配,同时继续拒绝未知 Host、非规范 authority、异源 Origin 与显式 cross-site 请求。
|
|
11
|
+
- 补充真实 DSH 隔离启动验证以及域名、LAN IP、端口、Origin、跨站请求和写接口标记的回归测试。
|
|
12
|
+
|
|
13
|
+
发布包:[`@michengai/dsh-skills-manager@0.1.30`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.30)。
|
|
14
|
+
|
|
15
|
+
## 0.1.29 — 2026-08-28
|
|
16
|
+
|
|
17
|
+
- 恢复 DSH 系列 README 的标准头部导航:更新日志入口位于语言切换与 Apache-2.0 许可证链接之间。
|
|
18
|
+
|
|
19
|
+
发布包:[`@michengai/dsh-skills-manager@0.1.29`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.29)。
|
|
20
|
+
|
|
7
21
|
## 0.1.28 — 2026-08-28
|
|
8
22
|
|
|
9
23
|
- 将 README 中的更新日志入口前移到页面顶部,打开项目即可看到发布历史。
|
|
@@ -25,19 +39,3 @@
|
|
|
25
39
|
- 将大字符串 Base64 正则校验改为栈安全校验,避免数 MiB 文件耗尽 JavaScript 调用栈。
|
|
26
40
|
|
|
27
41
|
发布包:[`@michengai/dsh-skills-manager@0.1.26`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.26)。
|
|
28
|
-
|
|
29
|
-
## 0.1.25 — 2026-08-27
|
|
30
|
-
|
|
31
|
-
- 新增公共 Agent、Codex、Claude、Gemini 与 OpenCode 技能源加载;启停策略仅写入管理器状态,不修改外部源文件。
|
|
32
|
-
- 新增正文详情、格式诊断、对话创建、可恢复回收站及二次永久删除。
|
|
33
|
-
- 导入改为单弹窗流程,统一支持 `.zip`、技能文件夹和单个 `SKILL.md`,并补充路径、大小、数量与归档安全边界。
|
|
34
|
-
- 修复损坏状态文件覆盖、回收站回滚丢失、Windows `EPERM` 发布失败和「修复并启用」字段缺失等问题。
|
|
35
|
-
|
|
36
|
-
发布包:[`@michengai/dsh-skills-manager@0.1.25`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.25)。
|
|
37
|
-
|
|
38
|
-
## 0.1.24 — 2026-08-23
|
|
39
|
-
|
|
40
|
-
- 新增中英文更新日志,展示最近五个发布版本。
|
|
41
|
-
- 在中英文 README 中加入更新日志入口,并将日志纳入 npm 包。
|
|
42
|
-
|
|
43
|
-
发布包:[`@michengai/dsh-skills-manager@0.1.24`](https://www.npmjs.com/package/@michengai/dsh-skills-manager/v/0.1.24)。
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**Load and safely manage skills from DSH and common local Agents**
|
|
10
10
|
|
|
11
|
-
[简体中文](README.zh-CN.md) · [Apache-2.0](LICENSE)
|
|
11
|
+
[简体中文](README.zh-CN.md) · [Changelog](CHANGELOG.md) · [Apache-2.0](LICENSE)
|
|
12
12
|
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://www.npmjs.com/package/@michengai/dsh-skills-manager)
|
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
|
|
20
20
|
> DSH Skills Manager is a community-maintained DeepSeek Harness (DSH) plugin, not an official DeepSeek AI product.
|
|
21
21
|
|
|
22
|
-
## Changelog
|
|
23
|
-
|
|
24
|
-
See [CHANGELOG.md](CHANGELOG.md) for the five most recent releases.
|
|
25
22
|
|
|
26
23
|
## Features
|
|
27
24
|
|
|
@@ -159,9 +156,9 @@ Escape closes only the frontmost upload or confirmation dialog and leaves Settin
|
|
|
159
156
|
|
|
160
157
|
- Enable, disable, and delete accept only one ordinary skill-name path segment.
|
|
161
158
|
- Replacements copy to a temporary sibling path first and keep the original until that succeeds.
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
- Import accepts the local path the user
|
|
159
|
+
- Every endpoint, including GET `/state`, accepts only a loopback `Host` or a canonical `host[:port]` that the DSH Web runtime already trusts through its LAN bind and `--trusted-host`; unknown hosts still receive 403.
|
|
160
|
+
- Browser requests must also carry a same-origin `Origin` when present and must not be marked cross-site; write endpoints continue to require JSON and the DSH client request marker.
|
|
161
|
+
- Import accepts the local path selected by the user. The Host trust fence prevents DNS rebinding but is not authentication; reverse-proxy and LAN deployments still need authentication, a VPN, or network access controls.
|
|
165
162
|
|
|
166
163
|
## Secondary development
|
|
167
164
|
|
package/README.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**在 DeepSeek Harness 中统一加载并安全管理本机 Agent Skills**
|
|
10
10
|
|
|
11
|
-
[English](README.md) · [Apache-2.0](LICENSE)
|
|
11
|
+
[English](README.md) · [更新日志](CHANGELOG.zh-CN.md) · [Apache-2.0](LICENSE)
|
|
12
12
|
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://www.npmjs.com/package/@michengai/dsh-skills-manager)
|
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
|
|
20
20
|
> DSH Skills Manager 是社区维护的 DeepSeek Harness(DSH)插件,并非 DeepSeek AI 官方产品。
|
|
21
21
|
|
|
22
|
-
## 更新日志
|
|
23
|
-
|
|
24
|
-
最近五个发布版本见 [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md)。
|
|
25
22
|
|
|
26
23
|
## 功能概览
|
|
27
24
|
|
|
@@ -159,9 +156,9 @@ dsh --profile web --dump-config
|
|
|
159
156
|
|
|
160
157
|
- 启用、停用和删除只接受单个普通技能名称,目录穿越名称会被拒绝。
|
|
161
158
|
- 覆盖前先复制到同目录临时路径;复制成功前不会改动现有技能。
|
|
162
|
-
- 全部接口(含 GET `/state`)只接受 loopback `Host
|
|
163
|
-
-
|
|
164
|
-
- 导入接受用户选定的本机路径。
|
|
159
|
+
- 全部接口(含 GET `/state`)只接受 loopback `Host`,或 DSH Web runtime 已通过 LAN 绑定和 `--trusted-host` 明确信任的 `host[:port]`;未知 Host 继续返回 403。
|
|
160
|
+
- 浏览器请求还必须满足同源 `Origin` 且不能标记为 cross-site;写入接口继续要求 JSON 与 DSH 客户端请求标记。
|
|
161
|
+
- 导入接受用户选定的本机路径。Host 信任栅栏用于防 DNS rebinding,不是身份认证;通过反向代理或局域网提供服务时,仍应配置认证、VPN 或网络访问控制。
|
|
165
162
|
|
|
166
163
|
## 二次开发
|
|
167
164
|
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// dsh-skills-manager host half:设置面板的 HTTP 后端(webServer prefix 路由)
|
|
2
2
|
// - host 路由仅依赖 node: 内置与本地 core.js;ZIP 解压封装在 core
|
|
3
3
|
// - 路由:状态、启停、详情、创建、导入、回收站
|
|
4
|
-
// - 全部接口先校验
|
|
4
|
+
// - 全部接口先校验 DSH Web 信任的 Host;写接口再校验请求标记与 JSON
|
|
5
5
|
// - $DSH_HOME\skills 可创建、导入、回收、启停;外部 Agent 根只读,由 manager 状态控制加载
|
|
6
6
|
|
|
7
7
|
import { appendFile, rename, rm, stat } from "node:fs/promises";
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "./core.js";
|
|
25
25
|
|
|
26
26
|
const name = "skills-manager";
|
|
27
|
-
const inject = ["webServer", "skills", "tools"];
|
|
27
|
+
const inject = ["webServer", "webRuntime", "skills", "tools"];
|
|
28
28
|
const CLIENT_MARKER_HEADER = "x-dsh-skills-manager";
|
|
29
29
|
const MAX_LOG_BYTES = 1 << 20;
|
|
30
30
|
// 文件夹上传允许 64 MiB 原始内容;Base64 会膨胀约 1/3,再为最多 1000 条路径预留余量。
|
|
@@ -91,16 +91,80 @@ function readBody(req, limit = 1 << 20) {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/**
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
/** 将裸 `host[:port]` authority 解析为 URL;非法形态返回 undefined。 */
|
|
95
|
+
function parseAuthority(authority) {
|
|
96
|
+
try {
|
|
97
|
+
return new URL(`http://${authority}`);
|
|
98
|
+
} catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 返回 authority 的规范形态。用 http/https 双重解析保留显式的 :80 / :443,
|
|
105
|
+
* 避免默认端口被 WHATWG URL 自动剥离后意外扩大为任意端口授权。
|
|
106
|
+
*/
|
|
107
|
+
function canonicalAuthority(authority, parsed) {
|
|
108
|
+
const port = parsed.port !== "" ? parsed.port : new URL(`https://${authority}`).port;
|
|
109
|
+
return port === "" ? parsed.hostname : `${parsed.hostname}:${port}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 只接受纯净、规范的 host[:port],拒绝路径、userinfo、空白和非规范端口。 */
|
|
113
|
+
function isCanonicalAuthority(authority, parsed) {
|
|
114
|
+
return canonicalAuthority(authority, parsed) === authority.toLowerCase();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** 与 DSH `/api` 信任栅栏一致:localhost、IPv6 loopback 或 IPv4 127/8。 */
|
|
118
|
+
function isLoopbackHostname(hostname) {
|
|
119
|
+
if (hostname === "localhost" || hostname === "[::1]") return true;
|
|
120
|
+
const parts = hostname.split(".");
|
|
121
|
+
return parts.length === 4 && parts[0] === "127" && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 带端口的 trustedHosts 条目精确匹配;不带端口的条目匹配同主机的任意端口。 */
|
|
125
|
+
function isTrustedAuthority(hostUrl, trustedHosts) {
|
|
126
|
+
return trustedHosts.some((entry) => {
|
|
127
|
+
if (typeof entry !== "string") return false;
|
|
128
|
+
const entryUrl = parseAuthority(entry);
|
|
129
|
+
if (!entryUrl || !isCanonicalAuthority(entry, entryUrl)) return false;
|
|
130
|
+
return canonicalAuthority(entry, entryUrl) === entryUrl.hostname
|
|
131
|
+
? entryUrl.hostname === hostUrl.hostname
|
|
132
|
+
: entryUrl.host === hostUrl.host;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 复用 DSH Web runtime 的 trustedHosts(LAN IP + `--trusted-host`),同时
|
|
138
|
+
* 保留 loopback 默认值;未知 Host、跨站 Fetch 或异源 Origin 继续拒绝。
|
|
139
|
+
*/
|
|
140
|
+
function validateRequestOrigin(req, trustedHosts = []) {
|
|
141
|
+
const host = typeof req.headers.host === "string" ? req.headers.host : "";
|
|
142
|
+
const hostUrl = parseAuthority(host);
|
|
143
|
+
if (!hostUrl || !isCanonicalAuthority(host, hostUrl)) {
|
|
144
|
+
return { statusCode: 403, code: "error.proto.forbiddenHost", error: "forbidden host" };
|
|
145
|
+
}
|
|
146
|
+
if (!isLoopbackHostname(hostUrl.hostname) && !isTrustedAuthority(hostUrl, trustedHosts)) {
|
|
147
|
+
return { statusCode: 403, code: "error.proto.forbiddenHost", error: "forbidden host" };
|
|
148
|
+
}
|
|
149
|
+
if (req.headers["sec-fetch-site"] === "cross-site") {
|
|
150
|
+
return { statusCode: 403, code: "error.proto.forbiddenHost", error: "forbidden host" };
|
|
151
|
+
}
|
|
152
|
+
const origin = req.headers.origin;
|
|
153
|
+
if (typeof origin === "string") {
|
|
154
|
+
try {
|
|
155
|
+
if (new URL(origin).host !== hostUrl.host) {
|
|
156
|
+
return { statusCode: 403, code: "error.proto.forbiddenHost", error: "forbidden host" };
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
return { statusCode: 403, code: "error.proto.forbiddenHost", error: "forbidden host" };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
98
162
|
return null;
|
|
99
163
|
}
|
|
100
164
|
|
|
101
165
|
/** 写接口额外要求自定义头与 JSON;自定义头迫使跨站 fetch 预检,且本接口不回 CORS。 */
|
|
102
|
-
function validateMutationRequest(req) {
|
|
103
|
-
const hostError =
|
|
166
|
+
function validateMutationRequest(req, trustedHosts) {
|
|
167
|
+
const hostError = validateRequestOrigin(req, trustedHosts);
|
|
104
168
|
if (hostError) return hostError;
|
|
105
169
|
if (req.headers[CLIENT_MARKER_HEADER] !== "1") return { statusCode: 403, code: "error.proto.forbidden", error: "forbidden mutation request" };
|
|
106
170
|
const contentType = String(req.headers["content-type"] || "").split(";", 1)[0].trim().toLowerCase();
|
|
@@ -239,6 +303,7 @@ function registerAgentSkillProviders(ctx, invalidators) {
|
|
|
239
303
|
|
|
240
304
|
function apply(ctx) {
|
|
241
305
|
const log = makeLog();
|
|
306
|
+
const trustedHosts = Array.isArray(ctx.webRuntime.trustedHosts) ? ctx.webRuntime.trustedHosts : [];
|
|
242
307
|
const roots = userRoots();
|
|
243
308
|
const rootByKey = Object.fromEntries(roots.map((r) => [r.key, r]));
|
|
244
309
|
const providerInvalidators = new Set();
|
|
@@ -297,7 +362,7 @@ function apply(ctx) {
|
|
|
297
362
|
const u = new URL(req.url, "http://localhost");
|
|
298
363
|
const path = u.pathname.replace(/\/+$/, "");
|
|
299
364
|
try {
|
|
300
|
-
const hostError =
|
|
365
|
+
const hostError = validateRequestOrigin(req, trustedHosts);
|
|
301
366
|
if (hostError) {
|
|
302
367
|
json(res, hostError.statusCode, { ok: false, code: hostError.code, error: hostError.error });
|
|
303
368
|
return;
|
|
@@ -315,7 +380,7 @@ function apply(ctx) {
|
|
|
315
380
|
json(res, 405, { ok: false, code: "error.proto.method", error: `method not allowed: ${req.method}` });
|
|
316
381
|
return;
|
|
317
382
|
}
|
|
318
|
-
const requestError = validateMutationRequest(req);
|
|
383
|
+
const requestError = validateMutationRequest(req, trustedHosts);
|
|
319
384
|
if (requestError) {
|
|
320
385
|
json(res, requestError.statusCode, { ok: false, code: requestError.code, error: requestError.error });
|
|
321
386
|
return;
|
package/package.json
CHANGED