@marktrue/dsh-mobile-remote 0.0.0-stage → 3.2.1-alpha.1
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 +796 -0
- package/CONTRIBUTING.md +59 -0
- package/FAQ.md +111 -0
- package/LICENSE +21 -0
- package/README.md +255 -2
- package/cordis.patch.yml +14 -0
- package/docs/00-/345/274/200/345/217/221/346/200/273/347/272/262.md +122 -0
- package/docs/01-PRD.md +148 -0
- package/docs/02-architecture.md +249 -0
- package/docs/03-api.md +816 -0
- package/docs/04-security.md +96 -0
- package/docs/05-test-cases.md +326 -0
- package/docs/06-install-run.md +308 -0
- package/docs/07-user-manual.md +65 -0
- package/docs/08-/346/211/251/345/261/225/345/274/200/345/217/221/346/214/207/345/215/227.md +86 -0
- package/docs/09-compatibility.md +195 -0
- package/docs/10-release.md +927 -0
- package/docs/screenshots/01-home.png +0 -0
- package/docs/screenshots/02-notifications.png +0 -0
- package/docs/screenshots/03-sessions.png +0 -0
- package/docs/screenshots/04-chat.png +0 -0
- package/docs/screenshots/05-settings.png +0 -0
- package/docs/screenshots/06-new-session.png +0 -0
- package/docs/screenshots/pgy-appstore.jpg +0 -0
- package/docs/screenshots/pgy-mobile-app.jpg +0 -0
- package/docs/screenshots/pgy-pc-client.jpg +0 -0
- package/lib/account-usage.js +145 -0
- package/lib/app-alignment.js +112 -0
- package/lib/app-carrier-store.js +1972 -0
- package/lib/app-carrier.js +602 -0
- package/lib/client.js +706 -0
- package/lib/desktop-form.js +738 -0
- package/lib/endpoint-registry.js +162 -0
- package/lib/git-read-bundled-provider.js +255 -0
- package/lib/git-read-service.js +322 -0
- package/lib/index.js +9292 -0
- package/lib/plugin-content-digest.js +80 -0
- package/lib/plugin-self-update.js +758 -0
- package/lib/plugin-update-controller.js +95 -0
- package/lib/plugin-update-transaction.js +322 -0
- package/lib/plugin-update.js +309 -0
- package/lib/runtime-dependency-snapshot.js +345 -0
- package/lib/session-title-cache.js +123 -0
- package/lib/session-title-refresh.js +261 -0
- package/lib/update-fetch.js +24 -0
- package/lib/update-lock-arbiter.js +158 -0
- package/lib/update-supervisor.js +312 -0
- package/node_modules/ansi-regex/index.d.ts +37 -0
- package/node_modules/ansi-regex/index.js +10 -0
- package/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-regex/package.json +55 -0
- package/node_modules/ansi-regex/readme.md +78 -0
- package/node_modules/ansi-styles/index.d.ts +345 -0
- package/node_modules/ansi-styles/index.js +163 -0
- package/node_modules/ansi-styles/license +9 -0
- package/node_modules/ansi-styles/package.json +56 -0
- package/node_modules/ansi-styles/readme.md +152 -0
- package/node_modules/camelcase/index.d.ts +63 -0
- package/node_modules/camelcase/index.js +76 -0
- package/node_modules/camelcase/license +9 -0
- package/node_modules/camelcase/package.json +43 -0
- package/node_modules/camelcase/readme.md +99 -0
- package/node_modules/cliui/CHANGELOG.md +76 -0
- package/node_modules/cliui/LICENSE.txt +14 -0
- package/node_modules/cliui/README.md +115 -0
- package/node_modules/cliui/index.js +354 -0
- package/node_modules/cliui/package.json +65 -0
- package/node_modules/color-convert/CHANGELOG.md +54 -0
- package/node_modules/color-convert/LICENSE +21 -0
- package/node_modules/color-convert/README.md +68 -0
- package/node_modules/color-convert/conversions.js +839 -0
- package/node_modules/color-convert/index.js +81 -0
- package/node_modules/color-convert/package.json +48 -0
- package/node_modules/color-convert/route.js +97 -0
- package/node_modules/color-name/LICENSE +8 -0
- package/node_modules/color-name/README.md +11 -0
- package/node_modules/color-name/index.js +152 -0
- package/node_modules/color-name/package.json +28 -0
- package/node_modules/decamelize/index.js +13 -0
- package/node_modules/decamelize/license +21 -0
- package/node_modules/decamelize/package.json +38 -0
- package/node_modules/decamelize/readme.md +48 -0
- package/node_modules/dijkstrajs/.travis.yml +4 -0
- package/node_modules/dijkstrajs/CONTRIBUTING.md +8 -0
- package/node_modules/dijkstrajs/LICENSE.md +19 -0
- package/node_modules/dijkstrajs/README.md +18 -0
- package/node_modules/dijkstrajs/dijkstra.js +165 -0
- package/node_modules/dijkstrajs/package.json +60 -0
- package/node_modules/dijkstrajs/test/dijkstra.test.js +96 -0
- package/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
- package/node_modules/emoji-regex/README.md +73 -0
- package/node_modules/emoji-regex/es2015/index.js +6 -0
- package/node_modules/emoji-regex/es2015/text.js +6 -0
- package/node_modules/emoji-regex/index.d.ts +23 -0
- package/node_modules/emoji-regex/index.js +6 -0
- package/node_modules/emoji-regex/package.json +50 -0
- package/node_modules/emoji-regex/text.js +6 -0
- package/node_modules/find-up/index.d.ts +137 -0
- package/node_modules/find-up/index.js +89 -0
- package/node_modules/find-up/license +9 -0
- package/node_modules/find-up/package.json +53 -0
- package/node_modules/find-up/readme.md +156 -0
- package/node_modules/get-caller-file/LICENSE.md +6 -0
- package/node_modules/get-caller-file/README.md +41 -0
- package/node_modules/get-caller-file/index.d.ts +2 -0
- package/node_modules/get-caller-file/index.js +22 -0
- package/node_modules/get-caller-file/index.js.map +1 -0
- package/node_modules/get-caller-file/package.json +42 -0
- package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
- package/node_modules/is-fullwidth-code-point/index.js +50 -0
- package/node_modules/is-fullwidth-code-point/license +9 -0
- package/node_modules/is-fullwidth-code-point/package.json +42 -0
- package/node_modules/is-fullwidth-code-point/readme.md +39 -0
- package/node_modules/locate-path/index.d.ts +83 -0
- package/node_modules/locate-path/index.js +65 -0
- package/node_modules/locate-path/license +9 -0
- package/node_modules/locate-path/package.json +45 -0
- package/node_modules/locate-path/readme.md +122 -0
- package/node_modules/p-limit/index.d.ts +38 -0
- package/node_modules/p-limit/index.js +57 -0
- package/node_modules/p-limit/license +9 -0
- package/node_modules/p-limit/package.json +52 -0
- package/node_modules/p-limit/readme.md +101 -0
- package/node_modules/p-locate/index.d.ts +64 -0
- package/node_modules/p-locate/index.js +52 -0
- package/node_modules/p-locate/license +9 -0
- package/node_modules/p-locate/package.json +53 -0
- package/node_modules/p-locate/readme.md +90 -0
- package/node_modules/p-try/index.d.ts +39 -0
- package/node_modules/p-try/index.js +9 -0
- package/node_modules/p-try/license +9 -0
- package/node_modules/p-try/package.json +42 -0
- package/node_modules/p-try/readme.md +58 -0
- package/node_modules/path-exists/index.d.ts +28 -0
- package/node_modules/path-exists/index.js +23 -0
- package/node_modules/path-exists/license +9 -0
- package/node_modules/path-exists/package.json +39 -0
- package/node_modules/path-exists/readme.md +52 -0
- package/node_modules/pngjs/.eslintignore +2 -0
- package/node_modules/pngjs/.eslintrc.json +19 -0
- package/node_modules/pngjs/.prettierignore +3 -0
- package/node_modules/pngjs/LICENSE +20 -0
- package/node_modules/pngjs/README.md +429 -0
- package/node_modules/pngjs/browser.js +16096 -0
- package/node_modules/pngjs/coverage/lcov-report/base.css +354 -0
- package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +881 -0
- package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +554 -0
- package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +77 -0
- package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +647 -0
- package/node_modules/pngjs/coverage/lcov-report/constants.js.html +176 -0
- package/node_modules/pngjs/coverage/lcov-report/crc.js.html +200 -0
- package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
- package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +593 -0
- package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +152 -0
- package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +143 -0
- package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +611 -0
- package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +359 -0
- package/node_modules/pngjs/coverage/lcov-report/index.html +426 -0
- package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +365 -0
- package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +230 -0
- package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +248 -0
- package/node_modules/pngjs/coverage/lcov-report/packer.js.html +467 -0
- package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +128 -0
- package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +575 -0
- package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +404 -0
- package/node_modules/pngjs/coverage/lcov-report/parser.js.html +950 -0
- package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +116 -0
- package/node_modules/pngjs/coverage/lcov-report/png.js.html +662 -0
- package/node_modules/pngjs/coverage/lcov-report/prettify.css +101 -0
- package/node_modules/pngjs/coverage/lcov-report/prettify.js +1007 -0
- package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/node_modules/pngjs/coverage/lcov-report/sorter.js +169 -0
- package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +584 -0
- package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +215 -0
- package/node_modules/pngjs/coverage/lcov.info +2177 -0
- package/node_modules/pngjs/lib/bitmapper.js +267 -0
- package/node_modules/pngjs/lib/bitpacker.js +158 -0
- package/node_modules/pngjs/lib/chunkstream.js +189 -0
- package/node_modules/pngjs/lib/constants.js +32 -0
- package/node_modules/pngjs/lib/crc.js +40 -0
- package/node_modules/pngjs/lib/filter-pack.js +171 -0
- package/node_modules/pngjs/lib/filter-parse-async.js +24 -0
- package/node_modules/pngjs/lib/filter-parse-sync.js +21 -0
- package/node_modules/pngjs/lib/filter-parse.js +177 -0
- package/node_modules/pngjs/lib/format-normaliser.js +93 -0
- package/node_modules/pngjs/lib/interlace.js +95 -0
- package/node_modules/pngjs/lib/packer-async.js +50 -0
- package/node_modules/pngjs/lib/packer-sync.js +56 -0
- package/node_modules/pngjs/lib/packer.js +129 -0
- package/node_modules/pngjs/lib/paeth-predictor.js +16 -0
- package/node_modules/pngjs/lib/parser-async.js +165 -0
- package/node_modules/pngjs/lib/parser-sync.js +108 -0
- package/node_modules/pngjs/lib/parser.js +290 -0
- package/node_modules/pngjs/lib/png-sync.js +12 -0
- package/node_modules/pngjs/lib/png.js +194 -0
- package/node_modules/pngjs/lib/sync-inflate.js +168 -0
- package/node_modules/pngjs/lib/sync-reader.js +45 -0
- package/node_modules/pngjs/package.json +73 -0
- package/node_modules/qrcode/README.md +770 -0
- package/node_modules/qrcode/bin/qrcode +159 -0
- package/node_modules/qrcode/helper/to-sjis-browser.js +2 -0
- package/node_modules/qrcode/helper/to-sjis.js +105 -0
- package/node_modules/qrcode/lib/browser.js +76 -0
- package/node_modules/qrcode/lib/can-promise.js +7 -0
- package/node_modules/qrcode/lib/core/alignment-pattern.js +83 -0
- package/node_modules/qrcode/lib/core/alphanumeric-data.js +59 -0
- package/node_modules/qrcode/lib/core/bit-buffer.js +37 -0
- package/node_modules/qrcode/lib/core/bit-matrix.js +65 -0
- package/node_modules/qrcode/lib/core/byte-data.js +30 -0
- package/node_modules/qrcode/lib/core/error-correction-code.js +135 -0
- package/node_modules/qrcode/lib/core/error-correction-level.js +50 -0
- package/node_modules/qrcode/lib/core/finder-pattern.js +22 -0
- package/node_modules/qrcode/lib/core/format-info.js +29 -0
- package/node_modules/qrcode/lib/core/galois-field.js +69 -0
- package/node_modules/qrcode/lib/core/kanji-data.js +54 -0
- package/node_modules/qrcode/lib/core/mask-pattern.js +234 -0
- package/node_modules/qrcode/lib/core/mode.js +167 -0
- package/node_modules/qrcode/lib/core/numeric-data.js +43 -0
- package/node_modules/qrcode/lib/core/polynomial.js +62 -0
- package/node_modules/qrcode/lib/core/qrcode.js +495 -0
- package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +56 -0
- package/node_modules/qrcode/lib/core/regex.js +31 -0
- package/node_modules/qrcode/lib/core/segments.js +330 -0
- package/node_modules/qrcode/lib/core/utils.js +63 -0
- package/node_modules/qrcode/lib/core/version-check.js +9 -0
- package/node_modules/qrcode/lib/core/version.js +163 -0
- package/node_modules/qrcode/lib/index.js +12 -0
- package/node_modules/qrcode/lib/renderer/canvas.js +63 -0
- package/node_modules/qrcode/lib/renderer/png.js +78 -0
- package/node_modules/qrcode/lib/renderer/svg-tag.js +81 -0
- package/node_modules/qrcode/lib/renderer/svg.js +19 -0
- package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +85 -0
- package/node_modules/qrcode/lib/renderer/terminal/terminal.js +49 -0
- package/node_modules/qrcode/lib/renderer/terminal.js +9 -0
- package/node_modules/qrcode/lib/renderer/utf8.js +71 -0
- package/node_modules/qrcode/lib/renderer/utils.js +99 -0
- package/node_modules/qrcode/lib/server.js +138 -0
- package/node_modules/qrcode/license +10 -0
- package/node_modules/qrcode/package.json +77 -0
- package/node_modules/require-directory/.jshintrc +67 -0
- package/node_modules/require-directory/.npmignore +1 -0
- package/node_modules/require-directory/.travis.yml +3 -0
- package/node_modules/require-directory/LICENSE +22 -0
- package/node_modules/require-directory/README.markdown +184 -0
- package/node_modules/require-directory/index.js +86 -0
- package/node_modules/require-directory/package.json +40 -0
- package/node_modules/require-main-filename/CHANGELOG.md +26 -0
- package/node_modules/require-main-filename/LICENSE.txt +14 -0
- package/node_modules/require-main-filename/README.md +26 -0
- package/node_modules/require-main-filename/index.js +18 -0
- package/node_modules/require-main-filename/package.json +35 -0
- package/node_modules/set-blocking/CHANGELOG.md +26 -0
- package/node_modules/set-blocking/LICENSE.txt +14 -0
- package/node_modules/set-blocking/README.md +31 -0
- package/node_modules/set-blocking/index.js +7 -0
- package/node_modules/set-blocking/package.json +42 -0
- package/node_modules/string-width/index.d.ts +29 -0
- package/node_modules/string-width/index.js +47 -0
- package/node_modules/string-width/license +9 -0
- package/node_modules/string-width/package.json +56 -0
- package/node_modules/string-width/readme.md +50 -0
- package/node_modules/strip-ansi/index.d.ts +17 -0
- package/node_modules/strip-ansi/index.js +4 -0
- package/node_modules/strip-ansi/license +9 -0
- package/node_modules/strip-ansi/package.json +54 -0
- package/node_modules/strip-ansi/readme.md +46 -0
- package/node_modules/which-module/LICENSE +13 -0
- package/node_modules/which-module/README.md +58 -0
- package/node_modules/which-module/index.js +9 -0
- package/node_modules/which-module/package.json +41 -0
- package/node_modules/wrap-ansi/index.js +186 -0
- package/node_modules/wrap-ansi/license +9 -0
- package/node_modules/wrap-ansi/package.json +61 -0
- package/node_modules/wrap-ansi/readme.md +97 -0
- package/node_modules/y18n/CHANGELOG.md +35 -0
- package/node_modules/y18n/LICENSE +13 -0
- package/node_modules/y18n/README.md +109 -0
- package/node_modules/y18n/index.js +188 -0
- package/node_modules/y18n/package.json +39 -0
- package/node_modules/yargs/CHANGELOG.md +420 -0
- package/node_modules/yargs/LICENSE +21 -0
- package/node_modules/yargs/README.md +140 -0
- package/node_modules/yargs/build/lib/apply-extends.d.ts +2 -0
- package/node_modules/yargs/build/lib/apply-extends.js +65 -0
- package/node_modules/yargs/build/lib/argsert.d.ts +2 -0
- package/node_modules/yargs/build/lib/argsert.js +65 -0
- package/node_modules/yargs/build/lib/command.d.ts +64 -0
- package/node_modules/yargs/build/lib/command.js +416 -0
- package/node_modules/yargs/build/lib/common-types.d.ts +36 -0
- package/node_modules/yargs/build/lib/common-types.js +25 -0
- package/node_modules/yargs/build/lib/completion-templates.d.ts +2 -0
- package/node_modules/yargs/build/lib/completion-templates.js +50 -0
- package/node_modules/yargs/build/lib/completion.d.ts +21 -0
- package/node_modules/yargs/build/lib/completion.js +135 -0
- package/node_modules/yargs/build/lib/is-promise.d.ts +1 -0
- package/node_modules/yargs/build/lib/is-promise.js +9 -0
- package/node_modules/yargs/build/lib/levenshtein.d.ts +1 -0
- package/node_modules/yargs/build/lib/levenshtein.js +58 -0
- package/node_modules/yargs/build/lib/middleware.d.ts +10 -0
- package/node_modules/yargs/build/lib/middleware.js +57 -0
- package/node_modules/yargs/build/lib/obj-filter.d.ts +1 -0
- package/node_modules/yargs/build/lib/obj-filter.js +14 -0
- package/node_modules/yargs/build/lib/parse-command.d.ts +11 -0
- package/node_modules/yargs/build/lib/parse-command.js +36 -0
- package/node_modules/yargs/build/lib/process-argv.d.ts +2 -0
- package/node_modules/yargs/build/lib/process-argv.js +31 -0
- package/node_modules/yargs/build/lib/usage.d.ts +49 -0
- package/node_modules/yargs/build/lib/usage.js +540 -0
- package/node_modules/yargs/build/lib/validation.d.ts +34 -0
- package/node_modules/yargs/build/lib/validation.js +330 -0
- package/node_modules/yargs/build/lib/yargs.d.ts +274 -0
- package/node_modules/yargs/build/lib/yargs.js +1190 -0
- package/node_modules/yargs/build/lib/yerror.d.ts +4 -0
- package/node_modules/yargs/build/lib/yerror.js +11 -0
- package/node_modules/yargs/index.js +40 -0
- package/node_modules/yargs/locales/be.json +46 -0
- package/node_modules/yargs/locales/de.json +46 -0
- package/node_modules/yargs/locales/en.json +51 -0
- package/node_modules/yargs/locales/es.json +46 -0
- package/node_modules/yargs/locales/fi.json +49 -0
- package/node_modules/yargs/locales/fr.json +53 -0
- package/node_modules/yargs/locales/hi.json +49 -0
- package/node_modules/yargs/locales/hu.json +46 -0
- package/node_modules/yargs/locales/id.json +50 -0
- package/node_modules/yargs/locales/it.json +46 -0
- package/node_modules/yargs/locales/ja.json +51 -0
- package/node_modules/yargs/locales/ko.json +49 -0
- package/node_modules/yargs/locales/nb.json +44 -0
- package/node_modules/yargs/locales/nl.json +49 -0
- package/node_modules/yargs/locales/nn.json +44 -0
- package/node_modules/yargs/locales/pirate.json +13 -0
- package/node_modules/yargs/locales/pl.json +49 -0
- package/node_modules/yargs/locales/pt.json +45 -0
- package/node_modules/yargs/locales/pt_BR.json +48 -0
- package/node_modules/yargs/locales/ru.json +46 -0
- package/node_modules/yargs/locales/th.json +46 -0
- package/node_modules/yargs/locales/tr.json +48 -0
- package/node_modules/yargs/locales/zh_CN.json +48 -0
- package/node_modules/yargs/locales/zh_TW.json +47 -0
- package/node_modules/yargs/package.json +92 -0
- package/node_modules/yargs/yargs.js +14 -0
- package/node_modules/yargs-parser/CHANGELOG.md +601 -0
- package/node_modules/yargs-parser/LICENSE.txt +14 -0
- package/node_modules/yargs-parser/README.md +449 -0
- package/node_modules/yargs-parser/index.js +1032 -0
- package/node_modules/yargs-parser/lib/tokenize-arg-string.js +40 -0
- package/node_modules/yargs-parser/package.json +46 -0
- package/package.json +77 -4
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 04 安全设计文档 — dsh-mobile-remote
|
|
2
|
+
|
|
3
|
+
> 版本:v3.2.0 · 状态:已实现 · 配套:02-architecture.md、03-api.md、09-compatibility.md
|
|
4
|
+
|
|
5
|
+
## 1. 威胁模型
|
|
6
|
+
|
|
7
|
+
### 1.1 资产
|
|
8
|
+
|
|
9
|
+
| 资产 | 价值 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| agent 控制权 | 极高:agent 可执行 bash/pwsh(本机全权限 ≥ 远程代码执行) |
|
|
12
|
+
| 会话内容 | 高:对话、工具输出、工作区文件内容 |
|
|
13
|
+
| 访问口令 | 高:等于 agent 控制权(App 持有即已认证) |
|
|
14
|
+
|
|
15
|
+
### 1.2 攻击面与威胁
|
|
16
|
+
|
|
17
|
+
| 威胁 | 路径 | 前置条件 | 等级 |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| T1 局域网内任意设备驱动 agent | 系统监听 0.0.0.0 后,同网段设备直接访问 `/m/api/*` | 口令未启用 | **高** |
|
|
20
|
+
| T2 公网暴露 | 路由器 3080 端口映射/穿透到公网 | 用户违反约束 | **极高**(禁止项) |
|
|
21
|
+
| T3 口令爆破 | 反复携带口令访问 API | 口令启用 | 低(v2.6 起登录限流:10 次/60s → 429 + Retry-After) |
|
|
22
|
+
| T5 中间人窃听 | WiFi 被监听,明文 HTTP | 无 TLS | 中(见 §3) |
|
|
23
|
+
| T6 DNS 重绑定 | 攻击者域名解析到 127.0.0.1 后经浏览器访问 | 无 TLS | 低(Host 校验阻断,见 §5) |
|
|
24
|
+
|
|
25
|
+
### 1.3 攻击面缩减原则
|
|
26
|
+
1. **默认回环**:未配置 0.0.0.0 时,插件随 webserver 只监听 127.0.0.1,移动 API 物理不可达。
|
|
27
|
+
2. **通路分级**:可信 WiFi(家用)→ 允许 0.0.0.0 + 建议口令;外出 → 虚拟组网(蒲公英实测推荐 / Tailscale 思路,WireGuard 加密 + 设备认证);公网直连 → 禁止。
|
|
28
|
+
3. **口令即闸门**:启用口令后,全部移动 API(含 SSE)在建立连接时校验;任何绕过路径返回 401。
|
|
29
|
+
## 2. 身份鉴权方案
|
|
30
|
+
|
|
31
|
+
### 2.1 访问口令(推荐启用)
|
|
32
|
+
|
|
33
|
+
- 配置项 `authToken`(`cordis.patch.yml` 注入),**默认空 = 关闭**。
|
|
34
|
+
- App 每次请求携带 `X-Mobile-Token: <token>` 头;服务端常量时间比较(`crypto.timingSafeEqual`)。
|
|
35
|
+
- 失败统一 `401 auth-required`,避免枚举。
|
|
36
|
+
- 口令长度要求(文档约束):≥ 16 字符,用 `crypto.randomBytes(24).toString('base64url')` 生成。
|
|
37
|
+
- 口令比较为**常量时间**(sha256 定长化 + `timingSafeEqual`,v2.6 起消除长度侧信道)。
|
|
38
|
+
- **登录限流(v2.6)**:错误口令按来源 IP 计数,窗口内 ≥10 次 → `429 rate-limited` + `Retry-After`;认证成功重置计数;仅口令启用时生效(可配 `rateLimit.maxFailures/windowMs/blockMs`)。frp 等中继场景所有外部请求同源(中继 IP),阈值对单用户足够宽裕。
|
|
39
|
+
- 每台电脑使用**不同口令**;二维码含口令,泄露后立即更换(改配置 + 重启,旧码作废)。
|
|
40
|
+
### 2.2 Cookie 通道(兼容保留)
|
|
41
|
+
|
|
42
|
+
- 名称 `dsh_mobile_token`;`HttpOnly`、`SameSite=Lax`、`Path=/m`。v2.1 起无登录入口(网页版已移除),仅供旧客户端兼容,与请求头等价。
|
|
43
|
+
### 2.3 认证中间件实现要求
|
|
44
|
+
- 所有受保护端点统一走一个 `authorized(req)` 前置校验;禁止散落判断。
|
|
45
|
+
- 校验失败:`401` + 统一错误体;SSE 端点同样在 HTTP 头阶段拒绝(不建立流)。
|
|
46
|
+
## 3. 传输安全
|
|
47
|
+
|
|
48
|
+
| 通路 | 传输安全 | 说明 |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| 127.0.0.1 回环 | 本机可信 | 默认 |
|
|
51
|
+
| 局域网(可信 WiFi) | 无 TLS,明文 | 家用 WiFi 信任模型;口令启用时明文风险可接受 |
|
|
52
|
+
| 虚拟组网(蒲公英/Tailscale) | **WireGuard 端到端加密 + 设备身份** | 外出唯一通路(蒲公英国内可用,实测推荐) |
|
|
53
|
+
| HTTPS 反代(可选,v2.6 文档化) | TLS(受信任 CA / 手机安装内网 CA 根证书) | 不信任 WiFi / 公网反代场景;部署见 06-install-run §6b |
|
|
54
|
+
| 公网裸 HTTP | **禁止** | 运维约束:不映射端口、不做 NAT 穿透到 3080 |
|
|
55
|
+
|
|
56
|
+
明文路径下口令可能被同网段监听者截获——这是**明确的残余风险**,缓解手段:仅可信 WiFi 使用、口令启用、定期更换。
|
|
57
|
+
|
|
58
|
+
### 3b. LAN 桥(v3.0.0:桌面版局域网直连的官方通道)
|
|
59
|
+
> 背景:DSH Desktop(0.1.1-rc.2 起)强制 webserver 只听 `127.0.0.1`(`dsh-plugin-desktop` 的 DesktopWebServer 构造器对非回环 host 直接 throw,用户 patch 无法覆盖),手机无法直连桌面版。插件在 DSH 进程内自建第二个 HTTP 监听(`lanBridge`,默认 `0.0.0.0:3080`),把 `/m/api*` 流式转发到回环 webserver(SSE 透传)。
|
|
60
|
+
|
|
61
|
+
**这是新增攻击面,边界在代码中强制(`lib/index.js`「LAN 桥」段):**
|
|
62
|
+
1. **只转发移动 API 面**:`pathname.startsWith(/m/api)` 之外一律 404——桌面 `/api` RPC 网关、`/m/api/qr-config`(含口令)、`/m/qr.png`(仅本机语义)、一切其他 `/m/*` 面均不转发;Host 头重写为 `127.0.0.1:<webServer.port>` 使下游 hostAllowed 自然放行(真实鉴权仍由口令把关)。
|
|
63
|
+
2. **口令强制**:未配置 `authToken`、或口令短于 16 字符 → **拒绝启动桥**(仅警告不断言;schema 不设 min 以免破坏既有用户)并记日志。
|
|
64
|
+
3. **资源防护**:`maxConnections=128`、headersTimeout 15s、requestTimeout 60s、upstream 15s 超时、在网连接随插件卸载销毁。
|
|
65
|
+
4. **暴露条件**:桥默认关闭;开启即等于「0.0.0.0 移动 API 可达」——前置条件:可信 WiFi + ≥16 字符强口令(同 §2.1)。`/m/api/diagnostics` 的 `runtime.lanBridge.listening` 可确认监听状态(绑定失败时 QR/地址自动回退,不会指向死端口)。
|
|
66
|
+
5. **与 §3 表格关系**:桥上的流量走「局域网(可信 WiFi)明文」档位,残余风险与缓解同 §3 末。
|
|
67
|
+
|
|
68
|
+
## 4. 前端注入防护(XSS)
|
|
69
|
+
- App 为 Flutter 原生渲染(无 HTML 注入面);消息内链接仅 http/https 可点击(v2.6 起 scheme 白名单,`file:`/`intent:`/`tel:` 等一律渲染为纯文本)。
|
|
70
|
+
- 插件 API 返回的文本为纯 JSON(不含 HTML);桌面客户端模块(设置页二维码)仅渲染服务端返回的地址/口令文本。
|
|
71
|
+
## 5. Host 校验
|
|
72
|
+
|
|
73
|
+
- 移动 API 处理前校验 `Host` 头:必须匹配回环地址、本机任一面 internal IPv4(含 Tailscale),不匹配 → 403(阻断 DNS 重绑定,与 dsh /api 信任围栏同思路)。
|
|
74
|
+
- 名单在插件激活时以 `os.networkInterfaces()` 快照(过滤虚拟网卡:VMnet/vEthernet/代理虚拟网段)。
|
|
75
|
+
- `trustedHosts`(v2.4.2+):隧道/中继类方案(frp/SakuraFrp 托管)下请求 Host 是中继地址,需在此显式放行(配合强口令);纯虚拟组网/局域网无需配置。
|
|
76
|
+
## 6. 数据安全
|
|
77
|
+
|
|
78
|
+
- 插件**持久化仅限最小移动端状态**(`~/.dsh/mobile-remote/`:通知已读 id、会话最近活跃时间),不含会话内容;口令只存在于 profile 配置。
|
|
79
|
+
- 会话内容沿用 dsh 现有策略存储;插件仅在请求处理期间持有内存副本。
|
|
80
|
+
- **二维码数据端点 `/m/api/qr-config` 仅允许 loopback**(TCP socket 来源校验,无法伪造)—— 桌面设置页读取后展示二维码;二维码含地址+口令,请勿截屏转发。
|
|
81
|
+
- **问询/审批应答(`/m/api/respond`)不绕过内核安全**:插件只是把客户端 payload 转交 `apiProxy.respond`,答案内容(选项合法性、custom/selected 互斥、审批 outcome 枚举)全部由内核 schema 校验;rpcId 必须命中内核 pending 表(先到先得,不可伪造待答)。取消操作同样走内核 `ASK_CANCELLED` 语义。
|
|
82
|
+
- **第三方推送通道脱敏(v2.6)**:Server酱/ntfy/Bark/generic 等推送默认只收到「事件类型 + 会话短码」(`pushContent: minimal`),会话标题/错误详情等核心内容默认不出本机;仅显式配置 `pushContent: standard` 后外发——第三方服务不可信。
|
|
83
|
+
- **用量与额度投影(v3.2)**:`/m/api/account-usage` 只返回成功来源的余额/配额、脱敏 Codex 账户标签和汇总失败数;DeepSeek/OpenCode Go 密钥与 Codex OAuth token 永不进入响应、App 日志或持久化文件。OpenCode Go 只请求固定官方 HTTPS 端点;Codex 启用代理但代理不可用时拒绝直连。服务端只保留 60 秒进程内快照,单来源失败不回退到过期数据。
|
|
84
|
+
- **悬浮球 overlay 面板脱敏(v3.2)**:悬浮球是 **`TYPE_APPLICATION_OVERLAY` 系统级浮层**,可能出现在锁屏、他人可见、或覆盖其它应用之上。因此面板的用量与额度区块只展示来源标题、金额文字与配额细条/颜色;**不显示账户身份(displayName/maskedEmail)**、不显示任何聚合或换算数值;配额窗口永不相加。数据仅按需获取(展开面板时,客户端节流)、失败沿用旧值并标注相对时间,不新增后台轮询或持久化。
|
|
85
|
+
## 7. 安全测试要点(并入 05-test-cases.md)
|
|
86
|
+
1. 口令启用后:未认证访问 bootstrap/send/events/history 返回 401;错误口令 401。
|
|
87
|
+
2. 口令关闭时:以上端点返回 200。
|
|
88
|
+
3. Host 校验:伪造 Host 头 → 403。
|
|
89
|
+
4. `/m/api/qr-config`:非 loopback 来源 → 403;loopback → 200。
|
|
90
|
+
5. 系统监听 0.0.0.0 后,从另一台设备验证:同网段可访问(设计内行为);确认公网端口未开放。
|
|
91
|
+
6. LAN 桥:未配置口令/短于 16 字符 → `diagnostics.runtime.lanBridge.listening=false` 且日志有拒绝提示;配置正确 → listening=true,经桥访问 bootstrap 200、无口令 401、`/m/api/qr-config` 与 `/api/*` 一律 404(模拟环境若无法真机,用另一台同网设备 curl 验证)。
|
|
92
|
+
## 8. 运维约束(与 06-install-run.md 联动)
|
|
93
|
+
- 启用 0.0.0.0 前确认网络为可信 WiFi;离开可信网络前确认服务未在公网可达。
|
|
94
|
+
- 建议(非强制)在 profile 配置中启用口令;口令变更 = 改配置 + 重启 dsh。
|
|
95
|
+
- 虚拟组网为外出标准姿势(蒲公英国内实测推荐;Tailscale 国内控制面不可达);纯组网方案插件侧无需额外配置,隧道/中继类需 `trustedHosts` 放行 + 强口令。
|
|
96
|
+
- `authToken` 必须启用(v2.6 起启动日志与桌面设置页对「认证未启用」显式警示);不信任的 WiFi(酒店/咖啡馆)下优先 HTTPS 反代(06-install-run §6b)。
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# 05 测试用例设计文档 — dsh-mobile-remote
|
|
2
|
+
|
|
3
|
+
> 版本:v3.2.0(用量与额度见 F-24~F-32,对话时间线见 F-33~F-35;v3.1.1 用例见 F-23) · 配套:03-api.md、04-security.md
|
|
4
|
+
> 环境:Windows + DSH Desktop(desktop profile,内核 0.1.1-rc.2;web profile 亦适用) + Android(DSH Remote App)
|
|
5
|
+
> 前置:插件已安装并启用(LAN 桥监听 0.0.0.0:3080);访问口令为安装时生成的随机串(下文 `<TOKEN>`)。
|
|
6
|
+
## 1. 测试范围与环境
|
|
7
|
+
- 功能:认证、发消息、事件回流、历史、会话、通知、新建会话、目录、默认配置、二维码。
|
|
8
|
+
- 安全:口令校验、Host 校验、loopback 限制。
|
|
9
|
+
- 兼容:Android 深色/浅色主题。
|
|
10
|
+
- 自动化:`tools/e2e-check.mjs`(Node ≥ 20,`DSH_MOBILE_TOKEN` 环境变量)覆盖核心 API 链路。
|
|
11
|
+
## 2. 功能测试用例
|
|
12
|
+
|
|
13
|
+
### F-01 认证:未携带凭证访问 API
|
|
14
|
+
|
|
15
|
+
| 项目 | 内容 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| 步骤 | 不带凭证请求 `GET /m/api/bootstrap` |
|
|
18
|
+
| 预期 | 401 `{"error":"auth-required"}` |
|
|
19
|
+
|
|
20
|
+
### F-02 认证:错误口令
|
|
21
|
+
| 项目 | 内容 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| 步骤 | 携带错误 token 请求 `GET /m/api/bootstrap` |
|
|
24
|
+
| 预期 | 401 `{"error":"auth-required"}` |
|
|
25
|
+
|
|
26
|
+
### F-03 认证:正确口令
|
|
27
|
+
| 项目 | 内容 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| 步骤 | 携带正确 token(`X-Mobile-Token` 头)请求 `GET /m/api/bootstrap` |
|
|
30
|
+
| 预期 | 200 `{"ok":true}` |
|
|
31
|
+
|
|
32
|
+
### F-04 bootstrap 状态
|
|
33
|
+
| 项目 | 内容 |
|
|
34
|
+
|---|---|
|
|
35
|
+
| 步骤 | 带 token 请求 `/m/api/bootstrap` |
|
|
36
|
+
| 预期 | 200;`auth.enabled=true`;`server.urls` 含局域网 IPv4 与 127.0.0.1;`agents` 含运行中会话;`sessions` 非空 |
|
|
37
|
+
|
|
38
|
+
### F-05 发消息(端到端,自动化覆盖)
|
|
39
|
+
|
|
40
|
+
| 项目 | 内容 |
|
|
41
|
+
|---|---|
|
|
42
|
+
| 步骤 | 连 SSE(带 token)→ `POST /m/api/send {sessionId, text}` |
|
|
43
|
+
| 预期 | send 200 `{ok, agentId, messageId}`;SSE 收到 `session/event` 帧(user/message → assistant/message) |
|
|
44
|
+
| 实测 | ✅ 端到端通过(App 发送 → 会话收到 → 回复回流) |
|
|
45
|
+
|
|
46
|
+
### F-06 发消息:空文本
|
|
47
|
+
| 项目 | 内容 |
|
|
48
|
+
|---|---|
|
|
49
|
+
| 步骤 | `POST /m/api/send {text:""}` |
|
|
50
|
+
| 预期 | 400 `{"error":"empty-text"}` |
|
|
51
|
+
|
|
52
|
+
### F-07 发消息:无运行中 agent
|
|
53
|
+
|
|
54
|
+
| 项目 | 内容 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| 前置 | 电脑端无任何会话 |
|
|
57
|
+
| 步骤 | `POST /m/api/send`(不带 sessionId) |
|
|
58
|
+
| 预期 | 503 `{"error":"no-live-agent"}` |
|
|
59
|
+
|
|
60
|
+
### F-08 历史加载
|
|
61
|
+
|
|
62
|
+
| 项目 | 内容 |
|
|
63
|
+
|---|---|
|
|
64
|
+
| 步骤 | `GET /m/api/history?sessionId=<id>&after=0&limit=200` |
|
|
65
|
+
| 预期 | 200;事件按 seq 升序,摘要格式与 SSE 一致 |
|
|
66
|
+
|
|
67
|
+
### F-09 新建会话
|
|
68
|
+
|
|
69
|
+
| 项目 | 内容 |
|
|
70
|
+
|---|---|
|
|
71
|
+
| 步骤 | `POST /m/api/sessions {preset, cwd}` |
|
|
72
|
+
| 预期 | 200 `{sessionId}`;PC 端会话列表出现;cwd 在工作区子目录时自动归属该工作区 |
|
|
73
|
+
|
|
74
|
+
### F-10 通知与已读
|
|
75
|
+
| 项目 | 内容 |
|
|
76
|
+
|---|---|
|
|
77
|
+
| 步骤 | 任务完成 → `GET /m/api/notifications` → `POST /m/api/notifications/read` |
|
|
78
|
+
| 预期 | 通知出现且同会话同类型聚合为一条;标记已读后 unread 归零 |
|
|
79
|
+
|
|
80
|
+
### F-11 默认配置修改
|
|
81
|
+
|
|
82
|
+
| 项目 | 内容 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| 步骤 | `POST /m/api/defaults {agentPreset:"code"}` |
|
|
85
|
+
| 预期 | 200;`GET /m/api/catalog` 的 defaults.agentPreset 变更为 code |
|
|
86
|
+
|
|
87
|
+
### F-12 二维码端点
|
|
88
|
+
| 项目 | 内容 |
|
|
89
|
+
|---|---|
|
|
90
|
+
| 步骤 | `GET /m/qr.png?text=<url>` |
|
|
91
|
+
| 预期 | 返回 `image/png` |
|
|
92
|
+
|
|
93
|
+
### F-13 桌面二维码数据(qr-config)
|
|
94
|
+
| 项目 | 内容 |
|
|
95
|
+
|---|---|
|
|
96
|
+
| 步骤 | loopback 请求 `GET /m/api/qr-config` |
|
|
97
|
+
| 预期 | 200 `{urls, token, path}`;非 loopback → 403 |
|
|
98
|
+
|
|
99
|
+
### F-14 目录浏览
|
|
100
|
+
|
|
101
|
+
| 项目 | 内容 |
|
|
102
|
+
|---|---|
|
|
103
|
+
| 步骤 | `GET /m/api/directories?path=` → 盘符;`path=F:\` → 子目录;`POST /m/api/directories {path,name}` |
|
|
104
|
+
| 预期 | 盘符/子目录列表正确;新建文件夹成功 |
|
|
105
|
+
|
|
106
|
+
### F-15 问询弹窗(端到端,v2.3)
|
|
107
|
+
|
|
108
|
+
| 项目 | 内容 |
|
|
109
|
+
|---|---|
|
|
110
|
+
| 前置 | 桌面端已重启(加载弹窗桥);App 诊断 `respondBridge`/`frameBridge` ✅;PC 与手机同时打开同一会话 |
|
|
111
|
+
| 步骤 | 发指令触发 `ask_user_question`(带选项)→ 观察两端弹窗 → 手机选选项提交 |
|
|
112
|
+
| 预期 | ① 两端同时弹卡片;② 提交后 agent 收到答案(PC 端可见答案生效)、两端卡片同步消失 |
|
|
113
|
+
| 变体 A | 手机输入自定义答案提交(单选语义:选项与自定义二选一) |
|
|
114
|
+
| 变体 B | 手机点 ✕ → 卡片即时消失,agent 收到取消(`ASK_CANCELLED`) |
|
|
115
|
+
| 变体 C | PC 端先答 → 手机卡片同步消失;手机后答提示"可能电脑端已先回答" |
|
|
116
|
+
| 变体 D | 断网期间产生问询 → App 重连后补发弹窗(pendingFrames 回放) |
|
|
117
|
+
|
|
118
|
+
### F-16 权限审批弹窗(端到端,v2.3)
|
|
119
|
+
|
|
120
|
+
| 项目 | 内容 |
|
|
121
|
+
|---|---|
|
|
122
|
+
| 前置 | 会话权限预设 Workspace Write、审批策略「询问」 |
|
|
123
|
+
| 步骤 | 指令触发工作区外写文件 → 两端弹「权限请求」→ 手机点「允许一次」 |
|
|
124
|
+
| 预期 | ① 两端同时弹;② 允许后操作继续(文件写入成功)、卡片消失 |
|
|
125
|
+
| 变体 A | 点「拒绝」→ 操作被拒(agent 收到拒绝结果)、卡片消失 |
|
|
126
|
+
| 变体 B | 策略「从不询问」→ 不弹窗(内核直接按预设处理),无报错 |
|
|
127
|
+
|
|
128
|
+
### F-17 通知删除
|
|
129
|
+
|
|
130
|
+
| 项目 | 内容 |
|
|
131
|
+
|---|---|
|
|
132
|
+
| 步骤 | 通知页长按单删 / 垃圾桶批量多选 / 清空全部;随后让 agent 再完成一轮任务 |
|
|
133
|
+
| 预期 | ① 删除后列表与角标即时刷新(SSE `notifications/changed`);② 新事件仍正常产生新通知(删除≠静音);③ PC 端通知中心不受影响 |
|
|
134
|
+
|
|
135
|
+
### F-18 登录限流(v2.6,端到端)
|
|
136
|
+
|
|
137
|
+
| 项目 | 内容 |
|
|
138
|
+
|---|---|
|
|
139
|
+
| 前置 | `authToken` 已启用 |
|
|
140
|
+
| 步骤 | 连续错误口令请求 `/m/api/bootstrap`(错误 token) |
|
|
141
|
+
| 预期 | 阈值(10 次/60s)内 401;超限后 `429 { "error": "rate-limited" }` + `Retry-After` 头;窗口过后自动恢复;正确口令成功后计数重置 |
|
|
142
|
+
| 变体 A | `authToken` 未启用(留空)时无限流,按原语义返回 |
|
|
143
|
+
| 说明 | `tools/e2e-check.mjs` 末尾已含自动化断言(封锁后本机 IP 60s 内 429,故段位置于脚本最后) |
|
|
144
|
+
|
|
145
|
+
### F-19 链接 scheme 白名单(v2.6,App 单元测试)
|
|
146
|
+
|
|
147
|
+
| 项目 | 内容 |
|
|
148
|
+
|---|---|
|
|
149
|
+
| 步骤 | `flutter test test/md_link_test.dart` |
|
|
150
|
+
| 预期 | http/https 放行;`file:`/`intent:`/`tel:`/`javascript:`/`data:` 与解析失败/空串一律返回 null(渲染为纯文本不可点击) |
|
|
151
|
+
|
|
152
|
+
### F-20 思维链字段(reasoning,v3.1.0 候选,端到端)
|
|
153
|
+
|
|
154
|
+
| 项目 | 内容 |
|
|
155
|
+
|---|---|
|
|
156
|
+
| 前置 | 插件与 App 均为 v3.1.0 代码(assistant/message 摘要含 `reasoning`;App 渲染折叠块) |
|
|
157
|
+
| 步骤 | 连 SSE → 让 agent 回复一条带思考过程的消息(任何任务即可)→ 观察 assistant/message 摘要与 App 渲染 |
|
|
158
|
+
| 预期 | ① 摘要 `reasoning` = 全部 reasoning 类型 content 块文本拼接,≤20000 字符(超长截断并附「已截断」);② 无 reasoning 块时**不发送**该字段;③ 流式与历史摘要均携带;④ App 在消息上方渲染可折叠「思维链」块(图标+字数+箭头),默认状态跟随设置「思维链默认展开」,单条消息独立切换,手动状态按消息持久化 |
|
|
159
|
+
| 变体 A | 思维链 >20000 字符:截断显示,无乱码/异常 |
|
|
160
|
+
| 变体 B | 旧版 App(≤v3.0.0)接收带 reasoning 摘要:忽略字段,按无折叠块的历史样式显示 |
|
|
161
|
+
| 实测 | ✅ 端到端通过(真机:折叠块渲染/切换/持久化;logcat 无异常) |
|
|
162
|
+
|
|
163
|
+
### F-21 会话标题字段(title,v3.1.0 候选,端到端)
|
|
164
|
+
|
|
165
|
+
| 项目 | 内容 |
|
|
166
|
+
|---|---|
|
|
167
|
+
| 步骤 | 带 token 请求 `/m/api/bootstrap` → 检查 agents/sessions 条目;App 回桌面点悬浮球看「运行中的会话」面板 |
|
|
168
|
+
| 预期 | ① agents/sessions 条目含 `title`(`sessionTitleOf ?? shortSessionId` 兜底);② 悬浮球面板显示会话标题(超宽省略号截断),而非 session id 短码;③ 旧版插件(无 title)时 App 回退显示 id 短码 |
|
|
169
|
+
| 实测 | ✅ 端到端通过(真机:面板显示会话标题) |
|
|
170
|
+
|
|
171
|
+
### F-22 思维链折叠状态持久化(v3.1.0 候选,App 真机)
|
|
172
|
+
|
|
173
|
+
| 项目 | 内容 |
|
|
174
|
+
|---|---|
|
|
175
|
+
| 步骤 | 设置「思维链默认展开」任意态 → 手动折叠某条消息的思维链块 → ① 列表滚动离开再滚回 ② 退出会话重新进入 ③ 杀进程重启 |
|
|
176
|
+
| 预期 | 手动折叠/展开状态保持(按会话+消息 key 持久化在 `dsh_mr_reasoning_overrides`,每会话软上限 100 条);未手动切换的消息跟随设置默认值 |
|
|
177
|
+
| 实测 | ✅ 真机全部通过(滚动/重进/重启) |
|
|
178
|
+
|
|
179
|
+
### F-23 WSL/类 Unix 路径选择(v3.1.1,issue #5,单测)
|
|
180
|
+
|
|
181
|
+
| 项目 | 内容 |
|
|
182
|
+
|---|---|
|
|
183
|
+
| 前置 | 插件 v3.1.1 + App v3.1.1+,服务端运行在 WSL/Linux |
|
|
184
|
+
| 步骤 | App 新建会话 → 工作目录 → 根视图 → 进入 `/home` 逐级进入真实目录 → 「选这里」→ 创建会话 |
|
|
185
|
+
| 预期 | ① 根视图(POSIX)显示「根目录」、根项 `/`;进入 `home` 后路径为 `/home`(**回归:旧版拼成 `/\home` 并报「读取失败」**);② 目录可逐级浏览、可新建文件夹;③ 新建会话 cwd 为所选真实路径;④ 「已注册工作区」显示服务端原始路径(非 `\home\user` 归一形态),点选直接可用 |
|
|
186
|
+
| 变体 A | Windows 服务端回归:根视图盘符、`C:\` 逐级浏览、新建文件夹、cwd 与 v3.1.0 行为一致 |
|
|
187
|
+
| 变体 B | 旧版 App(≤v3.0.0)+ 新版插件:浏览路径为 `/\home` 形态,服务端归一化后仍可正常进入/选择(`//home` 在 POSIX 与 `/home` 等价) |
|
|
188
|
+
| 单测 | `flutter test test/dirpicker_logic_test.dart`(joinDirPath/dirSepOf);`node tools/wsl-path-check.mjs`(normalizeServerPath,8/8) |
|
|
189
|
+
|
|
190
|
+
### F-24 用量与额度投影:DeepSeek(#18 用量与额度)
|
|
191
|
+
|
|
192
|
+
| 项目 | 内容 |
|
|
193
|
+
|---|---|
|
|
194
|
+
| 前置 | 电脑端配置 `DEEPSEEK_API_KEY`,插件已重启 |
|
|
195
|
+
| 步骤 | 带 token 请求 `GET /m/api/account-usage`,打开 App 设置 → 账户 → 用量与额度 |
|
|
196
|
+
| 预期 | 200;`sources` 含 `deepseek` 的 CNY 金额;App 卡片显示金额;key 不出现在响应、日志或页面 |
|
|
197
|
+
|
|
198
|
+
### F-25 Codex Connect 当前活动账户(#18 用量与额度)
|
|
199
|
+
|
|
200
|
+
| 项目 | 内容 |
|
|
201
|
+
|---|---|
|
|
202
|
+
| 前置 | 安装并登录 `dsh-codex-connect`,保存至少一个 Codex 账户 |
|
|
203
|
+
| 步骤 | 请求 `/m/api/account-usage`,切换 dsh-codex-connect 活动账户后再次刷新 |
|
|
204
|
+
| 预期 | `sources` 含 `codex`;仅显示当前活动账户的 displayName/maskedEmail 与有效主/附加配额窗口、Credits/个人上限(若接口返回);不返回 OAuth token;活动账户切换后下一次刷新跟随新账户 |
|
|
205
|
+
|
|
206
|
+
### F-26 OpenCode Go 套餐窗口(#18 用量与额度)
|
|
207
|
+
|
|
208
|
+
| 项目 | 内容 |
|
|
209
|
+
|---|---|
|
|
210
|
+
| 前置 | DSH 凭据配置 `OPENCODE_GO_API_KEY`,且账号有 OpenCode Go 套餐 |
|
|
211
|
+
| 步骤 | 请求 `/m/api/account-usage`,检查 App 详情卡片 |
|
|
212
|
+
| 预期 | `sources` 含 `opencode-go`;rolling/weekly/monthly 的原始已用百分比转换为剩余百分比;`rate-limited` 窗口保留为 0% 并标记限流;percent=0 的占位重置时间不显示;其它有效重置时间按手机本地时间显示 |
|
|
213
|
+
|
|
214
|
+
### F-27 部分来源失败与空状态(#18 用量与额度)
|
|
215
|
+
|
|
216
|
+
| 项目 | 内容 |
|
|
217
|
+
|---|---|
|
|
218
|
+
| 步骤 | 让一个已配置来源返回 401/网络失败,另一个来源保持可用;再分别测试所有来源均未配置 |
|
|
219
|
+
| 预期 | 成功来源仍显示;失败来源从 `sources` 隐藏且 `failedCount` 增加,App 显示汇总提示;全部未配置时 `sources=[]`,App 保留入口并显示电脑端配置引导 |
|
|
220
|
+
|
|
221
|
+
### F-28 缓存、并发与手动刷新(#18 用量与额度)
|
|
222
|
+
|
|
223
|
+
| 项目 | 内容 |
|
|
224
|
+
|---|---|
|
|
225
|
+
| 步骤 | 快速连续进入设置和详情页;点击详情页顶部刷新;观察服务端上游请求与 App 显示 |
|
|
226
|
+
| 预期 | 普通进入请求复用 60 秒成功快照且并发请求共享一轮上游查询;手动刷新请求 `?refresh=1` 并发查询全部来源;不产生本地持久化额度文件,不周期轮询上游 |
|
|
227
|
+
|
|
228
|
+
### F-29 悬浮球面板三来源区块(#18 用量与额度)
|
|
229
|
+
|
|
230
|
+
| 项目 | 内容 |
|
|
231
|
+
|---|---|
|
|
232
|
+
| 前置 | 插件 v3.2+(含 `/account-usage`)、App 开启悬浮球,DeepSeek / Codex / OpenCode Go 至少各一个来源可用 |
|
|
233
|
+
| 步骤 | 展开悬浮球面板,等待用量与额度区块渲染 |
|
|
234
|
+
| 预期 | 出现「用量与额度」区块(含「详情 ▸」);每来源一行:DeepSeek 出金额文字(CNY ¥),Codex / OpenCode Go 配额行每个进度条上方居中显示窗口短标签(Codex:5h / 每周;OpenCode Go:5h / 每周 / 每月),条只出细条与颜色、不出百分比数字;主 bucket 窗口按 5h → 周 → 月排列;金额行在低余额时变红;点击区块或「详情 ▸」打开 App 用量页;底部「去充值」保留 |
|
|
235
|
+
|
|
236
|
+
### F-30 悬浮球展开时按需获取与节流(#18 用量与额度)
|
|
237
|
+
|
|
238
|
+
| 项目 | 内容 |
|
|
239
|
+
|---|---|
|
|
240
|
+
| 步骤 | 反复快速展开/收起悬浮球面板;观察服务端 account-usage 调用次数 |
|
|
241
|
+
| 预期 | 每次展开最多触发一次查询;2 分钟节流窗内重复展开复用在途/缓存结果,不反复打上游;首次展开区块先显示「查询中…」再异步就地更新;App 被杀后展开面板仍能拉到数据 |
|
|
242
|
+
|
|
243
|
+
### F-31 悬浮球面板降级与陈值(#18 用量与额度)
|
|
244
|
+
|
|
245
|
+
| 项目 | 内容 |
|
|
246
|
+
|---|---|
|
|
247
|
+
| 前置 | 场景 A:旧插件(无 `/account-usage`,404);场景 B:全部来源未配置;场景 C:单次拉取失败 |
|
|
248
|
+
| 步骤 | 展开面板,分别观察 A / B / C |
|
|
249
|
+
| 预期 | A/B/C 下区块整体不出现、不显示任何报错,原有单行余额原位显示且点击仍=去充值(A 不退化现有功能);C 曾成功过 → 区块保留旧值并以小灰字标注相对时间(超过 10 分钟才标注);C 从未成功过 → 退回单行余额;不产生本地持久化额度文件 |
|
|
250
|
+
|
|
251
|
+
### F-32 悬浮球面板隐私边界(#18 用量与额度)
|
|
252
|
+
|
|
253
|
+
| 项目 | 内容 |
|
|
254
|
+
|---|---|
|
|
255
|
+
| 步骤 | 展开面板,检查区块、球体与操作说明弹窗 |
|
|
256
|
+
| 预期 | 区块不显示 Codex 账户身份(displayName/maskedEmail);球体自身不常驻金额;配额窗口与来源永不相加、不换算;附加 Codex bucket(服务端命名「名称 · 5h」)不出现在面板;设置 → 悬浮球操作说明的面板内容已包含「用量与额度」 |
|
|
257
|
+
|
|
258
|
+
### F-33 对话时间线能力与历史/实时一致(PR #24 时间线)
|
|
259
|
+
|
|
260
|
+
| 项目 | 内容 |
|
|
261
|
+
|---|---|
|
|
262
|
+
| 前置 | 插件返回 `capabilities.eventTimeline`;准备 user、assistant、tool/call、tool/result、unknown Visible event 与长参数/结果 |
|
|
263
|
+
| 步骤 | App 连接 SSE → 记录实时事件;断线后请求 `/history`;展开工具卡与未知事件详情 |
|
|
264
|
+
| 预期 | 事件顺序和 durable seq 一致;工具按 callId 合并且显示完整生命周期;长详情按需加载;未知事件通用卡可展开;历史分页 `hasMore` 可补齐所有缺口 |
|
|
265
|
+
| 变体 A | 旧插件无 capability / 无详情端点 |
|
|
266
|
+
| 预期 | 保留旧摘要与聊天;详情显示“不可用”,不伪造数据 |
|
|
267
|
+
| 变体 B | 手机离线后展开详情 |
|
|
268
|
+
| 预期 | 已缓存摘要保留;详情显示不可用并可在恢复连接后重试 |
|
|
269
|
+
| 单测 | `node tools/timeline-contract-check.mjs`(未知/内部事件过滤、详情指针、fail-closed allow-list、llm-request 不外泄、`/event-detail` 鉴权与身份校验、bootstrap agentId→sessionId,75/75);`flutter test test/timeline_test.dart`(reducer 合并规则:tool/call 替换 delta 参数、锚点/detail seq 收敛、可见性分类)|
|
|
270
|
+
|
|
271
|
+
### F-34 普通/调试模式与富内容(PR #24 时间线)
|
|
272
|
+
|
|
273
|
+
| 项目 | 内容 |
|
|
274
|
+
|---|---|
|
|
275
|
+
| 步骤 | 普通模式查看工具、注入事件、图片/Markdown/附件;切换调试模式逐条展开 |
|
|
276
|
+
| 预期 | 普通模式摘要且隐藏选定 runtime 注入与协议元数据(`session/title`、`model/selection`、`feedback/*` 等);调试模式显示协议元数据、未知事件、原始工具 IO;图片/Markdown 可预览;**工具与 assistant 产出的文件不展示**(issue #1 需求变更,普通与调试模式都不出现;要看原始载荷走「查看原始事件」);用户自己的附件只显示文件名/类型/大小、**不提供下载**;系统提示词、请求快照与压缩摘要永不显示 |
|
|
277
|
+
| 变体 | 并行同名工具、失败工具、已解决的审批、todo/Job 状态、压缩前事件、流式工具参数(delta → tool/call)|
|
|
278
|
+
| 预期 | 各自按 callId/事件 seq 保持独立;工具参数以 `tool/call` 的完整实参为准(不得出现 delta 与整串拼接);审批走 durable `approval/asked`/`approval/decided` 可历史回放,问询只有瞬态帧(重进会话不保证回放);明确失败卡片普通模式默认收起,调试模式默认展开并自动加载详情;用户手动展开/收起优先于自动规则,覆盖仅在当前会话页面有效 |
|
|
279
|
+
| 变体 | 带思维链的 assistant 消息;详情无正文增量 |
|
|
280
|
+
| 预期 | 正文提取口径与服务端摘要同源(`blocksToText`):原始事件/详情正文**不得包含 `reasoning` 块文本**(思维链只在折叠块出现一次,不因加载详情而重复);普通模式仅当 `detail.textChars` 大于当前可见正文长度(确有增量)时才显示加载入口,相等时不显示;调试模式入口文案为「查看原始事件」(不再叫「加载完整正文」);单测 `flutter test test/timeline_test.dart` 覆盖 `timelineDetailText`(不从 `content` 拼接 reasoning)与 `timelineHasTextIncrement`;契约 `tools/timeline-contract-check.mjs` 覆盖摘要/详情的正文口径与 `textChars` 一致性 |
|
|
281
|
+
|
|
282
|
+
> 边界说明:问询(`question/requested`)在核心里只有瞬态远程帧、没有 durable 事件,因此**不保证**历史回放;审批有 durable 事件,重进会话仍可见。
|
|
283
|
+
|
|
284
|
+
### F-35 断线多页 catch-up 与滚动锚点(PR #24 时间线)
|
|
285
|
+
|
|
286
|
+
| 项目 | 内容 |
|
|
287
|
+
|---|---|
|
|
288
|
+
| 步骤 | 生成超过 100 条未读 Visible event,断开 SSE,恢复连接并向上翻历史 |
|
|
289
|
+
| 预期 | catch-up 持续读取 `hasMore` 直到 durable cursor 收敛;重复帧不重复渲染;加载更早事件不改变当前 viewport 锚点 |
|
|
290
|
+
|
|
291
|
+
## 3. 安全测试用例
|
|
292
|
+
|
|
293
|
+
### S-01 Host 校验
|
|
294
|
+
|
|
295
|
+
| 项目 | 内容 |
|
|
296
|
+
|---|---|
|
|
297
|
+
| 步骤 | 请求带 `Host: evil.example.com` 访问 `/m/api/bootstrap`(带有效 token) |
|
|
298
|
+
| 预期 | 403 `{"error":"host-not-allowed"}` |
|
|
299
|
+
|
|
300
|
+
### S-02 qr-config loopback 限制
|
|
301
|
+
|
|
302
|
+
| 项目 | 内容 |
|
|
303
|
+
|---|---|
|
|
304
|
+
| 步骤 | 非 loopback 来源请求 `GET /m/api/qr-config` |
|
|
305
|
+
| 预期 | 403 `{"error":"loopback-only"}` |
|
|
306
|
+
|
|
307
|
+
### S-03 口令关闭模式
|
|
308
|
+
|
|
309
|
+
| 项目 | 内容 |
|
|
310
|
+
|---|---|
|
|
311
|
+
| 前置 | patch 将 `authToken` 置空并重启 |
|
|
312
|
+
| 步骤 | 无凭证访问 `/m/api/bootstrap` |
|
|
313
|
+
| 预期 | 200(无认证) |
|
|
314
|
+
|
|
315
|
+
### S-04 监听范围
|
|
316
|
+
|
|
317
|
+
| 项目 | 内容 |
|
|
318
|
+
|---|---|
|
|
319
|
+
| 步骤 | `netstat`/`Get-NetTCPConnection` 查看 3080 监听地址 |
|
|
320
|
+
| 预期 | `0.0.0.0:3080`(LAN 桥启用,局域网/虚拟组网可访问);确认公网端口未开放 |
|
|
321
|
+
|
|
322
|
+
## 4. 回归执行建议
|
|
323
|
+
|
|
324
|
+
- 每次修改插件源码后:`cd C:\Users\<用户>\.dsh\profiles\desktop && corepack pnpm install`(同步 file: 副本)→ 重启 DSH Desktop → 跑 `tools/e2e-check.mjs` 与 `node tools/account-usage-check.mjs` → 手机 App 冒烟(连接/发消息/通知/新建会话/图片发送/用量与额度)。
|
|
325
|
+
- 修改 App 后:`flutter analyze` → `flutter test`(新增 `test/usage_model_test.dart`;另含 `test/api_logic_test.dart`:多地址合并/轮换、回环与链路本地排除,5 用例;`test/md_link_test.dart`:链接 scheme 白名单)→ `flutter build apk --release` → 覆盖安装。
|
|
326
|
+
- 修改原生悬浮球后:`cd dsh-mobile-app/android && ./gradlew :app:testDebugUnitTest --tests "com.dsh.remote.UsagePanelModelTest"`(面板模型 seam 纯 JVM 单测:节流/降级/主 bucket/分档/过期/CNY)→ 真机冒烟(F-29~F-32:三来源区块、展开节流、降级与陈值标注、隐私边界)。
|