dsh-vault 1.10.73 → 1.10.74-rc.2
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/README-zh.md +38 -8
- package/README.md +20 -9
- package/lib/cli.js +92 -9
- package/lib/client.js +166 -166
- package/lib/store.js +16 -4
- package/package.json +5 -3
package/README-zh.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/README.zh.md#L2869)
|
|
9
9
|
[](https://github.com/Dominic789654/awesome-deepseek-harness/blob/main/README.zh-CN.md#L903)
|
|
10
10
|
|
|
11
|
+
[English](README.md) | **中文**
|
|
12
|
+
|
|
11
13
|
dsh-vault 是一个面向 DeepSeek Harness 的安全加密插件:把你在使用 AI 过程中产生的**用户名、邮箱、手机号、密码、二次动态密钥(TOTP)** 以及开发工作流中常用的 **SSH 连接、API Key、Secret、OAuth access/refresh token** 等敏感凭据加密存储,并通过模型工具提供增删改查、检索、密码生成与动态验证码生成能力。
|
|
12
14
|
|
|
13
15
|
**安全性与实现**
|
|
@@ -225,7 +227,7 @@ echo "$DEMO_API_KEY"
|
|
|
225
227
|
两个前提要知道:
|
|
226
228
|
|
|
227
229
|
- **只有打了 `env` 标签的条目才会被导出。** 一个都没打时,输出的是
|
|
228
|
-
|
|
230
|
+
`dsh-vault: nothing to export — no entry carries the "env" tag.`;先在 UI 里给条目的标签加上 `env`,或让助手执行 `vault_update { id, tags: ["env"] }`。
|
|
229
231
|
- 名字默认由标题 + 字段推导(`DASHSCOPE` + `apiKey` → `DASHSCOPE_API_KEY`);想固定名字就在条目的 `envKeys` 里指定 —— 见[环境变量导出](#环境变量导出)。
|
|
230
232
|
|
|
231
233
|
|
|
@@ -237,7 +239,7 @@ echo "$DEMO_API_KEY"
|
|
|
237
239
|
| `env` | **命令** | 打印带该标签的条目 |
|
|
238
240
|
| `envKeys` | 条目的**字段**(可选) | 固定确切名字,例如 `["DASHSCOPE_API_KEY"]`;旧的单数写法 `envKey` 仍作为"只写一个"的简写接受 |
|
|
239
241
|
|
|
240
|
-
除此之外没有任何叫 `env*` 的东西:`list --json` / `show` 只报 `envKeys`(该条目会导出的名字,也就是 `get` 接受的名字)
|
|
242
|
+
除此之外没有任何叫 `env*` 的东西:`list --json` / `show` 只报 `envKeys`(该条目会导出的名字,也就是 `get` 接受的名字)、`envTagged`(是否被 `env` 包含)与 `hasTotp`(`totp` 能否为它生成动态码)。
|
|
241
243
|
|
|
242
244
|
### 2. 有哪些命令
|
|
243
245
|
|
|
@@ -251,6 +253,8 @@ dsh-vault get my-entry # 取该条目的主密钥,仅 stdout
|
|
|
251
253
|
dsh-vault get my-entry --field apiKey # 取指定字段
|
|
252
254
|
dsh-vault get my-entry --mask # 只确认存在,不打印值
|
|
253
255
|
dsh-vault show my-entry # 非敏感字段的 JSON
|
|
256
|
+
dsh-vault totp my-entry # 当前的 TOTP 动态码(只有 6 位数字)
|
|
257
|
+
dsh-vault totp my-entry --json # 附带 digits / period / secondsRemaining
|
|
254
258
|
dsh-vault env # 带 env 标签的条目 → KEY=VALUE
|
|
255
259
|
dsh-vault env --mask # 人类视角,两段都是 KEY=VALUE:
|
|
256
260
|
# ## exported items
|
|
@@ -263,6 +267,8 @@ dsh-vault verify # 校验主密码,stdout 无输出
|
|
|
263
267
|
|
|
264
268
|
密钥只走 stdout,其余(进度、错误)走 stderr,所以 `$(dsh-vault get …)` 与管道都正常;退出码 `0` 成功、`1` 运行错误、`2` 用法错误。
|
|
265
269
|
|
|
270
|
+
`totp` 是为了让脚本能自动完成两步验证、又完全不碰长期密钥:`code=$(dsh-vault totp my-entry)` 只输出六位数字。如果你用 `get my-entry --field otpSecret`,打印的是**密钥本身** —— 只有确实要把密钥导入别处时才那么做。`--json` 会多给一个 `secondsRemaining`,脚本据此等一个新窗口,而不是硬闯即将过期的码。
|
|
271
|
+
|
|
266
272
|
### 3. shell 引号:不是 `export $(…)`
|
|
267
273
|
|
|
268
274
|
正确的是 `eval "$(dsh-vault env)"`;`export $(dsh-vault env)` 是错的。命令替换会**按空白拆分**,而且**不会**去掉 CLI 输出的引号 —— 值里只要有一个空格就会被拆成多个词,所有值还会带上字面引号:
|
|
@@ -320,7 +326,7 @@ npm i -g dsh-vault && dsh-vault list # 全局
|
|
|
320
326
|
npx dsh-vault list # 或者干脆不装
|
|
321
327
|
```
|
|
322
328
|
|
|
323
|
-
读命令(`list`、`get`、`env`、`show`、`verify`、`export-env`)只依赖 Node,所以全局与 `npx` 都能直接用;**只有写保险库**才需要 harness 运行时,而插件环境一定有。
|
|
329
|
+
读命令(`list`、`get`、`env`、`show`、`totp`、`verify`、`export-env`)只依赖 Node,所以全局与 `npx` 都能直接用;**只有写保险库**才需要 harness 运行时,而插件环境一定有。
|
|
324
330
|
|
|
325
331
|
profile 这条路还有两个注意点:垫片可能被之后在该目录执行的 `pnpm install` 清掉;`pnpm dsh plugin --profile web add dsh-vault` 会依据包的 `dsh.bundle` 声明重整 `dsh.profile.bundles` —— 如果你自己的 patch 层**也**插入了这个插件,就会**挂两份**,要先删掉 patch 层那一行。
|
|
326
332
|
|
|
@@ -401,10 +407,33 @@ k-anonymity **没有**隐藏的东西,直说:
|
|
|
401
407
|
| **完整** | 全部,含批量导入导出、浏览器会话、备份与保险库文件操作 |
|
|
402
408
|
| **自定义…** | 基础 + 任选: 管理 / 导入导出 / 浏览器会话 / 备份与文件 |
|
|
403
409
|
|
|
404
|
-
也可在插件配置里写 `tools: basic|standard|full|custom`;界面选择优先,并保存在 `<vault 目录>/
|
|
410
|
+
也可在插件配置里写 `tools: basic|standard|full|custom`;界面选择优先,并保存在 `<vault 目录>/tools.json`(工具集是进程级设置,不随保险库变化)。
|
|
405
411
|
|
|
406
412
|
## 配置
|
|
407
413
|
|
|
414
|
+
> [!WARNING] 安全:不要把主密码明文写进配置
|
|
415
|
+
> profile patch(`cordis.patch.yml` / `cordis.yml`)是很容易被提交进 git、截图或进日志的配置文件 ——
|
|
416
|
+
> 一行明文的 `masterPassword:` 就等于泄露了整个加密保险库的钥匙。请一律使用
|
|
417
|
+
> `masterPasswordEnv` 并把真实密码放进进程/ shell 环境(例如 `export DSH_VAULT_PASSWORD='…'`)。
|
|
418
|
+
> 凭据类插件的证据化检查清单见
|
|
419
|
+
> [community-plugin-audit handbook](https://github.com/sandbaseai/deepseek-harness-handbook/blob/main/docs/en/security/community-plugin-audit.md)。
|
|
420
|
+
>
|
|
421
|
+
> ```yaml
|
|
422
|
+
> # ✗ 不要 —— 配置文件里写明文主密码
|
|
423
|
+
> - id: vault
|
|
424
|
+
> name: dsh-vault
|
|
425
|
+
> config:
|
|
426
|
+
> masterPassword: 'my-secret'
|
|
427
|
+
> ```
|
|
428
|
+
>
|
|
429
|
+
> ```yaml
|
|
430
|
+
> # ✓ 应该 —— 引用环境变量,并让文件本身保持 0600
|
|
431
|
+
> - id: vault
|
|
432
|
+
> name: dsh-vault
|
|
433
|
+
> config:
|
|
434
|
+
> masterPasswordEnv: DSH_VAULT_PASSWORD
|
|
435
|
+
> ```
|
|
436
|
+
|
|
408
437
|
| 配置项 | 说明 |
|
|
409
438
|
|---|---|
|
|
410
439
|
| `masterPassword` | 直接配置主密码(会出现在 cordis.yml 中,不推荐) |
|
|
@@ -441,7 +470,7 @@ git clone git@github.com:Ox0400/dsh-vault.git
|
|
|
441
470
|
cd dsh-vault
|
|
442
471
|
pnpm install # 安装 devDependencies(typescript/tsdown/vitest 等)
|
|
443
472
|
pnpm build # 构建 host 侧 lib/*.js 与浏览器 bundle lib/client.js
|
|
444
|
-
pnpm test # 运行
|
|
473
|
+
pnpm test # 运行 489 项 vitest 测试
|
|
445
474
|
```
|
|
446
475
|
|
|
447
476
|
> 测试需要 harness 的 `dsh-llm`/`dsh-system-prompt` 等 peer 包,在 harness monorepo 内开发时由 workspace 链接提供。
|
|
@@ -449,7 +478,7 @@ pnpm test # 运行 420 项 vitest 测试
|
|
|
449
478
|
常用命令:
|
|
450
479
|
|
|
451
480
|
```sh
|
|
452
|
-
# 单元 + 集成测试(vitest,
|
|
481
|
+
# 单元 + 集成测试(vitest,489 项)
|
|
453
482
|
pnpm test # 或 npx vitest run
|
|
454
483
|
|
|
455
484
|
# 类型检查
|
|
@@ -462,7 +491,7 @@ pnpm build # = build:host (tsc) + build:client (tsdown)
|
|
|
462
491
|
npm pack
|
|
463
492
|
```
|
|
464
493
|
|
|
465
|
-
仓库内所有测试通过:
|
|
494
|
+
仓库内所有测试通过:489/489(crypto/TOTP/密码生成/store CRUD/网关/集成)。
|
|
466
495
|
|
|
467
496
|
浏览器侧检查跑在真实 `dsh web` 上(不属于 vitest),且**一律拒绝在 default 库上运行**:
|
|
468
497
|
|
|
@@ -504,7 +533,8 @@ UI 不写死颜色,而是读取宿主的设计变量:本地语义层映射
|
|
|
504
533
|
- `dsh.bundle.patch` → `cordis.patch.yml`(安装到 profile 后自动应用的 layer)
|
|
505
534
|
- `dsh.client` → 浏览器端声明(`exports["./client"]` 指向 `lib/client.js`)
|
|
506
535
|
- `prepare` 脚本 → git 安装时自包含构建(`tsc` host + `tsdown` client)
|
|
507
|
-
-
|
|
536
|
+
- 运行时依赖只有 `playwright-core`(懒加载,仅浏览器登录会话使用);其余全部由宿主通过
|
|
537
|
+
`peerDependencies` 提供,不会产生重复实例
|
|
508
538
|
|
|
509
539
|
可选发布途径:
|
|
510
540
|
|
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ Each record has a `title`, an optional `kind`, and any combination of fields:
|
|
|
80
80
|
| `vault_duplicates` | Find duplicate groups: `mode` = `both` (default) / `title` / `content` (no secrets) |
|
|
81
81
|
| `vault_report` | Printable inventory with expiry/rotation columns and a stats footer (no secrets) |
|
|
82
82
|
| `vault_export` / `vault_import` | Portable encrypted backup/migration of the whole vault (separate export password) |
|
|
83
|
-
| `vault_backup` / `vault_backup_now` | Timestamped encrypted backup named `<vault>-backups-YYYY-MM-DD_HH-MM-SS-<hex>.json` (owning vault + date visible); retention pruning keeps the newest N |
|
|
83
|
+
| `vault_backup` / `vault_backup_now` | Timestamped encrypted backup named `<vault>-backups-YYYY-MM-DD_HH-MM-SS-<hex>.json` (owning vault + date visible); retention pruning keeps the newest N (`maxBackups`, default 10) |
|
|
84
84
|
| `vault_restore_backup` | Restore from a backup: `mode: "merge"` (default) copies the backup entries INTO the current vault so they appear in the entries list; `mode: "replace"` overwrites the whole vault with a safety snapshot first |
|
|
85
85
|
| `vault_vault_rename` / `vault_vault_delete` | Rename a named vault (file moves, active session follows) or permanently delete one (default vault protected) |
|
|
86
86
|
| `vault_match_url` | Find login entries matching a URL (Bitwarden/1Password-style: exact host, subdomain, parent domain, path-prefix; www./port normalization) with a 0–100 score — never returns the password |
|
|
@@ -237,7 +237,7 @@ echo "$DEMO_API_KEY"
|
|
|
237
237
|
Two things worth knowing:
|
|
238
238
|
|
|
239
239
|
- **Only `env`-tagged entries are exported.** With none tagged you get
|
|
240
|
-
|
|
240
|
+
`dsh-vault: nothing to export — no entry carries the "env" tag.`; tag one in the UI (the
|
|
241
241
|
entry's tags field) or with `vault_update { id, tags: ["env"] }`.
|
|
242
242
|
- The name is derived from the title and field (`DASHSCOPE` + `apiKey` →
|
|
243
243
|
`DASHSCOPE_API_KEY`) unless the entry sets `envKeys` — see
|
|
@@ -253,7 +253,8 @@ Two things worth knowing:
|
|
|
253
253
|
| `envKeys` | an entry **field** (optional) | pin exact names, e.g. `["DASHSCOPE_API_KEY"]`; the old singular `envKey` is accepted as a one-element shorthand |
|
|
254
254
|
|
|
255
255
|
Nothing else is called `env*`: `list --json` / `show` report `envKeys` (the names
|
|
256
|
-
an entry exports — what `get` accepts)
|
|
256
|
+
an entry exports — what `get` accepts), `envTagged` (whether `env` includes it)
|
|
257
|
+
and `hasTotp` (whether `totp` can produce a code for it).
|
|
257
258
|
|
|
258
259
|
### 2. The commands
|
|
259
260
|
|
|
@@ -267,6 +268,8 @@ dsh-vault get my-entry # the entry's primary secret, stdout
|
|
|
267
268
|
dsh-vault get my-entry --field apiKey # one named field
|
|
268
269
|
dsh-vault get my-entry --mask # confirm it exists without printing it
|
|
269
270
|
dsh-vault show my-entry # non-secret metadata as JSON
|
|
271
|
+
dsh-vault totp my-entry # the current TOTP code (digits only)
|
|
272
|
+
dsh-vault totp my-entry --json # + digits, period, secondsRemaining
|
|
270
273
|
dsh-vault env # env-tagged entries as KEY=VALUE
|
|
271
274
|
dsh-vault env --mask # human view, both sections as KEY=VALUE:
|
|
272
275
|
# ## exported items
|
|
@@ -281,6 +284,13 @@ Secrets go to stdout and everything else (progress, errors) to stderr, so
|
|
|
281
284
|
`$(dsh-vault get …)` and pipelines behave. Exit codes: `0` ok, `1` runtime
|
|
282
285
|
error, `2` usage error.
|
|
283
286
|
|
|
287
|
+
`totp` exists so a script can automate two-factor login without ever handling the
|
|
288
|
+
long-lived secret: `code=$(dsh-vault totp my-entry)` prints six digits and nothing
|
|
289
|
+
else. `get my-entry --field otpSecret` would print the **secret** instead — use it
|
|
290
|
+
only when a tool genuinely needs to enrol the secret elsewhere. `--json` adds
|
|
291
|
+
`secondsRemaining`, which is what lets a caller wait for a fresh window rather
|
|
292
|
+
than race an expiring code.
|
|
293
|
+
|
|
284
294
|
### 3. Shell quoting: not `export $(…)`
|
|
285
295
|
|
|
286
296
|
`eval "$(dsh-vault env)"` is right; `export $(dsh-vault env)` is wrong. Command
|
|
@@ -349,7 +359,7 @@ npm i -g dsh-vault && dsh-vault list # globally
|
|
|
349
359
|
npx dsh-vault list # or not at all
|
|
350
360
|
```
|
|
351
361
|
|
|
352
|
-
The read commands (`list`, `get`, `env`, `show`, `verify`, `export-env`) depend
|
|
362
|
+
The read commands (`list`, `get`, `env`, `show`, `totp`, `verify`, `export-env`) depend
|
|
353
363
|
on Node alone, which is why the global and `npx` forms work. Only *writing* to a
|
|
354
364
|
vault needs the harness runtime, and the plugin always has it.
|
|
355
365
|
|
|
@@ -464,7 +474,7 @@ Switch profiles in **Settings → Credentials → Permissions → Model tools**
|
|
|
464
474
|
| **Full** | everything, incl. bulk import/export, browser sessions, backups and vault-file operations |
|
|
465
475
|
| **Custom…** | core + any of: Management / Import-export / Browser sessions / Backups & files |
|
|
466
476
|
|
|
467
|
-
`tools: basic|standard|full|custom` can also be set in the plugin config; the UI choice wins and is stored in `<vault dir>/
|
|
477
|
+
`tools: basic|standard|full|custom` can also be set in the plugin config; the UI choice wins and is stored in `<vault dir>/tools.json` (the process-wide tool catalog is not per vault).
|
|
468
478
|
|
|
469
479
|
## Configuration
|
|
470
480
|
|
|
@@ -529,7 +539,7 @@ git clone git@github.com:Ox0400/dsh-vault.git
|
|
|
529
539
|
cd dsh-vault
|
|
530
540
|
pnpm install # installs devDependencies (typescript/tsdown/vitest, …)
|
|
531
541
|
pnpm build # builds host lib/*.js and the browser bundle lib/client.js
|
|
532
|
-
pnpm test # runs the
|
|
542
|
+
pnpm test # runs the 489 vitest tests
|
|
533
543
|
```
|
|
534
544
|
|
|
535
545
|
> Tests need harness peer packages such as `dsh-llm`/`dsh-system-prompt`; inside the harness monorepo these resolve via workspace links.
|
|
@@ -537,13 +547,13 @@ pnpm test # runs the 420 vitest tests
|
|
|
537
547
|
Common commands:
|
|
538
548
|
|
|
539
549
|
```sh
|
|
540
|
-
pnpm test # unit + integration tests (vitest,
|
|
550
|
+
pnpm test # unit + integration tests (vitest, 489)
|
|
541
551
|
pnpm typecheck # tsc -p tsconfig.json --noEmit
|
|
542
552
|
pnpm build # = build:host (tsc) + build:client (tsdown)
|
|
543
553
|
npm pack # optional: tarball for `dsh plugin add ./dsh-vault-0.1.1.tgz`
|
|
544
554
|
```
|
|
545
555
|
|
|
546
|
-
All
|
|
556
|
+
All 489 tests pass (crypto / TOTP / password generation / store CRUD / gateway / integration).
|
|
547
557
|
|
|
548
558
|
Browser checks run against a real `dsh web` instead of vitest, and every one of
|
|
549
559
|
them refuses to operate on the default vault:
|
|
@@ -601,7 +611,8 @@ This package is a standard npm bundle:
|
|
|
601
611
|
- `dsh.bundle.patch` → `cordis.patch.yml` (the layer applied automatically when a profile lists this bundle)
|
|
602
612
|
- `dsh.client` → browser-side declaration (`exports["./client"]` points at `lib/client.js`)
|
|
603
613
|
- `prepare` script → self-contained build on git install (`tsc` host + `tsdown` client)
|
|
604
|
-
- Runtime dependencies
|
|
614
|
+
- Runtime dependencies: only `playwright-core` (lazily `require`d, used by browser login sessions).
|
|
615
|
+
Everything else the host provides via `peerDependencies`, so there are no duplicate instances
|
|
605
616
|
|
|
606
617
|
Distribution options:
|
|
607
618
|
|
package/lib/cli.js
CHANGED
|
@@ -25,6 +25,7 @@ import { createInterface } from 'node:readline';
|
|
|
25
25
|
import { ReadStream } from 'node:tty';
|
|
26
26
|
import { openVault, defaultVaultPath } from "./store.js";
|
|
27
27
|
import { envLinesFor, envPairsForEntry, maskSecret, primarySecret, shellQuote } from "./env-export.js";
|
|
28
|
+
import { parseTotpSecret, totp } from "./totp.js";
|
|
28
29
|
function packageVersion() {
|
|
29
30
|
try {
|
|
30
31
|
const require = createRequire(import.meta.url);
|
|
@@ -44,8 +45,9 @@ Commands:
|
|
|
44
45
|
list List entries with their env name (never secrets)
|
|
45
46
|
get <id|title|ENVKEY> Print one field of an entry (default: its main secret)
|
|
46
47
|
show <id|title|ENVKEY> Print an entry's non-secret fields as JSON
|
|
48
|
+
totp <id|title|ENVKEY> Print the entry's current TOTP code (needs otpSecret)
|
|
47
49
|
env Print env-TAGGED entries as KEY=VALUE lines (an entry
|
|
48
|
-
must carry the tag "env"; "list"
|
|
50
|
+
must carry the tag "env"; "list" groups those first).
|
|
49
51
|
One value only? "get <entry>" needs no tag.
|
|
50
52
|
export-env <path> Write those lines to a file (mode 0600)
|
|
51
53
|
verify Check the master password (nothing on stdout)
|
|
@@ -61,13 +63,13 @@ Options:
|
|
|
61
63
|
list options:
|
|
62
64
|
--kind <k> Only entries of this kind
|
|
63
65
|
--tag <t> Only entries carrying this tag
|
|
64
|
-
(each row also shows the env name that "get" accepts
|
|
65
|
-
the count of further keys in parentheses
|
|
66
|
-
when the entry is included in "dsh-vault env")
|
|
66
|
+
(each row also shows the env name that "get" accepts
|
|
67
|
+
and the count of further keys in parentheses)
|
|
67
68
|
|
|
68
69
|
JSON output ("list --json", "show") reports per entry:
|
|
69
70
|
envKeys the env names it exports (what "get" accepts)
|
|
70
71
|
envTagged whether "dsh-vault env" includes it
|
|
72
|
+
hasTotp whether "totp <entry>" can produce a code
|
|
71
73
|
|
|
72
74
|
get options:
|
|
73
75
|
--field <name> apiKey | secret | accessToken | refreshToken | privateKey
|
|
@@ -76,6 +78,14 @@ get options:
|
|
|
76
78
|
--mask Print a masked value (first 4 chars + ***)
|
|
77
79
|
--all Print the whole entry as JSON (every secret — opt-in)
|
|
78
80
|
|
|
81
|
+
totp options:
|
|
82
|
+
(none beyond the common options; --json adds digits, period and secondsRemaining)
|
|
83
|
+
|
|
84
|
+
The code is the only thing on stdout, so 2FA can be automated:
|
|
85
|
+
code=$(dsh-vault totp TwoFactor)
|
|
86
|
+
"get <entry> --field otpSecret" prints the long-lived SECRET instead — use
|
|
87
|
+
"totp" when a script needs a code, so the secret stays out of logs and history.
|
|
88
|
+
|
|
79
89
|
env options:
|
|
80
90
|
--prefix <P> Prefix for derived key names (an explicit envKey is kept verbatim)
|
|
81
91
|
--kind <k> Only entries of this kind
|
|
@@ -219,7 +229,13 @@ async function promptHidden(prompt) {
|
|
|
219
229
|
}
|
|
220
230
|
async function readStdinLine() {
|
|
221
231
|
const rl = createInterface({ input: process.stdin });
|
|
222
|
-
|
|
232
|
+
// Zero-byte input (</dev/null, a `pass` lookup that failed, a CI step with no
|
|
233
|
+
// secret set) emits `close` but never `line`, so waiting only for `line` hung
|
|
234
|
+
// forever and the empty-password check below was unreachable.
|
|
235
|
+
const line = await new Promise(res => {
|
|
236
|
+
rl.once('line', res);
|
|
237
|
+
rl.once('close', () => res(''));
|
|
238
|
+
});
|
|
223
239
|
rl.close();
|
|
224
240
|
return line;
|
|
225
241
|
}
|
|
@@ -255,6 +271,11 @@ async function resolvePassword(parsed, io) {
|
|
|
255
271
|
* prints exactly the value `env` would emit for that key.
|
|
256
272
|
*/
|
|
257
273
|
export function resolveEntry(store, needle) {
|
|
274
|
+
// `''` is a substring of every title, so an empty argument used to match a
|
|
275
|
+
// single entry and print its secret with exit 0.
|
|
276
|
+
if (needle.trim().length === 0) {
|
|
277
|
+
throw new Error('an empty name matches every entry — pass an id, a title, an envKey or an exported key');
|
|
278
|
+
}
|
|
258
279
|
const entries = store.list();
|
|
259
280
|
const byId = entries.find(e => e.id === needle);
|
|
260
281
|
if (byId !== undefined)
|
|
@@ -324,6 +345,9 @@ function publicFields(entry) {
|
|
|
324
345
|
out[key] = value;
|
|
325
346
|
}
|
|
326
347
|
out.hasSecret = primarySecret(entry) !== undefined;
|
|
348
|
+
// Presence only — the same level of detail as hasSecret, so a script can find
|
|
349
|
+
// the entries `totp` accepts without pulling any secret into a transcript.
|
|
350
|
+
out.hasTotp = typeof entry.otpSecret === 'string' && entry.otpSecret.length > 0;
|
|
327
351
|
// ONE name for "the env names this entry exports": `envKeys`. It used to be a
|
|
328
352
|
// configured `envKeys` plus a computed `exportedKeys`, which read as two
|
|
329
353
|
// similar things — the computed list is the useful one (it already starts with
|
|
@@ -333,7 +357,7 @@ function publicFields(entry) {
|
|
|
333
357
|
out.envTagged = (entry.tags ?? []).includes('env');
|
|
334
358
|
return out;
|
|
335
359
|
}
|
|
336
|
-
const COMMANDS = new Set(['list', 'get', 'show', 'env', 'export-env', 'verify']);
|
|
360
|
+
const COMMANDS = new Set(['list', 'get', 'show', 'totp', 'env', 'export-env', 'verify']);
|
|
337
361
|
/** Run one CLI invocation; returns the process exit code. */
|
|
338
362
|
export async function runCli(argv, io) {
|
|
339
363
|
const parsed = parseArgs(argv);
|
|
@@ -359,6 +383,7 @@ export async function runCli(argv, io) {
|
|
|
359
383
|
list: ['kind', 'tag'],
|
|
360
384
|
get: ['field', 'fields', 'mask', 'all'],
|
|
361
385
|
show: [],
|
|
386
|
+
totp: [],
|
|
362
387
|
env: ['prefix', 'kind', 'keys-only', 'mask', 'explain', 'file'],
|
|
363
388
|
'export-env': ['prefix', 'kind', 'keys-only'],
|
|
364
389
|
verify: [],
|
|
@@ -371,6 +396,15 @@ export async function runCli(argv, io) {
|
|
|
371
396
|
io.err(`accepted: ${[...allowed].map(f => `--${f}`).join(' ')}\n`);
|
|
372
397
|
return 2;
|
|
373
398
|
}
|
|
399
|
+
// `get my-entry --field` (no value) set the flag to `true`, which stringFlag
|
|
400
|
+
// reads as "not given" — so the command quietly printed the primary secret
|
|
401
|
+
// instead of the field the user asked for.
|
|
402
|
+
const needsValue = ['field', 'fields', 'kind', 'tag', 'prefix', 'file', 'vault', 'path'];
|
|
403
|
+
const valueless = needsValue.filter(name => parsed.flags.get(name) === true);
|
|
404
|
+
if (valueless.length > 0) {
|
|
405
|
+
io.err(`dsh-vault: ${valueless.map(f => `--${f}`).join(', ')} needs a value\n`);
|
|
406
|
+
return 2;
|
|
407
|
+
}
|
|
374
408
|
const json = parsed.flags.get('json') === true;
|
|
375
409
|
const mask = parsed.flags.get('mask') === true;
|
|
376
410
|
const keysOnly = parsed.flags.get('keys-only') === true;
|
|
@@ -469,15 +503,64 @@ export async function runCli(argv, io) {
|
|
|
469
503
|
else {
|
|
470
504
|
value = fieldValue(entry, name);
|
|
471
505
|
}
|
|
472
|
-
|
|
506
|
+
// Absent, empty and non-string are three different answers. Reporting all
|
|
507
|
+
// of them as "has no field" made `--field expiresAt` (a number) and
|
|
508
|
+
// `--field tags` (an array) look like fields that do not exist.
|
|
509
|
+
if (value === undefined) {
|
|
473
510
|
io.err(`dsh-vault: "${entry.title}" has no field "${name}"\n`);
|
|
474
511
|
return 1;
|
|
475
512
|
}
|
|
513
|
+
if (value === null || (typeof value === 'string' && value.length === 0)) {
|
|
514
|
+
io.err(`dsh-vault: "${entry.title}" field "${name}" is empty\n`);
|
|
515
|
+
return 1;
|
|
516
|
+
}
|
|
476
517
|
const textual = name === 'username' || name === 'url' || name === 'notes' || name.startsWith('fields.');
|
|
477
|
-
|
|
518
|
+
// Strings keep the old masking rule; anything else is printed as JSON so
|
|
519
|
+
// booleans, timestamps and tags stay usable from a script.
|
|
520
|
+
const shown = typeof value === 'string'
|
|
521
|
+
? (mask && !textual ? maskSecret(value) : value)
|
|
522
|
+
: JSON.stringify(value);
|
|
478
523
|
io.out(json ? `${JSON.stringify({ id: entry.id, title: entry.title, field: name, value: shown })}\n` : `${shown}\n`);
|
|
479
524
|
return 0;
|
|
480
525
|
}
|
|
526
|
+
if (parsed.command === 'totp') {
|
|
527
|
+
const needle = parsed.positional[0];
|
|
528
|
+
if (needle === undefined) {
|
|
529
|
+
io.err('dsh-vault: totp needs an entry id, title or env key\n');
|
|
530
|
+
return 2;
|
|
531
|
+
}
|
|
532
|
+
const entry = resolveEntry(store, needle).entry;
|
|
533
|
+
const secret = typeof entry.otpSecret === 'string' && entry.otpSecret.length > 0 ? entry.otpSecret : undefined;
|
|
534
|
+
if (secret === undefined) {
|
|
535
|
+
io.err(`dsh-vault: "${entry.title}" has no TOTP secret\n`);
|
|
536
|
+
io.err('hint: "show <entry>" lists non-secret fields; the secret goes in the entry\'s otpSecret field.\n');
|
|
537
|
+
return 1;
|
|
538
|
+
}
|
|
539
|
+
const nowMs = Date.now();
|
|
540
|
+
let parsedSecret;
|
|
541
|
+
let code;
|
|
542
|
+
try {
|
|
543
|
+
parsedSecret = parseTotpSecret(secret);
|
|
544
|
+
code = totp(secret, nowMs);
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
io.err(`dsh-vault: "${entry.title}" has an unusable otpSecret: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
548
|
+
return 1;
|
|
549
|
+
}
|
|
550
|
+
if (!json) {
|
|
551
|
+
// Digits only: the whole point is `code=$(dsh-vault totp …)` in a script.
|
|
552
|
+
io.out(`${code}\n`);
|
|
553
|
+
return 0;
|
|
554
|
+
}
|
|
555
|
+
// `secondsRemaining` is what lets a caller wait for a fresh window instead
|
|
556
|
+
// of racing an expiring code, which is the classic 2FA automation bug.
|
|
557
|
+
const secondsRemaining = parsedSecret.periodSeconds - Math.floor(nowMs / 1000) % parsedSecret.periodSeconds;
|
|
558
|
+
io.out(`${JSON.stringify({
|
|
559
|
+
id: entry.id, title: entry.title, code,
|
|
560
|
+
digits: parsedSecret.digits, period: parsedSecret.periodSeconds, secondsRemaining,
|
|
561
|
+
})}\n`);
|
|
562
|
+
return 0;
|
|
563
|
+
}
|
|
481
564
|
// env / export-env
|
|
482
565
|
const prefix = stringFlag(parsed, 'prefix') ?? '';
|
|
483
566
|
const kind = stringFlag(parsed, 'kind');
|
|
@@ -560,7 +643,7 @@ export async function runCli(argv, io) {
|
|
|
560
643
|
io.err(' Tag one entry and run it again:\n');
|
|
561
644
|
io.err(' UI: Settings → Credentials → the entry → tags → env\n');
|
|
562
645
|
io.err(' assistant: vault_update { id, tags: ["env"] }\n');
|
|
563
|
-
io.err(' `dsh-vault list`
|
|
646
|
+
io.err(' `dsh-vault list` groups tagged entries under "Exported by `env`" and shows each name.\n');
|
|
564
647
|
io.err(' Only need one value? `dsh-vault get <entry>` needs no tag at all.\n');
|
|
565
648
|
}
|
|
566
649
|
return 0;
|
package/lib/client.js
CHANGED
|
@@ -510,197 +510,197 @@ window.__ModuleLoader__.load({
|
|
|
510
510
|
document.head.appendChild(tag);
|
|
511
511
|
}
|
|
512
512
|
var VaultSection_module_css_default = {
|
|
513
|
-
"
|
|
513
|
+
"auditChipActive": "t_j5va_auditChipActive",
|
|
514
|
+
"badgeBtn": "t_j5va_badgeBtn",
|
|
515
|
+
"trashButton": "t_j5va_trashButton",
|
|
516
|
+
"pwHistPwd": "t_j5va_pwHistPwd",
|
|
517
|
+
"detailTotp": "t_j5va_detailTotp",
|
|
514
518
|
"secretField": "t_j5va_secretField",
|
|
515
|
-
"
|
|
516
|
-
"
|
|
517
|
-
"
|
|
518
|
-
"
|
|
519
|
-
"
|
|
519
|
+
"saveButton": "t_j5va_saveButton",
|
|
520
|
+
"error": "t_j5va_error",
|
|
521
|
+
"modeAuto": "t_j5va_modeAuto",
|
|
522
|
+
"checkField": "t_j5va_checkField",
|
|
523
|
+
"editorActions": "t_j5va_editorActions",
|
|
524
|
+
"dangerButton": "t_j5va_dangerButton",
|
|
525
|
+
"pwFair": "t_j5va_pwFair",
|
|
526
|
+
"tabActive": "t_j5va_tabActive",
|
|
527
|
+
"toolbar": "t_j5va_toolbar",
|
|
528
|
+
"histAdd": "t_j5va_histAdd",
|
|
529
|
+
"lenChipActive": "t_j5va_lenChipActive",
|
|
530
|
+
"emptyBox": "t_j5va_emptyBox",
|
|
531
|
+
"reportBox": "t_j5va_reportBox",
|
|
532
|
+
"pwMeter": "t_j5va_pwMeter",
|
|
533
|
+
"tabs": "t_j5va_tabs",
|
|
534
|
+
"detailRemain": "t_j5va_detailRemain",
|
|
535
|
+
"recentSearch": "t_j5va_recentSearch",
|
|
536
|
+
"recentLabel": "t_j5va_recentLabel",
|
|
537
|
+
"tagRow": "t_j5va_tagRow",
|
|
538
|
+
"tagCount": "t_j5va_tagCount",
|
|
539
|
+
"recentChip": "t_j5va_recentChip",
|
|
540
|
+
"tagManagerHead": "t_j5va_tagManagerHead",
|
|
541
|
+
"pwHistRow": "t_j5va_pwHistRow",
|
|
542
|
+
"status": "t_j5va_status",
|
|
543
|
+
"kindIcon": "t_j5va_kindIcon",
|
|
520
544
|
"importActions": "t_j5va_importActions",
|
|
521
|
-
"
|
|
522
|
-
"
|
|
523
|
-
"vaultManageRow": "t_j5va_vaultManageRow",
|
|
524
|
-
"importName": "t_j5va_importName",
|
|
525
|
-
"iconFieldWrap": "t_j5va_iconFieldWrap",
|
|
526
|
-
"copied": "t_j5va_copied",
|
|
527
|
-
"field": "t_j5va_field",
|
|
545
|
+
"reportLine": "t_j5va_reportLine",
|
|
546
|
+
"strengthTrack": "t_j5va_strengthTrack",
|
|
528
547
|
"rowExpired": "t_j5va_rowExpired",
|
|
529
|
-
"dangerButton": "t_j5va_dangerButton",
|
|
530
|
-
"auditChips": "t_j5va_auditChips",
|
|
531
|
-
"reportTitle": "t_j5va_reportTitle",
|
|
532
|
-
"healthBar": "t_j5va_healthBar",
|
|
533
|
-
"pwStrong": "t_j5va_pwStrong",
|
|
534
|
-
"tagName": "t_j5va_tagName",
|
|
535
|
-
"healthSummary": "t_j5va_healthSummary",
|
|
536
|
-
"lenChip": "t_j5va_lenChip",
|
|
537
|
-
"genOptRow": "t_j5va_genOptRow",
|
|
538
|
-
"exportChips": "t_j5va_exportChips",
|
|
539
|
-
"riskBanner": "t_j5va_riskBanner",
|
|
540
|
-
"searchBox": "t_j5va_searchBox",
|
|
541
548
|
"list": "t_j5va_list",
|
|
542
|
-
"
|
|
543
|
-
"
|
|
544
|
-
"
|
|
545
|
-
"
|
|
549
|
+
"bulkItem": "t_j5va_bulkItem",
|
|
550
|
+
"auditIssues": "t_j5va_auditIssues",
|
|
551
|
+
"pwBar": "t_j5va_pwBar",
|
|
552
|
+
"exportChips": "t_j5va_exportChips",
|
|
546
553
|
"customEq": "t_j5va_customEq",
|
|
547
|
-
"
|
|
548
|
-
"
|
|
549
|
-
"
|
|
554
|
+
"pwHistBox": "t_j5va_pwHistBox",
|
|
555
|
+
"auditChip": "t_j5va_auditChip",
|
|
556
|
+
"kindChip": "t_j5va_kindChip",
|
|
557
|
+
"rowMain": "t_j5va_rowMain",
|
|
558
|
+
"favToggle": "t_j5va_favToggle",
|
|
559
|
+
"policyField": "t_j5va_policyField",
|
|
560
|
+
"lenChip": "t_j5va_lenChip",
|
|
561
|
+
"copyAllBtn": "t_j5va_copyAllBtn",
|
|
550
562
|
"toolbarDivider": "t_j5va_toolbarDivider",
|
|
551
|
-
"
|
|
552
|
-
"
|
|
553
|
-
"
|
|
554
|
-
"
|
|
555
|
-
"
|
|
556
|
-
"
|
|
557
|
-
"
|
|
558
|
-
"
|
|
559
|
-
"auditChipActive": "t_j5va_auditChipActive",
|
|
560
|
-
"badge": "t_j5va_badge",
|
|
563
|
+
"importRow": "t_j5va_importRow",
|
|
564
|
+
"field": "t_j5va_field",
|
|
565
|
+
"emojiPicker": "t_j5va_emojiPicker",
|
|
566
|
+
"actionGroup": "t_j5va_actionGroup",
|
|
567
|
+
"strengthStrong": "t_j5va_strengthStrong",
|
|
568
|
+
"vaultSwitch": "t_j5va_vaultSwitch",
|
|
569
|
+
"customFieldRows": "t_j5va_customFieldRows",
|
|
570
|
+
"inlineField": "t_j5va_inlineField",
|
|
561
571
|
"rowCheck": "t_j5va_rowCheck",
|
|
562
|
-
"
|
|
563
|
-
"
|
|
564
|
-
"
|
|
572
|
+
"srOnly": "t_j5va_srOnly",
|
|
573
|
+
"modeReadonly": "t_j5va_modeReadonly",
|
|
574
|
+
"histUpdate": "t_j5va_histUpdate",
|
|
575
|
+
"clearButton": "t_j5va_clearButton",
|
|
576
|
+
"dueBadge": "t_j5va_dueBadge",
|
|
577
|
+
"riskBanner": "t_j5va_riskBanner",
|
|
578
|
+
"attachBox": "t_j5va_attachBox",
|
|
579
|
+
"kindChipActive": "t_j5va_kindChipActive",
|
|
565
580
|
"importList": "t_j5va_importList",
|
|
566
|
-
"
|
|
567
|
-
"
|
|
568
|
-
"
|
|
569
|
-
"
|
|
570
|
-
"
|
|
571
|
-
"
|
|
572
|
-
"
|
|
573
|
-
"
|
|
574
|
-
"
|
|
581
|
+
"moreWrap": "t_j5va_moreWrap",
|
|
582
|
+
"highBadge": "t_j5va_highBadge",
|
|
583
|
+
"dupGroup": "t_j5va_dupGroup",
|
|
584
|
+
"genOpts": "t_j5va_genOpts",
|
|
585
|
+
"selectActive": "t_j5va_selectActive",
|
|
586
|
+
"badge": "t_j5va_badge",
|
|
587
|
+
"badgeWarn": "t_j5va_badgeWarn",
|
|
588
|
+
"riskWarn": "t_j5va_riskWarn",
|
|
589
|
+
"strengthFair": "t_j5va_strengthFair",
|
|
575
590
|
"tabPane": "t_j5va_tabPane",
|
|
576
|
-
"
|
|
577
|
-
"
|
|
578
|
-
"
|
|
579
|
-
"
|
|
580
|
-
"
|
|
581
|
-
"
|
|
582
|
-
"rowDue": "t_j5va_rowDue",
|
|
583
|
-
"tagActions": "t_j5va_tagActions",
|
|
584
|
-
"vaultSwitch": "t_j5va_vaultSwitch",
|
|
585
|
-
"recentLabel": "t_j5va_recentLabel",
|
|
586
|
-
"badgeActive": "t_j5va_badgeActive",
|
|
587
|
-
"gridDetail": "t_j5va_gridDetail",
|
|
588
|
-
"detailCode": "t_j5va_detailCode",
|
|
591
|
+
"colorFieldWrap": "t_j5va_colorFieldWrap",
|
|
592
|
+
"customFieldRow": "t_j5va_customFieldRow",
|
|
593
|
+
"actionPrimary": "t_j5va_actionPrimary",
|
|
594
|
+
"bulkBar": "t_j5va_bulkBar",
|
|
595
|
+
"tagName": "t_j5va_tagName",
|
|
596
|
+
"reportTitle": "t_j5va_reportTitle",
|
|
589
597
|
"actionBtn": "t_j5va_actionBtn",
|
|
590
|
-
"
|
|
598
|
+
"rowSelected": "t_j5va_rowSelected",
|
|
599
|
+
"histDanger": "t_j5va_histDanger",
|
|
591
600
|
"pinStar": "t_j5va_pinStar",
|
|
592
|
-
"
|
|
593
|
-
"
|
|
594
|
-
"
|
|
595
|
-
"revealButton": "t_j5va_revealButton",
|
|
596
|
-
"modeAsk": "t_j5va_modeAsk",
|
|
597
|
-
"actionGroupLabel": "t_j5va_actionGroupLabel",
|
|
598
|
-
"status": "t_j5va_status",
|
|
599
|
-
"rowMain": "t_j5va_rowMain",
|
|
601
|
+
"actionGroups": "t_j5va_actionGroups",
|
|
602
|
+
"importDesc": "t_j5va_importDesc",
|
|
603
|
+
"vaultHeader": "t_j5va_vaultHeader",
|
|
600
604
|
"pinOn": "t_j5va_pinOn",
|
|
605
|
+
"detailActions": "t_j5va_detailActions",
|
|
606
|
+
"vaultManageRow": "t_j5va_vaultManageRow",
|
|
607
|
+
"tagActions": "t_j5va_tagActions",
|
|
608
|
+
"row": "t_j5va_row",
|
|
609
|
+
"editor": "t_j5va_editor",
|
|
610
|
+
"totp": "t_j5va_totp",
|
|
611
|
+
"auditWhen": "t_j5va_auditWhen",
|
|
612
|
+
"reportSub": "t_j5va_reportSub",
|
|
613
|
+
"pwStrong": "t_j5va_pwStrong",
|
|
614
|
+
"colorSwatch": "t_j5va_colorSwatch",
|
|
601
615
|
"gridInput": "t_j5va_gridInput",
|
|
602
|
-
"
|
|
603
|
-
"title": "t_j5va_title",
|
|
604
|
-
"lockedBanner": "t_j5va_lockedBanner",
|
|
605
|
-
"selectActive": "t_j5va_selectActive",
|
|
606
|
-
"backupButton": "t_j5va_backupButton",
|
|
607
|
-
"toolbar": "t_j5va_toolbar",
|
|
608
|
-
"importDesc": "t_j5va_importDesc",
|
|
609
|
-
"auditIssues": "t_j5va_auditIssues",
|
|
610
|
-
"editorActions": "t_j5va_editorActions",
|
|
611
|
-
"gridName": "t_j5va_gridName",
|
|
612
|
-
"uriCode": "t_j5va_uriCode",
|
|
613
|
-
"section": "t_j5va_section",
|
|
614
|
-
"badgeLink": "t_j5va_badgeLink",
|
|
616
|
+
"pwWeak": "t_j5va_pwWeak",
|
|
615
617
|
"favActive": "t_j5va_favActive",
|
|
616
|
-
"
|
|
617
|
-
"
|
|
618
|
-
"
|
|
619
|
-
"
|
|
620
|
-
"
|
|
621
|
-
"
|
|
622
|
-
"
|
|
623
|
-
"
|
|
624
|
-
"
|
|
618
|
+
"listCompact": "t_j5va_listCompact",
|
|
619
|
+
"brandGlyph": "t_j5va_brandGlyph",
|
|
620
|
+
"modeAsk": "t_j5va_modeAsk",
|
|
621
|
+
"customValueInput": "t_j5va_customValueInput",
|
|
622
|
+
"detailCode": "t_j5va_detailCode",
|
|
623
|
+
"gridDetail": "t_j5va_gridDetail",
|
|
624
|
+
"sortButton": "t_j5va_sortButton",
|
|
625
|
+
"urlGlyph": "t_j5va_urlGlyph",
|
|
626
|
+
"badgeLink": "t_j5va_badgeLink",
|
|
627
|
+
"strengthStars": "t_j5va_strengthStars",
|
|
628
|
+
"gridRow": "t_j5va_gridRow",
|
|
625
629
|
"badgeDanger": "t_j5va_badgeDanger",
|
|
626
|
-
"
|
|
627
|
-
"strengthStrong": "t_j5va_strengthStrong",
|
|
628
|
-
"auditChip": "t_j5va_auditChip",
|
|
629
|
-
"pwFair": "t_j5va_pwFair",
|
|
630
|
-
"totpRing": "t_j5va_totpRing",
|
|
631
|
-
"tab": "t_j5va_tab",
|
|
632
|
-
"customFieldRows": "t_j5va_customFieldRows",
|
|
633
|
-
"favToggle": "t_j5va_favToggle",
|
|
634
|
-
"emojiBtn": "t_j5va_emojiBtn",
|
|
635
|
-
"tabActive": "t_j5va_tabActive",
|
|
636
|
-
"intro": "t_j5va_intro",
|
|
637
|
-
"pwLabel": "t_j5va_pwLabel",
|
|
638
|
-
"trashButton": "t_j5va_trashButton",
|
|
639
|
-
"hit": "t_j5va_hit",
|
|
640
|
-
"histDanger": "t_j5va_histDanger",
|
|
630
|
+
"healthBar": "t_j5va_healthBar",
|
|
641
631
|
"histNeutral": "t_j5va_histNeutral",
|
|
632
|
+
"section": "t_j5va_section",
|
|
633
|
+
"emptyHint": "t_j5va_emptyHint",
|
|
634
|
+
"totpCopy": "t_j5va_totpCopy",
|
|
635
|
+
"kindFilter": "t_j5va_kindFilter",
|
|
636
|
+
"recentClear": "t_j5va_recentClear",
|
|
637
|
+
"gridName": "t_j5va_gridName",
|
|
638
|
+
"pwLabel": "t_j5va_pwLabel",
|
|
639
|
+
"auditRow": "t_j5va_auditRow",
|
|
642
640
|
"editorBody": "t_j5va_editorBody",
|
|
643
|
-
"
|
|
644
|
-
"
|
|
645
|
-
"
|
|
646
|
-
"
|
|
647
|
-
"
|
|
648
|
-
"
|
|
649
|
-
"
|
|
650
|
-
"
|
|
651
|
-
"
|
|
641
|
+
"searchBox": "t_j5va_searchBox",
|
|
642
|
+
"healthSummary": "t_j5va_healthSummary",
|
|
643
|
+
"uriCode": "t_j5va_uriCode",
|
|
644
|
+
"toolbarFilters": "t_j5va_toolbarFilters",
|
|
645
|
+
"policyBar": "t_j5va_policyBar",
|
|
646
|
+
"histLink": "t_j5va_histLink",
|
|
647
|
+
"tab": "t_j5va_tab",
|
|
648
|
+
"genOptRow": "t_j5va_genOptRow",
|
|
649
|
+
"lockedBanner": "t_j5va_lockedBanner",
|
|
652
650
|
"strengthWeak": "t_j5va_strengthWeak",
|
|
653
|
-
"
|
|
654
|
-
"
|
|
651
|
+
"empty": "t_j5va_empty",
|
|
652
|
+
"addButton": "t_j5va_addButton",
|
|
653
|
+
"brandGlyphSm": "t_j5va_brandGlyphSm",
|
|
654
|
+
"riskDanger": "t_j5va_riskDanger",
|
|
655
|
+
"lockedTag": "t_j5va_lockedTag",
|
|
656
|
+
"reportLineLink": "t_j5va_reportLineLink",
|
|
657
|
+
"actionGroupLabel": "t_j5va_actionGroupLabel",
|
|
658
|
+
"identity": "t_j5va_identity",
|
|
659
|
+
"hit": "t_j5va_hit",
|
|
660
|
+
"colorPalette": "t_j5va_colorPalette",
|
|
661
|
+
"rowDue": "t_j5va_rowDue",
|
|
662
|
+
"dupMerge": "t_j5va_dupMerge",
|
|
655
663
|
"scoreRing": "t_j5va_scoreRing",
|
|
656
|
-
"
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
"
|
|
664
|
+
"totpRing": "t_j5va_totpRing",
|
|
665
|
+
"badgeOk": "t_j5va_badgeOk",
|
|
666
|
+
"dupNames": "t_j5va_dupNames",
|
|
667
|
+
"vaultTarget": "t_j5va_vaultTarget",
|
|
660
668
|
"moreMenu": "t_j5va_moreMenu",
|
|
661
|
-
"
|
|
662
|
-
"
|
|
663
|
-
"
|
|
669
|
+
"auditChips": "t_j5va_auditChips",
|
|
670
|
+
"customKeyInput": "t_j5va_customKeyInput",
|
|
671
|
+
"revealButton": "t_j5va_revealButton",
|
|
672
|
+
"iconFieldWrap": "t_j5va_iconFieldWrap",
|
|
673
|
+
"intro": "t_j5va_intro",
|
|
674
|
+
"pwHistTime": "t_j5va_pwHistTime",
|
|
675
|
+
"attachAdd": "t_j5va_attachAdd",
|
|
676
|
+
"searchKbd": "t_j5va_searchKbd",
|
|
664
677
|
"lenChips": "t_j5va_lenChips",
|
|
665
|
-
"
|
|
678
|
+
"headerMessage": "t_j5va_headerMessage",
|
|
679
|
+
"detailDeleteBtn": "t_j5va_detailDeleteBtn",
|
|
666
680
|
"strengthFill": "t_j5va_strengthFill",
|
|
667
|
-
"
|
|
668
|
-
"
|
|
669
|
-
"resultCount": "t_j5va_resultCount",
|
|
670
|
-
"attachBox": "t_j5va_attachBox",
|
|
671
|
-
"tabs": "t_j5va_tabs",
|
|
672
|
-
"bulkItem": "t_j5va_bulkItem",
|
|
673
|
-
"reportBox": "t_j5va_reportBox",
|
|
681
|
+
"pinOff": "t_j5va_pinOff",
|
|
682
|
+
"deleteButton": "t_j5va_deleteButton",
|
|
674
683
|
"rowActions": "t_j5va_rowActions",
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
"
|
|
682
|
-
"
|
|
684
|
+
"detailItem": "t_j5va_detailItem",
|
|
685
|
+
"badgeActive": "t_j5va_badgeActive",
|
|
686
|
+
"moreButton": "t_j5va_moreButton",
|
|
687
|
+
"toolbarMain": "t_j5va_toolbarMain",
|
|
688
|
+
"reportJump": "t_j5va_reportJump",
|
|
689
|
+
"tagManager": "t_j5va_tagManager",
|
|
690
|
+
"genHistItem": "t_j5va_genHistItem",
|
|
691
|
+
"title": "t_j5va_title",
|
|
683
692
|
"inlineInput": "t_j5va_inlineInput",
|
|
684
|
-
"
|
|
685
|
-
"
|
|
693
|
+
"bulkCount": "t_j5va_bulkCount",
|
|
694
|
+
"emojiBtn": "t_j5va_emojiBtn",
|
|
695
|
+
"copied": "t_j5va_copied",
|
|
696
|
+
"resultCount": "t_j5va_resultCount",
|
|
697
|
+
"exportChip": "t_j5va_exportChip",
|
|
698
|
+
"importName": "t_j5va_importName",
|
|
699
|
+
"retryButton": "t_j5va_retryButton",
|
|
700
|
+
"backupButton": "t_j5va_backupButton",
|
|
686
701
|
"gridActions": "t_j5va_gridActions",
|
|
687
|
-
"
|
|
688
|
-
"
|
|
689
|
-
"deleteButton": "t_j5va_deleteButton",
|
|
690
|
-
"pwHistTime": "t_j5va_pwHistTime",
|
|
691
|
-
"sortButton": "t_j5va_sortButton",
|
|
692
|
-
"tagManagerHead": "t_j5va_tagManagerHead",
|
|
693
|
-
"actionGroup": "t_j5va_actionGroup",
|
|
694
|
-
"error": "t_j5va_error",
|
|
695
|
-
"vaultHeader": "t_j5va_vaultHeader",
|
|
696
|
-
"checkField": "t_j5va_checkField",
|
|
697
|
-
"clearButton": "t_j5va_clearButton",
|
|
698
|
-
"headerMessage": "t_j5va_headerMessage",
|
|
699
|
-
"reportSub": "t_j5va_reportSub",
|
|
700
|
-
"rowSelected": "t_j5va_rowSelected",
|
|
701
|
-
"listCompact": "t_j5va_listCompact",
|
|
702
|
-
"customFieldRow": "t_j5va_customFieldRow",
|
|
703
|
-
"inlineField": "t_j5va_inlineField"
|
|
702
|
+
"genHist": "t_j5va_genHist",
|
|
703
|
+
"detailBox": "t_j5va_detailBox"
|
|
704
704
|
};
|
|
705
705
|
//#endregion
|
|
706
706
|
//#region src/client/VaultSection.tsx
|
package/lib/store.js
CHANGED
|
@@ -977,15 +977,27 @@ export class VaultStore {
|
|
|
977
977
|
}
|
|
978
978
|
throw error;
|
|
979
979
|
}
|
|
980
|
-
|
|
980
|
+
// Name the file in every failure below: a vault can live at an explicit
|
|
981
|
+
// --path, and "Unexpected token 'o'" or "version undefined" told the user
|
|
982
|
+
// neither which file was damaged nor that it was damaged at all.
|
|
983
|
+
let parsed;
|
|
984
|
+
try {
|
|
985
|
+
parsed = JSON.parse(raw);
|
|
986
|
+
}
|
|
987
|
+
catch {
|
|
988
|
+
throw new Error(`${this.path} is not valid JSON — it is corrupt or not a vault file`);
|
|
989
|
+
}
|
|
990
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
991
|
+
throw new Error(`${this.path} is not a vault file (expected a JSON object)`);
|
|
992
|
+
}
|
|
981
993
|
if (parsed.version !== VAULT_FORMAT_VERSION) {
|
|
982
|
-
throw new Error(
|
|
994
|
+
throw new Error(`${this.path} has unsupported vault format version ${JSON.stringify(parsed.version)} (expected ${VAULT_FORMAT_VERSION})`);
|
|
983
995
|
}
|
|
984
996
|
if (!parsed.kdf || parsed.kdf.algo !== 'scrypt' || !parsed.kdf.saltHex) {
|
|
985
|
-
throw new Error(
|
|
997
|
+
throw new Error(`${this.path} is missing valid KDF parameters`);
|
|
986
998
|
}
|
|
987
999
|
if (!parsed.verify) {
|
|
988
|
-
throw new Error(
|
|
1000
|
+
throw new Error(`${this.path} is missing its password-verification envelope`);
|
|
989
1001
|
}
|
|
990
1002
|
return { file: parsed, created: false };
|
|
991
1003
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vault",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.74-rc.2",
|
|
4
4
|
"bin": {
|
|
5
|
-
"dsh-vault": "
|
|
5
|
+
"dsh-vault": "lib/cli.js"
|
|
6
6
|
},
|
|
7
7
|
"description": "Encrypted credential vault for DeepSeek Harness: store and retrieve usernames, emails, phone numbers, passwords, TOTP secrets, SSH/API-key/OAuth developer credentials through model tools and a Settings UI page.",
|
|
8
8
|
"type": "module",
|
|
@@ -88,6 +88,8 @@
|
|
|
88
88
|
"e2e:theme": "node tests/e2e/theme-check.mjs",
|
|
89
89
|
"e2e:polish": "node tests/e2e/polish-check.mjs",
|
|
90
90
|
"e2e:contrast": "node tests/e2e/contrast-audit.mjs",
|
|
91
|
-
"e2e:prompt": "node tests/e2e/cli-prompt.mjs"
|
|
91
|
+
"e2e:prompt": "node tests/e2e/cli-prompt.mjs",
|
|
92
|
+
"release:check": "node scripts/release-check.mjs",
|
|
93
|
+
"postpublish": "node scripts/release-check.mjs --version"
|
|
92
94
|
}
|
|
93
95
|
}
|