DSH 凭证加密 Fabric 插件:不替换
dsh-credentials-local,只通过 Fabric hook 增加密文 sidecar、解锁和输出脱敏能力。
Fabric 版本不再禁用或复制官方 credentials-local row。官方 provider 继续负责 .credentials.yaml 的 YAML 解析、环境变量优先级、watcher、文件锁、明文写入和 credentials/updated 生命周期;本插件只挂载 EncryptController 并注册目标函数的 hook。
加密态使用旁路文件,保证 Fabric 关闭时普通 dsh 仍能启动:
.credentials.yaml # 官方 provider 看到的 comment-only marker
.credentials.encrypt.yaml # AES-GCM / Argon2id 密文 sidecar
.dsh-encrypt.json # remember 和解锁锁定状态
代码边界:
lib/vault.js、lib/lockout.js、lib/leak-guard.js:不依赖 Fabric 的安全核心。lib/plain.js、lib/fabric-controller.js:sidecar、状态、迁移和密码操作,不注册宿主 hook。lib/fabric-handlers.js、lib/fabric-entry.js:唯一的 Fabric 适配层;只 hookresolve、describe、set、unset以及 Web route 注册。lib/web.js、lib/client.js:独立 Web/API 和浏览器面,headless 组合不会注册 HTTP 路由。
普通 dsh 跳过 dsh-encrypt-fabric row;fabric-dsh 才启用该 Fabric-required row。旧版“密文直接写在 .credentials.yaml”的文件不能在 provider 已启动后自动迁移,必须先执行迁移工具或手动移动到 sidecar,插件会 fail-closed 而不是把它当成空凭证库。
| 项目 | 值 |
|---|---|
| 形态 | bundle(dsh.bundle.patch → cordis.patch.yml,随 profile 启动,dsh plugin add 原生安装) |
| 版本 | 0.1.0-rc.10 |
| 依赖线 | npm rc.6(@deepseek-ai/cordis@^4.0.1 等 scoped 包 + @node-rs/argon2 原生绑定 + Fabric peer) |
| 环境 | Node.js ≥ 18(官方教程建议 22+);DSH @deepseek-ai/dsh@0.0.1-rc.1+ |
| License | MIT |
DSH 默认的凭证存储把密钥以明文 YAML 写在 $DSH_HOME/.credentials.yaml。dsh-encrypt Fabric 版本保持这条官方 provider 不变;设置密码后,官方文件只保留 marker,凭证按需写入并从 .credentials.encrypt.yaml 解密,Fabric 关闭时不会读取 sidecar。
- 旁路双文件:明文模式由官方
.credentials.yaml负责;加密模式使用 marker +.credentials.encrypt.yaml,Fabric 关闭时不会把密文误解析为普通凭证 - WebUI 全生命周期:设置密码 / 解锁 / 修改密码,全部在「设置 → 加密安全」完成(永远密文策略下没有「移除密码」)
- AES-256-GCM:每条凭证独立随机 nonce,凭证引用名绑定为 GCM AAD(换位即认证失败)
- SHA3-256 双重完整性:条目级指纹 + 覆盖文档头部的文档级指纹,损坏文件在启动时即被拒绝(绝不当作“空库”)
- Argon2id 密码派生(
@node-rs/argon2,m=64 MiB, t=3, p=1,OWASP 对齐):密码不落盘,仅存盐与 AEAD 验证器;旧版 scrypt(v2)密文仍可解锁并在解锁时自动升级到 Argon2id - 摘要校验(前后端分离):WebUI 用纯 JS Keccak-f[1600] 计算密码的 SHA3-256,只 POST
{ digest }到后端;后端用 Argon2id 拉伸摘要并与 AEAD 验证器匹配,原始密码不离开浏览器 - 解锁爆破锁定:连续密码解锁失败计数持久化到状态文件(重启不清零),达到阈值(默认 5 次)后指数退避锁定(30 s 起,2 倍递增,上限 15 min),WebUI 显示剩余锁定时间,HTTP 返回 429 +
Retry-After - 凭证泄露检测与输出脱敏(Leak Guard):模型实际解析过的凭证值会被登记为掩码模式——WebUI 的 HTTP 响应体与 WebSocket 事件帧在离开主机前扫描并替换为
[REDACTED:dsh-encrypt],提示词注入诱导模型回显密钥时也会被脱敏 - 发行代码完整性自校验:打包时生成
lib/integrity-manifest.json(所有发行文件的 SHA3-256),启动时逐文件校验、不一致即拒绝加载(fail-closed),覆盖 Fabric 入口、sidecar 控制器、Web 和浏览器面板包;哈希对行尾/BOM 归一化,git 克隆(CRLF↔LF)与 tarball 安装校验一致,真实代码篡改仍会被拦截 - 免密登录滑块:设置「多少天免密」(0 = 每次都输密码 / 1–30 天 / 永远,刻度仅保留头尾),解锁成功后签发 256 位票据(HttpOnly Cookie + 响应体回传,浏览器同时存 localStorage 并随
x-dsh-encrypt-remember请求头回传,不再依赖 Cookie 存取可靠性);打开 WebUI 即自动尝试免密解锁(localhost 专属),磁盘只存被 AEAD 包裹的密钥副本;到期或票据不匹配即失效 - 仅 localhost 免密与改密:非本机访问强制每次都输密码,且不能设置/修改密码(后端强制,返回
LOCAL_ONLY) - 永远密文(ciphertext-only):一旦设置密码,官方文件只保留 marker;外部把 marker 替换成明文时,控制器拒绝采用并保留最后可信 sidecar 快照,直到恢复 marker。
- 阅后即焚:密文只在被使用的那一刻解密;解密中间 Buffer 立即清零,新增
withUnlockedBuffer擦除式接口(回调结束后在 finally 中焚毁明文副本),改密时的明文集合用后即清空,密钥在锁定/卸载时清零 - 按请求解密:明文只存活于单次操作,不缓存、不进日志;密钥在锁定/卸载时清零
- 热重载:外部编辑明文即时生效;外部加密即时转锁定;损坏的中间状态保留最后一个好快照
- 原子写 + 文件锁:写入经
dsh-atomic-write,POSIX 上强制0600权限(启动即校验) - 自动化解锁:
DSH_CREDENTIAL_PASSWORD环境变量在启动时解锁(适合 headless)
本插件是 Fabric bundle。它不禁用官方 credentials row,也不提供第二个 CredentialProvider;dsh-encrypt-fabric 只在 fabric-dsh 中启用,并在当前 plugin fiber 注册六个可信 handler。
| 层 | 文件 | 职责 |
|---|---|---|
| 安全核心 | lib/vault.js、lib/lockout.js、lib/leak-guard.js |
加解密、KDF、完整性、锁定和输出扫描;无 Fabric 注册 |
| sidecar 控制器 | lib/plain.js、lib/fabric-controller.js |
marker、密文文件、状态文件、密码生命周期和 watcher |
| Fabric hook | lib/fabric-entry.js、lib/fabric-handlers.js |
只 hook 官方 resolve / describe / set / unset 与 Web route 注册 |
| Web/client | lib/web.js、lib/client.js |
密码 API、响应脱敏和「加密安全」设置面板 |
四个 credential hook 是 required: true;两个 Web route hook 是可选的,headless profile 没有 WebServer 时不会因为脱敏 hook 绑定为空而失败。Hook 没有 handler 时官方函数照常运行,普通 dsh 则直接跳过整个 Fabric-required row。
官方 provider 继续拥有 .credentials.yaml 的解析、环境优先级、热更新、文件锁、明文写入和 credentials/updated 生命周期。加密态只新增旁路 .credentials.encrypt.yaml,官方文件变成 comment-only marker;因此 Fabric 关闭时不会把密文 JSON 当作普通凭证文档。
先打包,再装进 profile(dsh plugin add 会把声明了 dsh.bundle.patch 的依赖自动 reconcile 进 bundles 列表):
# 打包(files 字段只含显式列出的 Fabric/core/Web 文件与 cordis.patch.yml,test/ 不入包;
# prepack 会自动重新生成 lib/integrity-manifest.json)
npm pack
# → dsh-encrypt-0.1.0-rc.10.tgz
# web profile(设置页「加密安全」)
dsh plugin --profile web add ./dsh-encrypt-0.1.0-rc.10.tgz
# headless profile(一次性任务 / 自动化解锁;web 与 headless 是不同 profile,需分别安装)
dsh plugin --profile headless add ./dsh-encrypt-0.1.0-rc.10.tgz@node-rs/argon2 是带预编译二进制(napi-rs)的原生依赖,dsh plugin add 安装依赖时会拉取对应平台的 optionalDependencies;Node.js ≥ 18(建议 22+)。若安装后出现原生模块缺失(极少数平台组合),重新执行一次 npm rebuild @node-rs/argon2 即可。
本地源码目录同样可以直接安装(路径用 Windows 正斜杠形式):
dsh plugin --profile web add "D:/path/to/DSH-Encrypt"Fabric 入口通过可选的 webServer/httpServer 注入自动注册密码路由;headless 组合不会挂载 Web API,不需要再插入 dsh-encrypt-web row。dsh-encrypt/client 仍由 package.json 的 dsh.client 声明挂载设置面板。
dsh --profile web --dump-config | grep dsh-encrypt
# 期望:出现 disabled 的 dsh-encrypt-fabric;通过 fabric-dsh 启动时由 launcher 自动启用必须通过 Fabric launcher 启动:
fabric-dsh web普通 dsh web 会跳过 Fabric-required row,不读取加密 sidecar。headless 使用环境变量自动解锁:
DSH_CREDENTIAL_PASSWORD='<密码>' fabric-dsh headless旧版直接把密文写入 .credentials.yaml 的文件不能在 provider 已启动后自动迁移。启动 Fabric 前执行:
dsh-encrypt-migrate --path "$DSH_HOME/.credentials.yaml"该命令只移动并验证密文,不需要密码;目标 sidecar 已存在时拒绝覆盖。
全部操作在「设置 → 加密安全」完成(面板 id encryption):
| 操作 | 前置状态 | 效果 |
|---|---|---|
| 设置加密密码(输入两次,至少 8 个字符) | 明文 | .credentials.yaml 写入 marker,明文凭证加密到 .credentials.encrypt.yaml,进程保持解锁 |
| 解锁 | 加密+锁定(重启后) | 校验密码摘要并派生密钥,立即恢复模型调用 |
| 修改密码 | 加密+解锁 | 全部条目在新密钥下重加密 |
| 免密登录时长(滑块) | 任意 | 0 = 每次都输密码;1–30 = N 天内免密;31 = 永远免密(均仅本机) |
滑块即改即存(松开即提交到 /api/credentials.config,只接受 localhost;刻度只显示「每次」与「永远」两头)。解锁成功后按当前滑块时长签发免密票据;改动滑块会作废旧票据,若当前已解锁则立即按新时长重新签发。打开 WebUI 时自动尝试免密解锁(localhost 专属),免密期间无需输密码;到期后回到密码解锁。非 localhost 访问始终需要输入密码,且不显示改密入口(后端同样强制)。
永远密文:设置密码后 .credentials.yaml 只保留 marker。若凭证文件被人替换成明文,控制器拒绝启动/刷新并保留 sidecar;恢复 marker 后即可继续使用。
解锁锁定:连续输错密码达到阈值(默认 5 次)后,解锁接口进入指数退避锁定——第 1 次锁定 30 秒,之后每次失败翻倍,上限 15 分钟;计数与截止时间持久化在状态文件里,重启进程不能清零。锁定期间面板显示剩余秒数并禁用解锁按钮,接口返回 429 + Retry-After;成功解锁即清零。
状态机:
set-password (restart) unlock
plain ──────────────────► encrypted+unlocked ──────► encrypted+locked ──► unlocked
▲ │ ▲ │
└────── marker-restore ──────┘ └──────────── change-password ───────────┘
- 锁定期间:web 服务照常运行;继承环境中的凭证仍可解析,文件内凭证解析抛
VAULT_LOCKED,describe报告source: "locked"——设置页即是解锁入口 - 免密窗口:票据在签发时写入
issuedAt与days;days: -1永不失效。滑块改动会立即作废旧票据 - 忘记密码:设计上不可恢复(密码不落盘);恢复手段 = 备份并删除 marker、sidecar 和状态文件后重新配置
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 | 继承环境(launching environment) | 只读、高于受管文件;对被其遮蔽的引用写入会被拒绝 |
| 2 | 受管文件 / 加密 sidecar | 明文模式读取 .credentials.yaml;加密模式由 Fabric 从 .credentials.encrypt.yaml 按需解密 |
| 3 | .env 回退(project-env / user-env) |
低于受管文件,仅在文件无此引用时兜底 |
allowEnvFallback: false 可关闭第 1、3 层(严格仅文件策略)。
明文形态(未设密码,与 dsh-credentials-local 完全一致):
OPENCODE_GO_API_KEY: sk-…加密形态由两个文件组成:
.credentials.yaml
# dsh-encrypt: encrypted sidecar
# Credential values are stored in .credentials.encrypt.yaml.
.credentials.encrypt.yaml 的内容是 JSON 密文文档:
{
"format": "dsh-encrypt-credentials",
"version": 3,
"algorithm": "aes-256-gcm+sha3-256",
"kdf": "argon2id",
"kdfInput": "sha3-256-password",
"m": 65536, "t": 3, "p": 1,
"salt": "<base64url>",
"verifier": { "data": "<base64url>", "sha3": "<hex>" },
"entries": {
"OPENCODE_GO_API_KEY": { "data": "<base64url>", "sha3": "<hex>" }
},
"sha3": "<document fingerprint>"
}kdf: "argon2id":主密钥 = Argon2id(密码的 SHA3-256 摘要, salt, m/t/p),原始密码不进后端。- v2
scrypt文档仍可解析与解锁,密码解锁后升级为 v3;旧版单文件位置必须先迁移到 sidecar。 verifier、条目级指纹、文档级指纹和 GCM AAD 继续由lib/vault.js校验。- 密码与票据从不落盘;
.credentials.yamlmarker 本身不含秘密。
仅 POST application/json(与官方 /api 相同的跨站写护栏);响应 { ok: true, value } 或 { ok: false, code, message },错误消息不含密码或任何密钥材料:
| 路径 | 请求体 | 作用 |
|---|---|---|
/api/credentials.status |
{} |
返回 { format, unlocked, plaintextForbidden, local, remember, ticketRejected, lockout, leakGuard };localhost 且携带免密票据(x-dsh-encrypt-remember 请求头或 Cookie)时先尝试票据解锁 |
/api/credentials.unlock |
{ digest } |
密码摘要解锁(64 位小写 hex SHA3-256);localhost 成功后再按滑块签发免密票据(票据随响应体 ticket 字段回传);失败次数过多时返回 429 + Retry-After(TOO_MANY_ATTEMPTS) |
/api/credentials.set-password |
{ digest } |
明文 → marker + 密文 sidecar(仅 localhost) |
/api/credentials.change-password |
{ digest } |
重加密(需已解锁,仅 localhost,作废旧票据并签发新票据) |
/api/credentials.config |
{ action: "get" } / { action: "set", rememberDays } |
读取状态或设置免密天数(-1 = 永远,0 = 每次,1–30;set 仅 localhost,签发新票据时随响应回传) |
所有路由只接受 POST application/json。密码本身永不出现在任何请求体里——浏览器只提交 digest(可用 node -p "require('crypto').createHash('sha3-256').update('密码').digest('hex')" 计算)。
headless 组合不挂载 web 行,因此没有 HTTP 面。
config:
path: '' # 凭证文件绝对路径;未设置时取 $DSH_HOME/.credentials.yaml
encryptedPath: '' # 加密 sidecar;默认与 credentials 文件同目录
statePath: '' # 状态文件;默认 $DSH_HOME/.dsh-encrypt.json
allowEnvFallback: true # 允许继承环境与 .env 回退层
passwordEnv: DSH_CREDENTIAL_PASSWORD # 启动自动解锁的环境变量名
watch: true # 监听文件变化热重载
debounceMs: 100 # 热重载防抖(毫秒)
leakGuard: true # 凭证泄露检测与输出脱敏
leakMinMaskLength: 8 # 短于该长度的值不脱敏(避免误伤普通文本)
leakMaxMaskLength: 256 # 长于该长度的值不脱敏(流式回看窗口)
maxUnlockAttempts: 5 # 连续解锁失败触发锁定的阈值
lockoutBaseMs: 30000 # 首次锁定窗口(毫秒,之后指数翻倍)
lockoutMaxMs: 900000 # 锁定窗口上限(毫秒)
trustedHosts: [] # 非回环部署允许的 Host authority(host 或 host:port)| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
path |
string | $DSH_HOME/.credentials.yaml |
凭证文件绝对路径(运行时注入 dshHome 计算默认值) |
dshHome |
string | 运行时注入 | Harness home |
encryptedPath |
string | .credentials.encrypt.yaml |
加密 sidecar 路径 |
statePath |
string | $DSH_HOME/.dsh-encrypt.json |
remember/lockout 状态文件 |
allowEnvFallback |
boolean | true |
是否启用继承环境与 .env 回退层 |
passwordEnv |
string | DSH_CREDENTIAL_PASSWORD |
启动自动解锁密码的环境变量名 |
watch |
boolean | true |
是否监听文件热重载 |
debounceMs |
number | 100 |
热重载防抖毫秒数(≥ 0) |
rememberDays |
number | 0 |
免密登录天数:0 = 每次都输密码,1..30 天,-1 = 永远(补丁配置的初始值;滑块运行时值写入 $DSH_HOME/.dsh-encrypt.json 并优先生效) |
leakGuard |
boolean | true |
泄露检测与输出脱敏总开关(关闭后 HTTP/WS 输出不再扫描) |
leakMinMaskLength |
number | 8 |
参与脱敏的最小值长度(4–64;更短的值视为普通文本) |
leakMaxMaskLength |
number | 256 |
参与脱敏的最大值长度(16–1024;同时是流式脱敏的跨块回看窗口) |
maxUnlockAttempts |
number | 5 |
连续解锁失败多少次后进入锁定(≥ 1) |
lockoutBaseMs |
number | 30000 |
首次锁定窗口毫秒数(≥ 1000;之后每次失败 ×2) |
lockoutMaxMs |
number | 900000 |
锁定窗口上限毫秒数(≥ 1000) |
trustedHosts |
string[] | [] |
非回环部署允许的 Host authority(host 或 host:port);密码修改仍要求 socket 回环 |
保证:
- 静态存储只含密文;内存快照在密文形态下只保存密文记录
- 每条目随机 12 字节 nonce;引用名作为 GCM AAD,记录换位会认证失败
- 双层 SHA3-256 指纹 + GCM 认证标签:篡改且重算指纹的攻击仍会以
VAULT_KEY_MISMATCH失败(与错误主密钥不可区分——这是 AEAD 的诚实答案) - 密码经 Argon2id(m=65536 KiB, t=3, p=1,
@node-rs/argon2)派生,只存盐与 AEAD 验证器;文档中成本参数有上限(m ≤ 256 MiB、t ≤ 64、p ≤ 32),恶意构造的参数无法耗尽内存/CPU - 阅后即焚:密文只在被使用的一刻解密;解密中间 Buffer 立即清零;
withUnlockedBuffer在回调结束后焚毁明文副本;改密的明文集合用后即清空;密钥(KEK)在锁定/卸载时清零(zeroizeBuffer) - 永远密文:设密后文件不可能回退明文;外部明文替换在解锁态被立即重加密,锁定/启动态被拒绝(
plaintextForbidden) - POSIX 上凭证文件必须是 owner-only(
0600),否则启动直接拒绝(chmod 600修复);Windows 无 mode 可查,保护由创建 API 与 OS ACL 表达 - 防爆破:连续解锁失败计数 + 指数退避锁定,计数持久化(重启不清零);接口返回 429 +
Retry-After,面板显示剩余时间 - 防注入回显(Leak Guard):模型解析过的凭证值成为掩码模式;WebUI 的 HTTP 响应体与 WebSocket 文本帧在离开主机前被替换为
[REDACTED:dsh-encrypt](分块边界安全的流式脱敏,绝不先漏出前缀) - Web API 先校验 Host 信任围栏(回环或
trustedHosts),密码变更和 remember 设置还要求 socket 来自回环;请求体限制为 64 KiB,内部错误不会回传路径或堆栈 - 运行时兼容护栏会检测 dsh 版本;跨越不支持的主次版本时以
UNSUPPORTED_DSHfail-closed,同一版本线的 rc 漂移给出警告
诚实边界:
- JavaScript 字符串不可变:解密出的明文无法在内存中清零。给出的保证是不持久化、不缓存、不进日志,以及操作结束即丢弃引用
- 解锁期间主密钥必须驻留内存(否则无法解密任何条目),请以操作系统账户与
0600文件保护 - 忘记密码无法找回(设计如此);唯一恢复手段是清除凭证文件后重新配置
- 完整性清单不是信任根:能改写插件目录的攻击者也能重新生成清单;该校验拦住的是未重算清单的篡改(运行时打补丁、截断、供应链替换)。真正的信任根是用户密码
- 锁定计数存在状态文件:同一 OS 用户可编辑
.dsh-encrypt.json重置计数——锁定防的是「在线猜密码」,不是同账户内的自我解锁 - 脱敏只覆盖本插件已知的值:仅模型实际解析过的凭证会被掩码;模型把密钥通过工具调用(bash/web_search/网络请求)外传不属于输出脱敏的覆盖范围,那属于 permission/tools 层职责;二进制 WS 帧与静态资源不做扫描;把密钥拆分/转码后再输出可绕过子串匹配
- 脱敏掩码集在解锁期间驻留内存(仅已解析值,锁定/改密/卸载即清空)——这是"掩码输出"与"不缓存明文"之间的有意识取舍
- 锁定态下未注册任何掩码(无明文可知),但也没有任何凭证会被解析出去,泄露面为零
VaultError 携带稳定的机器可读 code,消息永不含明文/密文/密钥材料:
| code | 含义 |
|---|---|
PASSWORD_WRONG |
摘要与验证器不匹配(错误密码在触碰任何条目之前即被拒绝) |
PASSWORD_INVALID |
摘要不是 64 位小写 hex 的 SHA3-256 |
LOCAL_ONLY |
该操作(改密/移除密码/免密设置)仅允许 localhost |
REMEMBER_EXPIRED |
免密票据已超过其窗口,需重新输入密码 |
REMEMBER_INVALID |
免密票据与本凭证库不匹配(或库中无票据) |
VAULT_LOCKED |
凭证库已锁定;在「设置 → 加密安全」解锁(或导出 DSH_CREDENTIAL_PASSWORD) |
VAULT_NOT_ENCRYPTED |
尚未设置密码,无锁可解/无密可改/可除 |
VAULT_ALREADY_ENCRYPTED |
已设密码;修改密码请用 change-password |
VAULT_CORRUPTED |
SHA3-256 完整性校验失败,错误中指名出问题的引用 |
VAULT_MIGRATION_REQUIRED |
检测到旧版单文件密文;先运行 sidecar 迁移再启动 Fabric |
VAULT_KEY_MISMATCH |
GCM 认证失败:主密钥不匹配,或密文被替换 |
PASSWORD_INVALID / MASTER_KEY_INVALID |
参数或密钥材料非法 |
TOO_MANY_ATTEMPTS |
解锁失败次数过多,已进入锁定窗口(HTTP 429 + Retry-After,错误携带 retryAfterMs) |
INTEGRITY_FAILED |
发行代码完整性校验失败(插件拒绝加载;合法重建后运行 npm run integrity) |
FORBIDDEN_HOST |
Web 请求 Host 未通过信任围栏 |
UNSUPPORTED_DSH |
当前 dsh 主次版本不在已验证兼容线 |
- rc.6 → rc.10 Fabric 重写:官方
credentials-localrow 不再被禁用;加密文档从旧版.credentials.yaml迁移到.credentials.encrypt.yamlsidecar,启动时遇到旧位置会返回VAULT_MIGRATION_REQUIRED,避免静默丢失凭证。 - KDF 兼容:旧 v2
scryptsidecar 仍可解锁,成功后升级为 v3 Argon2id;新增解锁爆破锁定、凭证泄露检测与输出脱敏、发行代码完整性自校验。 - 运行时兼容:
lib/compat.js对运行中的 dsh 做兼容线检查;同一0.1.x线的 rc 漂移警告,跨主次版本抛UNSUPPORTED_DSH。 - 官方 provider 的
resolve/describe/set/unset接缝保持不变;Fabric 关闭时普通 dsh 完全按官方明文 provider 工作。 - 对外导出:
dsh-encrypt(Fabric 入口)、dsh-encrypt/migrate(旧单文件迁移)、dsh-encrypt/vault(密码学核心)、dsh-encrypt/web(可选路由)、dsh-encrypt/client(浏览器包)
卸载前若希望普通 dsh 继续读取凭证,先把 sidecar 解密导出为官方明文 .credentials.yaml;若没有密码,无法恢复明文。删除插件前至少备份并移除 marker、sidecar 和状态文件:
- 备份/迁移
$DSH_HOME/.credentials.yaml、.credentials.encrypt.yaml与.dsh-encrypt.json - 从 profile bundle 列表移除
dsh-encrypt;不需要删除额外的dsh-encrypt-webrow,因为 Web 路由由 Fabric 入口的可选注入负责 - 移除 bundle:
dsh plugin --profile web remove dsh-encrypt
dsh plugin --profile headless remove dsh-encrypt基础 credentials 行随 bundle 移除自动恢复启用,之后在 DSH 设置中重新录入凭证。
pnpm install # 从 GitHub Releases 拉取 cordis-fabric runtime;Fabric bundle 复用同一 release 源
pnpm test # Fabric hook、marker、sidecar/controller 回归测试;pretest 自动重建完整性清单
pnpm run check:patch # 校验 cordis.patch.yml 与运行时 patch descriptors 无漂移
pnpm pack # prepack 自动重建清单后打包安全测试覆盖:锁定期望(阈值/指数退避/上限/不复位)、泄露检测(正则元字符转义、长度窗口、去重)、流式脱敏的分块边界不泄漏性质、WebSocket 帧过滤(拆分帧/长度类重建/控制帧直通/二进制直通)、完整性清单(篡改/缺失 fail-closed)、429 响应与路由包装(注册前后均被包裹)、101 握手缓冲后过滤帧。
额外验证脚本:
node test/client-smoke.mjs # 浏览器包 ModuleLoader/SSR 冒烟
# 两阶段真实重启 e2e(目标实例与 home 均显式传入,不硬编码回环地址):
node test/e2e-webui.mjs --base http://localhost:3199 --home <dsh-home> --phase 1
node test/e2e-webui.mjs --base http://localhost:3199 --home <dsh-home> --phase 2测试套件仅本地回归用,不随 npm 包分发(files 只含显式列出的 Fabric/core/Web 文件与 cordis.patch.yml)。
MIT · © 2026 Yauntyours