@logictan/dsh-config-manager 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/cordis.patch.yml +21 -0
- package/lib/adapters/agent-instructions.d.ts +13 -0
- package/lib/adapters/agent-instructions.js +46 -0
- package/lib/adapters/agent-presets.d.ts +13 -0
- package/lib/adapters/agent-presets.js +14 -0
- package/lib/adapters/credentials.d.ts +37 -0
- package/lib/adapters/credentials.js +122 -0
- package/lib/adapters/file-collection.d.ts +15 -0
- package/lib/adapters/file-collection.js +127 -0
- package/lib/adapters/index.d.ts +57 -0
- package/lib/adapters/index.js +55 -0
- package/lib/adapters/link-report.d.ts +16 -0
- package/lib/adapters/link-report.js +47 -0
- package/lib/adapters/mcp.d.ts +28 -0
- package/lib/adapters/mcp.js +178 -0
- package/lib/adapters/plugin-files.d.ts +18 -0
- package/lib/adapters/plugin-files.js +142 -0
- package/lib/adapters/plugins.d.ts +41 -0
- package/lib/adapters/plugins.js +603 -0
- package/lib/adapters/pnpm-workspace.d.ts +26 -0
- package/lib/adapters/pnpm-workspace.js +147 -0
- package/lib/adapters/prompts.d.ts +30 -0
- package/lib/adapters/prompts.js +231 -0
- package/lib/adapters/providers.d.ts +34 -0
- package/lib/adapters/providers.js +155 -0
- package/lib/adapters/self.d.ts +17 -0
- package/lib/adapters/self.js +71 -0
- package/lib/adapters/sessions.d.ts +14 -0
- package/lib/adapters/sessions.js +15 -0
- package/lib/adapters/settings.d.ts +28 -0
- package/lib/adapters/settings.js +165 -0
- package/lib/adapters/skills.d.ts +13 -0
- package/lib/adapters/skills.js +14 -0
- package/lib/adapters/test-helpers.d.ts +125 -0
- package/lib/adapters/test-helpers.js +236 -0
- package/lib/adapters/ui.d.ts +27 -0
- package/lib/adapters/ui.js +89 -0
- package/lib/adapters/workspaces.d.ts +13 -0
- package/lib/adapters/workspaces.js +106 -0
- package/lib/client.d.ts +1026 -0
- package/lib/client.js +10047 -0
- package/lib/core/analyzer.d.ts +99 -0
- package/lib/core/analyzer.js +934 -0
- package/lib/core/backup.d.ts +138 -0
- package/lib/core/backup.js +513 -0
- package/lib/core/boot-rescue.d.ts +146 -0
- package/lib/core/boot-rescue.js +618 -0
- package/lib/core/cache-cleaner.d.ts +31 -0
- package/lib/core/cache-cleaner.js +95 -0
- package/lib/core/config-lifecycle.d.ts +180 -0
- package/lib/core/config-lifecycle.js +481 -0
- package/lib/core/config-snapshot.d.ts +135 -0
- package/lib/core/config-snapshot.js +424 -0
- package/lib/core/config-state.d.ts +70 -0
- package/lib/core/config-state.js +177 -0
- package/lib/core/consult-source.d.ts +58 -0
- package/lib/core/consult-source.js +260 -0
- package/lib/core/crash-report.d.ts +84 -0
- package/lib/core/crash-report.js +317 -0
- package/lib/core/exporter.d.ts +68 -0
- package/lib/core/exporter.js +375 -0
- package/lib/core/ghost-sweep.d.ts +19 -0
- package/lib/core/ghost-sweep.js +52 -0
- package/lib/core/importer.d.ts +60 -0
- package/lib/core/importer.js +29 -0
- package/lib/core/index.d.ts +14 -0
- package/lib/core/index.js +14 -0
- package/lib/core/journal.d.ts +197 -0
- package/lib/core/journal.js +418 -0
- package/lib/core/local-plugin-host.d.ts +35 -0
- package/lib/core/local-plugin-host.js +120 -0
- package/lib/core/local-plugin-pack.d.ts +135 -0
- package/lib/core/local-plugin-pack.js +278 -0
- package/lib/core/messages.d.ts +310 -0
- package/lib/core/messages.js +621 -0
- package/lib/core/migration-consult.d.ts +117 -0
- package/lib/core/migration-consult.js +363 -0
- package/lib/core/migration-history.d.ts +153 -0
- package/lib/core/migration-history.js +411 -0
- package/lib/core/model-tools.d.ts +50 -0
- package/lib/core/model-tools.js +148 -0
- package/lib/core/msg-types.d.ts +8 -0
- package/lib/core/msg-types.js +6 -0
- package/lib/core/phase3-child-crash.d.ts +1 -0
- package/lib/core/phase3-child-crash.js +87 -0
- package/lib/core/phase3-host.d.ts +138 -0
- package/lib/core/phase3-host.js +359 -0
- package/lib/core/phase3-prod-child.d.ts +1 -0
- package/lib/core/phase3-prod-child.js +35 -0
- package/lib/core/phase4-crash-child.d.ts +1 -0
- package/lib/core/phase4-crash-child.js +103 -0
- package/lib/core/plugin-cli.d.ts +150 -0
- package/lib/core/plugin-cli.js +497 -0
- package/lib/core/reconcile.d.ts +120 -0
- package/lib/core/reconcile.js +389 -0
- package/lib/core/recovery-orchestrator.d.ts +94 -0
- package/lib/core/recovery-orchestrator.js +327 -0
- package/lib/core/restore.d.ts +162 -0
- package/lib/core/restore.js +703 -0
- package/lib/core/rollback.d.ts +12 -0
- package/lib/core/rollback.js +145 -0
- package/lib/core/run-registry.d.ts +85 -0
- package/lib/core/run-registry.js +157 -0
- package/lib/core/startup-barrier.d.ts +53 -0
- package/lib/core/startup-barrier.js +77 -0
- package/lib/core/types.d.ts +515 -0
- package/lib/core/types.js +27 -0
- package/lib/core/undo.d.ts +73 -0
- package/lib/core/undo.js +87 -0
- package/lib/core/validator.d.ts +18 -0
- package/lib/core/validator.js +77 -0
- package/lib/core/verify-recovery.d.ts +21 -0
- package/lib/core/verify-recovery.js +256 -0
- package/lib/core/watcher.d.ts +101 -0
- package/lib/core/watcher.js +219 -0
- package/lib/index.d.ts +238 -0
- package/lib/index.js +2764 -0
- package/lib/migrations/index.d.ts +29 -0
- package/lib/migrations/index.js +56 -0
- package/lib/migrations/v1-to-v2.d.ts +9 -0
- package/lib/migrations/v1-to-v2.js +10 -0
- package/lib/profiles/index.d.ts +4 -0
- package/lib/profiles/index.js +5 -0
- package/lib/profiles/profile-manager.d.ts +106 -0
- package/lib/profiles/profile-manager.js +543 -0
- package/lib/schema/config.d.ts +28 -0
- package/lib/schema/config.js +165 -0
- package/lib/schema/index.d.ts +24 -0
- package/lib/schema/index.js +26 -0
- package/lib/schema/manifest.d.ts +28 -0
- package/lib/schema/manifest.js +109 -0
- package/lib/schema/tombstones.d.ts +46 -0
- package/lib/schema/tombstones.js +64 -0
- package/lib/schema/types.d.ts +232 -0
- package/lib/schema/types.js +8 -0
- package/lib/schema/versions.d.ts +27 -0
- package/lib/schema/versions.js +50 -0
- package/lib/security/encryption.d.ts +85 -0
- package/lib/security/encryption.js +279 -0
- package/lib/security/index.d.ts +16 -0
- package/lib/security/index.js +17 -0
- package/lib/security/integrity.d.ts +30 -0
- package/lib/security/integrity.js +78 -0
- package/lib/security/redaction.d.ts +26 -0
- package/lib/security/redaction.js +95 -0
- package/lib/security/secret-scanner.d.ts +86 -0
- package/lib/security/secret-scanner.js +404 -0
- package/lib/security/vault.d.ts +59 -0
- package/lib/security/vault.js +151 -0
- package/lib/security/zip-security.d.ts +27 -0
- package/lib/security/zip-security.js +154 -0
- package/lib/sync/autosync-config.d.ts +39 -0
- package/lib/sync/autosync-config.js +151 -0
- package/lib/sync/autosync-scheduler.d.ts +139 -0
- package/lib/sync/autosync-scheduler.js +564 -0
- package/lib/sync/fs.d.ts +18 -0
- package/lib/sync/fs.js +49 -0
- package/lib/sync/git/git-transport.d.ts +100 -0
- package/lib/sync/git/git-transport.js +437 -0
- package/lib/sync/github-auth.d.ts +131 -0
- package/lib/sync/github-auth.js +264 -0
- package/lib/sync/layout.d.ts +43 -0
- package/lib/sync/layout.js +203 -0
- package/lib/sync/snapshot-json.d.ts +9 -0
- package/lib/sync/snapshot-json.js +107 -0
- package/lib/sync/sync-config.d.ts +81 -0
- package/lib/sync/sync-config.js +281 -0
- package/lib/sync/sync-engine.d.ts +269 -0
- package/lib/sync/sync-engine.js +591 -0
- package/lib/sync/sync-history.d.ts +42 -0
- package/lib/sync/sync-history.js +78 -0
- package/lib/sync/sync-selection.d.ts +29 -0
- package/lib/sync/sync-selection.js +127 -0
- package/lib/sync/sync-session.d.ts +40 -0
- package/lib/sync/sync-session.js +54 -0
- package/lib/sync/sync-state.d.ts +37 -0
- package/lib/sync/sync-state.js +108 -0
- package/lib/sync/transport.d.ts +75 -0
- package/lib/sync/transport.js +55 -0
- package/lib/sync/ui-prefs.d.ts +34 -0
- package/lib/sync/ui-prefs.js +109 -0
- package/lib/sync/webdav/webdav-transport.d.ts +77 -0
- package/lib/sync/webdav/webdav-transport.js +451 -0
- package/lib/ui/errors.d.ts +19 -0
- package/lib/ui/errors.js +81 -0
- package/lib/ui/export-flow.d.ts +82 -0
- package/lib/ui/export-flow.js +98 -0
- package/lib/ui/i18n.d.ts +289 -0
- package/lib/ui/i18n.js +581 -0
- package/lib/ui/migration-consult-view.d.ts +91 -0
- package/lib/ui/migration-consult-view.js +115 -0
- package/lib/ui/progress.d.ts +38 -0
- package/lib/ui/progress.js +87 -0
- package/lib/ui/report.d.ts +23 -0
- package/lib/ui/report.js +153 -0
- package/lib/ui/test-helpers.d.ts +69 -0
- package/lib/ui/test-helpers.js +154 -0
- package/lib/ui/types.d.ts +257 -0
- package/lib/ui/types.js +21 -0
- package/lib/utils/atomic-write.d.ts +88 -0
- package/lib/utils/atomic-write.js +399 -0
- package/lib/utils/env-lock.d.ts +393 -0
- package/lib/utils/env-lock.js +930 -0
- package/lib/utils/hashing.d.ts +17 -0
- package/lib/utils/hashing.js +41 -0
- package/lib/utils/json.d.ts +24 -0
- package/lib/utils/json.js +76 -0
- package/lib/utils/logger.d.ts +39 -0
- package/lib/utils/logger.js +82 -0
- package/lib/utils/paths.d.ts +42 -0
- package/lib/utils/paths.js +189 -0
- package/lib/utils/proxy.d.ts +69 -0
- package/lib/utils/proxy.js +455 -0
- package/lib/utils/recursive-walk.d.ts +24 -0
- package/lib/utils/recursive-walk.js +134 -0
- package/lib/utils/zip.d.ts +54 -0
- package/lib/utils/zip.js +285 -0
- package/package.json +188 -0
- package/src/adapters/agent-instructions.ts +48 -0
- package/src/adapters/agent-presets.ts +14 -0
- package/src/adapters/credentials.test.ts +90 -0
- package/src/adapters/credentials.ts +143 -0
- package/src/adapters/file-collection.ts +137 -0
- package/src/adapters/files.test.ts +353 -0
- package/src/adapters/index.ts +94 -0
- package/src/adapters/link-report.test.ts +65 -0
- package/src/adapters/link-report.ts +61 -0
- package/src/adapters/mcp.test.ts +96 -0
- package/src/adapters/mcp.ts +185 -0
- package/src/adapters/plugin-files.ts +146 -0
- package/src/adapters/plugins.test.ts +326 -0
- package/src/adapters/plugins.ts +615 -0
- package/src/adapters/pnpm-workspace.test.ts +87 -0
- package/src/adapters/pnpm-workspace.ts +150 -0
- package/src/adapters/prompts.test.ts +110 -0
- package/src/adapters/prompts.ts +230 -0
- package/src/adapters/providers.test.ts +126 -0
- package/src/adapters/providers.ts +173 -0
- package/src/adapters/roundtrip.test.ts +190 -0
- package/src/adapters/self.test.ts +92 -0
- package/src/adapters/self.ts +75 -0
- package/src/adapters/sessions.ts +15 -0
- package/src/adapters/settings.test.ts +100 -0
- package/src/adapters/settings.ts +187 -0
- package/src/adapters/skills.ts +14 -0
- package/src/adapters/test-helpers.ts +248 -0
- package/src/adapters/ui.test.ts +57 -0
- package/src/adapters/ui.ts +108 -0
- package/src/adapters/workspaces.test.ts +106 -0
- package/src/adapters/workspaces.ts +109 -0
- package/src/client/ConfigManagerSection.tsx +90 -0
- package/src/client/api.ts +66 -0
- package/src/client/client-types.ts +18 -0
- package/src/client/common/ErrorBanner.tsx +73 -0
- package/src/client/common/Icon.tsx +129 -0
- package/src/client/common/Modal.tsx +173 -0
- package/src/client/common/ToastViewport.tsx +73 -0
- package/src/client/common/toast-store.test.ts +192 -0
- package/src/client/common/toast-store.ts +181 -0
- package/src/client/common/toast-types.ts +7 -0
- package/src/client/common/ui.tsx +342 -0
- package/src/client/config-manager.module.css +2971 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/index.ts +65 -0
- package/src/client/locales.ts +56 -0
- package/src/client/lucide-icons.d.ts +38 -0
- package/src/client/run-store.test.ts +223 -0
- package/src/client/run-store.ts +372 -0
- package/src/client/sync/SyncConfirmView.tsx +379 -0
- package/src/client/sync/SyncHistoryView.test.ts +40 -0
- package/src/client/sync/SyncHistoryView.tsx +220 -0
- package/src/client/sync/SyncSettingsView.tsx +1535 -0
- package/src/client/sync/history-model.test.ts +194 -0
- package/src/client/sync/history-model.ts +195 -0
- package/src/client/sync/sync-api.test.ts +585 -0
- package/src/client/sync/sync-api.ts +543 -0
- package/src/client/sync/sync-locales.ts +416 -0
- package/src/client/sync/sync-push-preview.test.ts +52 -0
- package/src/client/sync/sync-view-v2.test.ts +213 -0
- package/src/client/sync/sync-view.test.ts +487 -0
- package/src/client/sync/sync-view.ts +759 -0
- package/src/core/analyzer.ts +1068 -0
- package/src/core/backup.ts +605 -0
- package/src/core/boot-rescue.test.ts +847 -0
- package/src/core/boot-rescue.ts +737 -0
- package/src/core/cache-cleaner.test.ts +169 -0
- package/src/core/cache-cleaner.ts +121 -0
- package/src/core/config-lifecycle.test.ts +727 -0
- package/src/core/config-lifecycle.ts +573 -0
- package/src/core/config-snapshot.test.ts +369 -0
- package/src/core/config-snapshot.ts +522 -0
- package/src/core/config-state.test.ts +381 -0
- package/src/core/config-state.ts +207 -0
- package/src/core/consult-source.test.ts +199 -0
- package/src/core/consult-source.ts +293 -0
- package/src/core/crash-report.test.ts +380 -0
- package/src/core/crash-report.ts +369 -0
- package/src/core/exporter.ts +417 -0
- package/src/core/ghost-sweep.test.ts +94 -0
- package/src/core/ghost-sweep.ts +67 -0
- package/src/core/importer.ts +83 -0
- package/src/core/index.ts +56 -0
- package/src/core/journal.test.ts +257 -0
- package/src/core/journal.ts +518 -0
- package/src/core/local-plugin-host.ts +152 -0
- package/src/core/local-plugin-pack.test.ts +359 -0
- package/src/core/local-plugin-pack.ts +363 -0
- package/src/core/messages.test.ts +54 -0
- package/src/core/messages.ts +664 -0
- package/src/core/migration-consult.test.ts +315 -0
- package/src/core/migration-consult.ts +494 -0
- package/src/core/migration-history.test.ts +319 -0
- package/src/core/migration-history.ts +484 -0
- package/src/core/model-tools.test.ts +190 -0
- package/src/core/model-tools.ts +208 -0
- package/src/core/msg-types.ts +10 -0
- package/src/core/phase3-child-crash.ts +95 -0
- package/src/core/phase3-host.ts +411 -0
- package/src/core/phase3-p1.test.ts +212 -0
- package/src/core/phase3-p2b-fingerprint.test.ts +189 -0
- package/src/core/phase3-prod-child.ts +36 -0
- package/src/core/phase3-production-integration.test.ts +286 -0
- package/src/core/phase4-crash-child.ts +101 -0
- package/src/core/phase4-crash-injection.test.ts +166 -0
- package/src/core/plugin-cli.fs.test.ts +184 -0
- package/src/core/plugin-cli.test.ts +137 -0
- package/src/core/plugin-cli.ts +563 -0
- package/src/core/reconcile.test.ts +392 -0
- package/src/core/reconcile.ts +498 -0
- package/src/core/recovery-orchestrator.test.ts +145 -0
- package/src/core/recovery-orchestrator.ts +366 -0
- package/src/core/restore-manage.test.ts +94 -0
- package/src/core/restore.ts +849 -0
- package/src/core/rollback.ts +151 -0
- package/src/core/run-progress.test.ts +237 -0
- package/src/core/run-registry.test.ts +232 -0
- package/src/core/run-registry.ts +209 -0
- package/src/core/smoke.test.ts +842 -0
- package/src/core/startup-barrier.ts +99 -0
- package/src/core/types.ts +542 -0
- package/src/core/undo.ts +116 -0
- package/src/core/validator.ts +84 -0
- package/src/core/verify-recovery.ts +265 -0
- package/src/core/watcher.test.ts +326 -0
- package/src/core/watcher.ts +263 -0
- package/src/index.facade.test.ts +265 -0
- package/src/index.sync.test.ts +217 -0
- package/src/index.ts +2975 -0
- package/src/migrations/index.ts +71 -0
- package/src/migrations/v1-to-v2.ts +17 -0
- package/src/profiles/index.ts +8 -0
- package/src/profiles/profile-manager.test.ts +282 -0
- package/src/profiles/profile-manager.ts +630 -0
- package/src/schema/config.ts +165 -0
- package/src/schema/index.ts +71 -0
- package/src/schema/manifest-schema.test.ts +519 -0
- package/src/schema/manifest.ts +124 -0
- package/src/schema/tombstones.test.ts +131 -0
- package/src/schema/tombstones.ts +89 -0
- package/src/schema/types.ts +245 -0
- package/src/schema/versions.ts +55 -0
- package/src/security/encryption.ts +335 -0
- package/src/security/index.ts +16 -0
- package/src/security/integrity.ts +102 -0
- package/src/security/redaction.ts +101 -0
- package/src/security/secret-scanner.ts +445 -0
- package/src/security/security.test.ts +1215 -0
- package/src/security/vault.test.ts +164 -0
- package/src/security/vault.ts +198 -0
- package/src/security/zip-security.ts +180 -0
- package/src/sync/autosync-config.test.ts +147 -0
- package/src/sync/autosync-config.ts +179 -0
- package/src/sync/autosync-scheduler.test.ts +490 -0
- package/src/sync/autosync-scheduler.ts +646 -0
- package/src/sync/fs.ts +48 -0
- package/src/sync/git/git-transport.test.ts +673 -0
- package/src/sync/git/git-transport.ts +491 -0
- package/src/sync/github-auth.test.ts +280 -0
- package/src/sync/github-auth.ts +356 -0
- package/src/sync/layout.test.ts +270 -0
- package/src/sync/layout.ts +222 -0
- package/src/sync/snapshot-json.test.ts +119 -0
- package/src/sync/snapshot-json.ts +128 -0
- package/src/sync/sync-config.test.ts +310 -0
- package/src/sync/sync-config.ts +299 -0
- package/src/sync/sync-engine.test.ts +1074 -0
- package/src/sync/sync-engine.ts +754 -0
- package/src/sync/sync-history.test.ts +116 -0
- package/src/sync/sync-history.ts +120 -0
- package/src/sync/sync-selection.test.ts +186 -0
- package/src/sync/sync-selection.ts +143 -0
- package/src/sync/sync-session.test.ts +143 -0
- package/src/sync/sync-session.ts +77 -0
- package/src/sync/sync-state.test.ts +164 -0
- package/src/sync/sync-state.ts +131 -0
- package/src/sync/transport.test.ts +111 -0
- package/src/sync/transport.ts +118 -0
- package/src/sync/ui-prefs.test.ts +152 -0
- package/src/sync/ui-prefs.ts +135 -0
- package/src/sync/webdav/webdav-transport.test.ts +871 -0
- package/src/sync/webdav/webdav-transport.ts +535 -0
- package/src/ui/errors.test.ts +47 -0
- package/src/ui/errors.ts +100 -0
- package/src/ui/export-flow.test.ts +87 -0
- package/src/ui/export-flow.ts +156 -0
- package/src/ui/i18n.ts +590 -0
- package/src/ui/migration-consult-view.test.ts +151 -0
- package/src/ui/migration-consult-view.ts +178 -0
- package/src/ui/progress.test.ts +47 -0
- package/src/ui/progress.ts +102 -0
- package/src/ui/report.test.ts +101 -0
- package/src/ui/report.ts +146 -0
- package/src/ui/test-helpers.ts +184 -0
- package/src/ui/types.ts +325 -0
- package/src/utils/atomic-write.test.ts +312 -0
- package/src/utils/atomic-write.ts +422 -0
- package/src/utils/env-lock.test.ts +999 -0
- package/src/utils/env-lock.ts +1086 -0
- package/src/utils/hashing.ts +53 -0
- package/src/utils/json.ts +77 -0
- package/src/utils/logger.ts +114 -0
- package/src/utils/paths.ts +176 -0
- package/src/utils/proxy.test.ts +465 -0
- package/src/utils/proxy.ts +493 -0
- package/src/utils/recursive-walk.test.ts +148 -0
- package/src/utils/recursive-walk.ts +145 -0
- package/src/utils/zip.ts +332 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 核心领域类型(Export Schema v1 的纯类型定义)。
|
|
3
|
+
*
|
|
4
|
+
* 对齐 Docs/design/architecture.md §13.1 —— m3/m5 的共同契约。
|
|
5
|
+
* 本文件只含与 DSH 运行时无关的纯数据形状;DSH Service 门面类型见 src/core/types.ts。
|
|
6
|
+
*/
|
|
7
|
+
/** 运行平台(等价 NodeJS.Platform,但保持类型自包含,可在浏览器侧引用) */
|
|
8
|
+
export type Platform = 'win32' | 'darwin' | 'linux' | 'freebsd' | 'openbsd' | 'aix' | 'sunos' | 'android' | 'cygwin' | 'haiku' | 'netbsd' | 'other';
|
|
9
|
+
/**
|
|
10
|
+
* 分区标识,与 ZIP 目录结构、manifest.sections 键一一对应。
|
|
11
|
+
* 明确不实现的分区(keybindings/workflows/commands)不在此列;
|
|
12
|
+
* rules 已由 agentInstructions(~/.dsh/AGENTS.md,dsh-agent-instructions 全局指令)承接。
|
|
13
|
+
*/
|
|
14
|
+
export type SectionId = 'settings' | 'ui' | 'providers' | 'plugins' | 'mcp' | 'prompts' | 'skills' | 'agentPresets' | 'agentInstructions' | 'workspaces' | 'pluginFiles' | 'credentialsStatus' | 'secrets' | 'sessions' | 'self';
|
|
15
|
+
/** 加密备份的算法参数(非秘密:salt/iv/authTag 仅用于解密与完整性校验) */
|
|
16
|
+
export interface EncryptionInfo {
|
|
17
|
+
algorithm: 'aes-256-gcm';
|
|
18
|
+
kdf: 'scrypt';
|
|
19
|
+
kdfParams: {
|
|
20
|
+
N: number;
|
|
21
|
+
r: number;
|
|
22
|
+
p: number;
|
|
23
|
+
keyLength: number;
|
|
24
|
+
};
|
|
25
|
+
salt: string;
|
|
26
|
+
iv: string;
|
|
27
|
+
authTag: string;
|
|
28
|
+
version: number;
|
|
29
|
+
}
|
|
30
|
+
/** 每个导出 ZIP 根部的 manifest.json */
|
|
31
|
+
export interface Manifest {
|
|
32
|
+
schemaVersion: number;
|
|
33
|
+
exporter: {
|
|
34
|
+
name: string;
|
|
35
|
+
version: string;
|
|
36
|
+
};
|
|
37
|
+
source: {
|
|
38
|
+
dshVersion: string;
|
|
39
|
+
platform: Platform;
|
|
40
|
+
arch: string;
|
|
41
|
+
};
|
|
42
|
+
exportedAt: string;
|
|
43
|
+
sections: Record<SectionId, boolean>;
|
|
44
|
+
security: {
|
|
45
|
+
containsSecrets: boolean;
|
|
46
|
+
encrypted: boolean;
|
|
47
|
+
encryption: EncryptionInfo | null;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** settings.yaml 中单个 namespace 的导出记录(redacted 值 + 乐观锁 revision) */
|
|
51
|
+
export interface NamespaceRecord {
|
|
52
|
+
value: unknown;
|
|
53
|
+
base?: unknown;
|
|
54
|
+
revision: number;
|
|
55
|
+
applies?: string[];
|
|
56
|
+
/** DSH describe({redactSecrets:true}) 报告的 secrets 位置(只含路径标记,不含值) */
|
|
57
|
+
secrets: {
|
|
58
|
+
path: string[];
|
|
59
|
+
set: boolean;
|
|
60
|
+
}[];
|
|
61
|
+
}
|
|
62
|
+
export interface SettingsSection {
|
|
63
|
+
version: 1;
|
|
64
|
+
namespaces: Record<string, NamespaceRecord>;
|
|
65
|
+
}
|
|
66
|
+
export interface UiMigrationNote {
|
|
67
|
+
plugin: string;
|
|
68
|
+
storage: string;
|
|
69
|
+
key: string;
|
|
70
|
+
migratable: false;
|
|
71
|
+
reason: string;
|
|
72
|
+
}
|
|
73
|
+
export interface UiSection {
|
|
74
|
+
version: 1;
|
|
75
|
+
namespaces: Record<string, NamespaceRecord>;
|
|
76
|
+
/** localStorage 等 Host 不可迁移项的说明(纯说明,不含任何值) */
|
|
77
|
+
uiMigrationNotes: UiMigrationNote[];
|
|
78
|
+
}
|
|
79
|
+
export interface ProviderEntry {
|
|
80
|
+
route: string;
|
|
81
|
+
apiKeyEnv?: string;
|
|
82
|
+
displayName?: string;
|
|
83
|
+
baseURL?: string;
|
|
84
|
+
models?: unknown[];
|
|
85
|
+
modelOverrides?: unknown;
|
|
86
|
+
reasoning?: unknown;
|
|
87
|
+
transport?: unknown;
|
|
88
|
+
retryPolicy?: unknown;
|
|
89
|
+
}
|
|
90
|
+
export interface ProvidersSection {
|
|
91
|
+
version: 1;
|
|
92
|
+
providers: Record<string, ProviderEntry>;
|
|
93
|
+
}
|
|
94
|
+
export interface PluginEntry {
|
|
95
|
+
name: string;
|
|
96
|
+
version: string;
|
|
97
|
+
/**
|
|
98
|
+
* 声明依赖 spec(profile package.json dependencies 原样),如 ^0.3.6、
|
|
99
|
+
* github:csyangwen/dsh-memory-evolve、file:/link: 等。
|
|
100
|
+
* 导入时非 registry 来源(github:/git+/file:/link:/workspace: 等)按此 spec 安装,
|
|
101
|
+
* 否则按裸包名走 npm 最新版(官方机制)。
|
|
102
|
+
*/
|
|
103
|
+
spec?: string;
|
|
104
|
+
isBundle: boolean;
|
|
105
|
+
inBundles: string[];
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
fiberPhase?: string;
|
|
108
|
+
}
|
|
109
|
+
export interface PatchLine {
|
|
110
|
+
file: string;
|
|
111
|
+
lineId: string;
|
|
112
|
+
raw: unknown;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 本地源插件的打包产物(T1)。
|
|
116
|
+
*
|
|
117
|
+
* 为什么放在 `plugins` 分区内(而非 `plugin-files/` 前缀):
|
|
118
|
+
* plugins 是 `defaultIncluded=true` 且 portable,而 pluginFiles 是 `deviceSpecific`
|
|
119
|
+
* 且 defaultIncluded=false —— 后者会让「快速导出」(默认路径)拿不到 tarball,
|
|
120
|
+
* 而「本地插件换机丢失」恰恰是最需要默认覆盖的场景。
|
|
121
|
+
*
|
|
122
|
+
* 为什么不新增分区 id:新增 SectionId 需同步 SECTION_IDS / SECTION_FILE_PREFIXES /
|
|
123
|
+
* manifest 校验 / analyzer / exporter / market BANNED / sync layout 共 7 处契约,
|
|
124
|
+
* 风险远大于收益;本字段落在既有 plugins 分区内即可被全部既有机制正确处理。
|
|
125
|
+
*
|
|
126
|
+
* 安全:base64 仅为传输编码,不做任何执行;导入端只把解包出的 tgz 交给官方
|
|
127
|
+
* `dsh plugin add` 通道。**市场侧已在发布与导入两端同时拒绝携带本字段的条目**
|
|
128
|
+
* (`market/prepare.ts` 6b3 + `market/security.ts`)——因为 tarball 是不可经公开仓库审阅的
|
|
129
|
+
* 不透明二进制,安装时可能执行 postinstall,等于给「分享配置」开一条携带任意代码、
|
|
130
|
+
* 绕过 BANNED 分区的通道。本地插件迁移只应发生在自己的备份里。
|
|
131
|
+
*/
|
|
132
|
+
export interface LocalPluginTarball {
|
|
133
|
+
/** 包名(与 PluginEntry.name 对应) */
|
|
134
|
+
packageName: string;
|
|
135
|
+
/** 打包时的插件版本 */
|
|
136
|
+
version: string;
|
|
137
|
+
/** 归档内相对路径(`local-plugins/<x>.tgz`,恒正斜杠) */
|
|
138
|
+
relativePath: string;
|
|
139
|
+
/** tarball 字节的 base64(非秘密;仅本地源插件的源码包) */
|
|
140
|
+
base64: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* profile 的 `patches/**` 文件(issue #35):pnpm-workspace.yaml 的 `patchedDependencies`
|
|
144
|
+
* 引用的补丁文件本身。声明与文件必须同进同出——只搬声明会让目标机的 pnpm 拒绝一切 `add`
|
|
145
|
+
* (`Failed to read patch file ... os error 2`)。
|
|
146
|
+
*/
|
|
147
|
+
export interface PnpmPatchFile {
|
|
148
|
+
/** 相对 **profile 目录** 的路径(如 `patches/dsh-approval-gate.patch`,恒正斜杠) */
|
|
149
|
+
relativePath: string;
|
|
150
|
+
/** 文件字节的 base64(patch 是纯文本、非秘密;仅为传输编码) */
|
|
151
|
+
base64: string;
|
|
152
|
+
}
|
|
153
|
+
export interface PluginsSection {
|
|
154
|
+
version: 1;
|
|
155
|
+
plugins: PluginEntry[];
|
|
156
|
+
patch: PatchLine[];
|
|
157
|
+
/**
|
|
158
|
+
* profile 的 pnpm-workspace.yaml 原文(allowBuilds / minimumReleaseAgeExclude /
|
|
159
|
+
* nodeLinker 等)。随插件分区迁移,保证目标 profile 的 pnpm 能按来源配置安装
|
|
160
|
+
* (git 插件构建脚本白名单、新发布版本冷静期等)。缺省/无文件为 null。
|
|
161
|
+
*/
|
|
162
|
+
pnpmWorkspace?: string | null;
|
|
163
|
+
/**
|
|
164
|
+
* 本地源(`link:` / `file:`)插件的打包产物(T1)。缺省/无本地源插件 = 不出现。
|
|
165
|
+
* 导出时由宿主注入的打包钩子填充;导入时据此把 spec 重写为 `file:<解包后的绝对路径>`。
|
|
166
|
+
*/
|
|
167
|
+
localTarballs?: LocalPluginTarball[];
|
|
168
|
+
/**
|
|
169
|
+
* `patchedDependencies` 引用的 patch 文件(issue #35)。缺省 = 无声明或无文件可收集。
|
|
170
|
+
* 导入端先落文件、再写(已剔除不可满足声明的)pnpm-workspace.yaml。
|
|
171
|
+
*/
|
|
172
|
+
patchFiles?: PnpmPatchFile[];
|
|
173
|
+
}
|
|
174
|
+
export interface McpServerEntry {
|
|
175
|
+
serverName: string;
|
|
176
|
+
type: 'stdio' | 'streamable-http';
|
|
177
|
+
command?: string;
|
|
178
|
+
args?: string[];
|
|
179
|
+
env?: Record<string, string>;
|
|
180
|
+
cwd?: string;
|
|
181
|
+
url?: string;
|
|
182
|
+
headers?: Record<string, string>;
|
|
183
|
+
}
|
|
184
|
+
export interface McpSection {
|
|
185
|
+
version: 1;
|
|
186
|
+
servers: McpServerEntry[];
|
|
187
|
+
}
|
|
188
|
+
export interface PromptEntry {
|
|
189
|
+
id: string;
|
|
190
|
+
name: string;
|
|
191
|
+
kind: 'systemPrompt' | 'planMode';
|
|
192
|
+
text: string;
|
|
193
|
+
sourceLineId?: string;
|
|
194
|
+
}
|
|
195
|
+
export interface PromptsSection {
|
|
196
|
+
version: 1;
|
|
197
|
+
prompts: PromptEntry[];
|
|
198
|
+
}
|
|
199
|
+
export interface WorkspaceRecord {
|
|
200
|
+
id: string;
|
|
201
|
+
path: string;
|
|
202
|
+
title?: string;
|
|
203
|
+
sessionIds: string[];
|
|
204
|
+
createdAt?: string;
|
|
205
|
+
updatedAt?: string;
|
|
206
|
+
}
|
|
207
|
+
export interface WorkspacesSection {
|
|
208
|
+
version: 1;
|
|
209
|
+
workspaces: WorkspaceRecord[];
|
|
210
|
+
}
|
|
211
|
+
export interface CredentialStatus {
|
|
212
|
+
ref: string;
|
|
213
|
+
required: boolean;
|
|
214
|
+
configured: boolean;
|
|
215
|
+
source?: 'env' | 'file' | 'projectEnv' | 'other';
|
|
216
|
+
hasValue: boolean;
|
|
217
|
+
}
|
|
218
|
+
export interface CredentialsSection {
|
|
219
|
+
version: 1;
|
|
220
|
+
credentials: CredentialStatus[];
|
|
221
|
+
}
|
|
222
|
+
export interface FileEntry {
|
|
223
|
+
relativePath: string;
|
|
224
|
+
data: Uint8Array;
|
|
225
|
+
contentHash: string;
|
|
226
|
+
}
|
|
227
|
+
export interface FilesSection {
|
|
228
|
+
version: 1;
|
|
229
|
+
files: FileEntry[];
|
|
230
|
+
}
|
|
231
|
+
/** 分区 JSON 文件的统一载荷(schema 校验用) */
|
|
232
|
+
export type SectionData = SettingsSection | UiSection | ProvidersSection | PluginsSection | McpSection | PromptsSection | WorkspacesSection | CredentialsSection | FilesSection;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export Schema 版本判定 —— 集中唯一出口(对齐 Docs/design/architecture.md §10.3)。
|
|
3
|
+
*
|
|
4
|
+
* 业务代码零版本判断:所有模块一律通过本文件函数判定,
|
|
5
|
+
* 任何 `if (v === 1)` 式散落判断均为禁止项。
|
|
6
|
+
*/
|
|
7
|
+
/** 当前 Export Schema 版本(v1 起点;未来随迁移链演进) */
|
|
8
|
+
export declare const CURRENT_SCHEMA_VERSION: 1;
|
|
9
|
+
/** 本插件支持导入的最低 schema 版本 */
|
|
10
|
+
export declare const MIN_SUPPORTED_SCHEMA_VERSION: 1;
|
|
11
|
+
/** 版本不受支持(过新或过旧)时的错误;携带版本号便于 UI 提示升级/更新备份 */
|
|
12
|
+
export declare class UnsupportedSchemaError extends Error {
|
|
13
|
+
readonly version: number;
|
|
14
|
+
constructor(version: number, message?: string);
|
|
15
|
+
}
|
|
16
|
+
/** 恰好是当前版本 */
|
|
17
|
+
export declare function isCurrent(v: number): boolean;
|
|
18
|
+
/** 版本在支持范围内(含需要迁移的旧版本) */
|
|
19
|
+
export declare function isSupported(v: number): boolean;
|
|
20
|
+
/** 旧版本:低于当前但仍在支持范围内 → 需要沿迁移链升级 */
|
|
21
|
+
export declare function needsMigration(v: number): boolean;
|
|
22
|
+
/** 过新版本:高于当前 → 必须升级本插件才能导入 */
|
|
23
|
+
export declare function isTooNew(v: number): boolean;
|
|
24
|
+
/** 是否可导入(当前版本或可迁移的旧版本) */
|
|
25
|
+
export declare function canImport(v: number): boolean;
|
|
26
|
+
/** 版本的可读描述(报告/日志用) */
|
|
27
|
+
export declare function describeVersion(v: number): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export Schema 版本判定 —— 集中唯一出口(对齐 Docs/design/architecture.md §10.3)。
|
|
3
|
+
*
|
|
4
|
+
* 业务代码零版本判断:所有模块一律通过本文件函数判定,
|
|
5
|
+
* 任何 `if (v === 1)` 式散落判断均为禁止项。
|
|
6
|
+
*/
|
|
7
|
+
/** 当前 Export Schema 版本(v1 起点;未来随迁移链演进) */
|
|
8
|
+
export const CURRENT_SCHEMA_VERSION = 1;
|
|
9
|
+
/** 本插件支持导入的最低 schema 版本 */
|
|
10
|
+
export const MIN_SUPPORTED_SCHEMA_VERSION = 1;
|
|
11
|
+
/** 版本不受支持(过新或过旧)时的错误;携带版本号便于 UI 提示升级/更新备份 */
|
|
12
|
+
export class UnsupportedSchemaError extends Error {
|
|
13
|
+
version;
|
|
14
|
+
constructor(version, message = `Export schema version ${version} is not supported (current: ${CURRENT_SCHEMA_VERSION})`) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'UnsupportedSchemaError';
|
|
17
|
+
this.version = version;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** 恰好是当前版本 */
|
|
21
|
+
export function isCurrent(v) {
|
|
22
|
+
return v === CURRENT_SCHEMA_VERSION;
|
|
23
|
+
}
|
|
24
|
+
/** 版本在支持范围内(含需要迁移的旧版本) */
|
|
25
|
+
export function isSupported(v) {
|
|
26
|
+
return v >= MIN_SUPPORTED_SCHEMA_VERSION && v <= CURRENT_SCHEMA_VERSION;
|
|
27
|
+
}
|
|
28
|
+
/** 旧版本:低于当前但仍在支持范围内 → 需要沿迁移链升级 */
|
|
29
|
+
export function needsMigration(v) {
|
|
30
|
+
return v >= MIN_SUPPORTED_SCHEMA_VERSION && v < CURRENT_SCHEMA_VERSION;
|
|
31
|
+
}
|
|
32
|
+
/** 过新版本:高于当前 → 必须升级本插件才能导入 */
|
|
33
|
+
export function isTooNew(v) {
|
|
34
|
+
return v > CURRENT_SCHEMA_VERSION;
|
|
35
|
+
}
|
|
36
|
+
/** 是否可导入(当前版本或可迁移的旧版本) */
|
|
37
|
+
export function canImport(v) {
|
|
38
|
+
return isCurrent(v) || needsMigration(v);
|
|
39
|
+
}
|
|
40
|
+
/** 版本的可读描述(报告/日志用) */
|
|
41
|
+
export function describeVersion(v) {
|
|
42
|
+
if (isTooNew(v))
|
|
43
|
+
return `schema v${v}(高于当前 ${CURRENT_SCHEMA_VERSION},需升级插件)`;
|
|
44
|
+
if (needsMigration(v))
|
|
45
|
+
return `schema v${v}(旧版,将迁移到 v${CURRENT_SCHEMA_VERSION})`;
|
|
46
|
+
if (isCurrent(v))
|
|
47
|
+
return `schema v${v}(当前版本)`;
|
|
48
|
+
return `schema v${v}(低于最低支持 ${MIN_SUPPORTED_SCHEMA_VERSION},不受支持)`;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=versions.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { EncryptionInfo } from '../schema/types.ts';
|
|
2
|
+
import type { EncryptionProvider } from '../core/types.ts';
|
|
3
|
+
export declare const SCHEMA_MAGIC = "DSC1";
|
|
4
|
+
export declare const SCHEMA_VERSION = 1;
|
|
5
|
+
/** 整体加密备份容器的 magic(D C A rchive):头部布局同 secrets.enc,kdf 参数用默认常量 */
|
|
6
|
+
export declare const ARCHIVE_MAGIC = "DCA1";
|
|
7
|
+
export declare const ARCHIVE_VERSION = 1;
|
|
8
|
+
/** 探测字节是否为整体加密备份容器(上传/下载时判定 containerType) */
|
|
9
|
+
export declare function isArchiveBlob(buf: Uint8Array): boolean;
|
|
10
|
+
export declare const SCRYPT_PARAMS: {
|
|
11
|
+
readonly N: 16384;
|
|
12
|
+
readonly r: 8;
|
|
13
|
+
readonly p: 1;
|
|
14
|
+
readonly keyLength: 32;
|
|
15
|
+
};
|
|
16
|
+
export declare const SALT_LENGTH = 16;
|
|
17
|
+
export declare const IV_LENGTH = 12;
|
|
18
|
+
export declare const TAG_LENGTH = 16;
|
|
19
|
+
/** magic(4) + version(1) + salt(16) + iv(12) + authTag(16) */
|
|
20
|
+
export declare const HEADER_LENGTH: number;
|
|
21
|
+
export type SecurityErrorCode = 'BAD_PASSWORD' | 'TAMPERED' | 'UNSUPPORTED_FORMAT';
|
|
22
|
+
export declare class SecurityError extends Error {
|
|
23
|
+
readonly code: SecurityErrorCode;
|
|
24
|
+
constructor(code: SecurityErrorCode, message: string);
|
|
25
|
+
}
|
|
26
|
+
/** KDF 参数值域校验(防 manifest 被篡改成超大 N 导致 DoS;非法即拒绝) */
|
|
27
|
+
export declare function validateKdfParams(params: unknown): params is EncryptionInfo['kdfParams'];
|
|
28
|
+
/** 派生密钥(scrypt,参数可来自 manifest;默认常量) */
|
|
29
|
+
export declare function deriveKey(password: string, salt: Uint8Array, params?: EncryptionInfo['kdfParams']): Promise<Buffer>;
|
|
30
|
+
/** 加密 .credentials.yaml 原文 → { blob, info }(info 直接进 manifest.security.encryption) */
|
|
31
|
+
export declare function encryptCredentials(plaintext: string, password: string): Promise<{
|
|
32
|
+
blob: Uint8Array;
|
|
33
|
+
info: EncryptionInfo;
|
|
34
|
+
}>;
|
|
35
|
+
/** 解密 secrets.enc(authTag 校验失败抛 BAD_PASSWORD;元数据不一致抛 TAMPERED) */
|
|
36
|
+
export declare function decryptCredentials(blob: Uint8Array, info: EncryptionInfo, password: string): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* 创建 core `EncryptionProvider`(对齐 core/types.ts 契约)。
|
|
39
|
+
* encrypt 使用闭包持有密码;decrypt 使用调用方传入的密码(支持换密码解密)。
|
|
40
|
+
*/
|
|
41
|
+
export declare function createEncryptionProvider(password: string): EncryptionProvider;
|
|
42
|
+
/**
|
|
43
|
+
* 加密完整 ZIP 字节 → 加密容器 blob。
|
|
44
|
+
*
|
|
45
|
+
* 布局(DCA1,与 secrets.enc 同构,kdf 参数用默认常量):
|
|
46
|
+
* magic "DCA1"(4B) + version(1B) + salt(16B) + iv(12B) + authTag(16B) + ciphertext(plainZIP)
|
|
47
|
+
*
|
|
48
|
+
* 语义:整个备份(manifest / 各分区 JSON / sessions / 插件文件 / secrets.enc……)
|
|
49
|
+
* 全部进入 ciphertext,磁盘上无法看到任何明文内容。verifyEncryptedBlob 用于
|
|
50
|
+
* 校验密码正确(GCM authTag 通过)且返回真实解密用的随机参数。
|
|
51
|
+
*/
|
|
52
|
+
export declare function encryptArchive(plainZip: Uint8Array, password: string): Promise<{
|
|
53
|
+
blob: Uint8Array;
|
|
54
|
+
info: EncryptionInfo;
|
|
55
|
+
kdf: {
|
|
56
|
+
salt: Buffer;
|
|
57
|
+
iv: Buffer;
|
|
58
|
+
};
|
|
59
|
+
}>;
|
|
60
|
+
/** 容器版解密:用「先验真实参数」校验密码并解密(—— 调用方必须先用 verifyEncryptedBlob 拿到 info/kdf) */
|
|
61
|
+
export declare function decryptArchive(blob: Uint8Array, info: EncryptionInfo, kdfParam: {
|
|
62
|
+
salt: Buffer;
|
|
63
|
+
iv: Buffer;
|
|
64
|
+
}, password: string): Promise<Uint8Array>;
|
|
65
|
+
export interface EncryptedBlobInfo {
|
|
66
|
+
/** 容器是否合法(magic/version/header 校验通过) */
|
|
67
|
+
valid: boolean;
|
|
68
|
+
/** 密码是否正确(GCM authTag 认证通过);valid=false 时恒 false */
|
|
69
|
+
ok: boolean;
|
|
70
|
+
/** 解密所需参数(valid && ok 时有效;用默认常量派生 key) */
|
|
71
|
+
info: EncryptionInfo | null;
|
|
72
|
+
kdf: {
|
|
73
|
+
salt: Buffer;
|
|
74
|
+
iv: Buffer;
|
|
75
|
+
} | null;
|
|
76
|
+
/** 错误码(供上层转用户可读错误):BAD_PASSWORD / TAMPERED / UNSUPPORTED_FORMAT */
|
|
77
|
+
code: SecurityErrorCode | null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 校验加密备份容器并验证密码(只读,零解密返回内容)。
|
|
81
|
+
* - 容器不合法(非 DCA1 / 截断 / 参数异常)→ valid=false + code
|
|
82
|
+
* - 密码正确 → ok=true + 返回随机参数(后续 decryptArchive 用之)
|
|
83
|
+
* - 密码错误 → ok=false + code=BAD_PASSWORD
|
|
84
|
+
*/
|
|
85
|
+
export declare function verifyEncryptedBlob(blob: Uint8Array, password: string): Promise<EncryptedBlobInfo>;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 凭据加密(规范 §7 / 设计 §7.3 / core `EncryptionProvider` 契约实现,零依赖 node:crypto)。
|
|
3
|
+
*
|
|
4
|
+
* 方案:scrypt(KDF)+ AES-256-GCM(AEAD),每次导出 salt/iv 全随机,密码绝不落盘。
|
|
5
|
+
* secrets.enc 二进制布局(设计 §7.3 细化):
|
|
6
|
+
*
|
|
7
|
+
* magic "DSC1"(4B) + version(1B) + salt(16B) + iv(12B) + authTag(16B) + ciphertext
|
|
8
|
+
*
|
|
9
|
+
* - magic/version 不符 → `UNSUPPORTED_FORMAT`(不是本插件产物 / 版本过新)
|
|
10
|
+
* - blob 内嵌参数与 manifest 的 EncryptionInfo 不一致 → `TAMPERED`(元数据被篡改)
|
|
11
|
+
* - GCM 认证失败(密码错误或密文被改) → `BAD_PASSWORD`(提示重输密码或核对备份来源)
|
|
12
|
+
*
|
|
13
|
+
* 与 core 契约的关系:
|
|
14
|
+
* - `createEncryptionProvider(password)` 返回 core `EncryptionProvider`(encrypt 无密码参数,
|
|
15
|
+
* 闭包持有导出密码;decrypt 用调用方传入的密码 → 支持「换密码解密」)。
|
|
16
|
+
* - 底层纯函数 encryptCredentials/decryptCredentials 对齐设计 §13.4,供直接调用与测试。
|
|
17
|
+
* - plaintext 语义:`.credentials.yaml` 文件原文(m5 经 HostContext.fs 文件级读取提供;
|
|
18
|
+
* ctx.credentials 永不回读值——研究报告 §3.2 硬约束),本模块只做字节级加解密。
|
|
19
|
+
*/
|
|
20
|
+
import crypto from 'node:crypto';
|
|
21
|
+
import { promisify } from 'node:util';
|
|
22
|
+
const scryptAsync = promisify(crypto.scrypt);
|
|
23
|
+
export const SCHEMA_MAGIC = 'DSC1';
|
|
24
|
+
export const SCHEMA_VERSION = 1;
|
|
25
|
+
/** 整体加密备份容器的 magic(D C A rchive):头部布局同 secrets.enc,kdf 参数用默认常量 */
|
|
26
|
+
export const ARCHIVE_MAGIC = 'DCA1';
|
|
27
|
+
export const ARCHIVE_VERSION = 1;
|
|
28
|
+
/** 探测字节是否为整体加密备份容器(上传/下载时判定 containerType) */
|
|
29
|
+
export function isArchiveBlob(buf) {
|
|
30
|
+
return Buffer.from(buf.subarray(0, 4)).toString('ascii') === ARCHIVE_MAGIC;
|
|
31
|
+
}
|
|
32
|
+
export const SCRYPT_PARAMS = { N: 16384, r: 8, p: 1, keyLength: 32 };
|
|
33
|
+
export const SALT_LENGTH = 16;
|
|
34
|
+
export const IV_LENGTH = 12;
|
|
35
|
+
export const TAG_LENGTH = 16;
|
|
36
|
+
/** magic(4) + version(1) + salt(16) + iv(12) + authTag(16) */
|
|
37
|
+
export const HEADER_LENGTH = 4 + 1 + SALT_LENGTH + IV_LENGTH + TAG_LENGTH;
|
|
38
|
+
export class SecurityError extends Error {
|
|
39
|
+
code;
|
|
40
|
+
constructor(code, message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'SecurityError';
|
|
43
|
+
this.code = code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** KDF 参数值域校验(防 manifest 被篡改成超大 N 导致 DoS;非法即拒绝) */
|
|
47
|
+
export function validateKdfParams(params) {
|
|
48
|
+
if (params === null || typeof params !== 'object')
|
|
49
|
+
return false;
|
|
50
|
+
const p = params;
|
|
51
|
+
if (typeof p['N'] !== 'number' || p['N'] < 2 ** 14 || p['N'] > 2 ** 20)
|
|
52
|
+
return false;
|
|
53
|
+
if (typeof p['r'] !== 'number' || p['r'] < 1 || p['r'] > 32)
|
|
54
|
+
return false;
|
|
55
|
+
if (typeof p['p'] !== 'number' || p['p'] < 1 || p['p'] > 32)
|
|
56
|
+
return false;
|
|
57
|
+
return typeof p['keyLength'] === 'number' && p['keyLength'] === 32;
|
|
58
|
+
}
|
|
59
|
+
/** 派生密钥(scrypt,参数可来自 manifest;默认常量) */
|
|
60
|
+
export async function deriveKey(password, salt, params = SCRYPT_PARAMS) {
|
|
61
|
+
return scryptAsync(password, salt, params.keyLength, { N: params.N, r: params.r, p: params.p });
|
|
62
|
+
}
|
|
63
|
+
/** 加密 .credentials.yaml 原文 → { blob, info }(info 直接进 manifest.security.encryption) */
|
|
64
|
+
export async function encryptCredentials(plaintext, password) {
|
|
65
|
+
if (password === '')
|
|
66
|
+
throw new SecurityError('BAD_PASSWORD', '加密密码不能为空');
|
|
67
|
+
// 产品决策(2026-09-13):加密**不做任何密码强度校验**——用户提供的任何密码都必须可用。
|
|
68
|
+
// 请勿在此重新加入强度闸门;用户自定的密码策略不由本插件约束。
|
|
69
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
70
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
71
|
+
const key = await deriveKey(password, salt);
|
|
72
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
73
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
74
|
+
const authTag = cipher.getAuthTag();
|
|
75
|
+
const header = Buffer.alloc(HEADER_LENGTH);
|
|
76
|
+
header.write(SCHEMA_MAGIC, 0, 'ascii');
|
|
77
|
+
header[4] = SCHEMA_VERSION;
|
|
78
|
+
salt.copy(header, 5);
|
|
79
|
+
iv.copy(header, 5 + SALT_LENGTH);
|
|
80
|
+
authTag.copy(header, 5 + SALT_LENGTH + IV_LENGTH);
|
|
81
|
+
const info = {
|
|
82
|
+
algorithm: 'aes-256-gcm',
|
|
83
|
+
kdf: 'scrypt',
|
|
84
|
+
kdfParams: { ...SCRYPT_PARAMS },
|
|
85
|
+
salt: salt.toString('base64'),
|
|
86
|
+
iv: iv.toString('base64'),
|
|
87
|
+
authTag: authTag.toString('base64'),
|
|
88
|
+
version: SCHEMA_VERSION,
|
|
89
|
+
};
|
|
90
|
+
return { blob: Buffer.concat([header, encrypted]), info };
|
|
91
|
+
}
|
|
92
|
+
/** 解密 secrets.enc(authTag 校验失败抛 BAD_PASSWORD;元数据不一致抛 TAMPERED) */
|
|
93
|
+
export async function decryptCredentials(blob, info, password) {
|
|
94
|
+
if (blob.length < HEADER_LENGTH) {
|
|
95
|
+
throw new SecurityError('TAMPERED', 'secrets.enc 体积过小(截断或损坏)');
|
|
96
|
+
}
|
|
97
|
+
const magic = Buffer.from(blob.subarray(0, 4)).toString('ascii');
|
|
98
|
+
if (magic !== SCHEMA_MAGIC) {
|
|
99
|
+
throw new SecurityError('UNSUPPORTED_FORMAT', `未知文件格式 magic "${magic}",不是本插件产物`);
|
|
100
|
+
}
|
|
101
|
+
const version = blob[4];
|
|
102
|
+
if (version !== SCHEMA_VERSION) {
|
|
103
|
+
throw new SecurityError('UNSUPPORTED_FORMAT', `不支持的 secrets.enc 版本 ${version}`);
|
|
104
|
+
}
|
|
105
|
+
if (info.algorithm !== 'aes-256-gcm' || info.kdf !== 'scrypt' || !validateKdfParams(info.kdfParams)) {
|
|
106
|
+
throw new SecurityError('UNSUPPORTED_FORMAT', 'manifest 加密参数非法或不受支持');
|
|
107
|
+
}
|
|
108
|
+
const blobSalt = blob.subarray(5, 5 + SALT_LENGTH);
|
|
109
|
+
const blobIv = blob.subarray(5 + SALT_LENGTH, 5 + SALT_LENGTH + IV_LENGTH);
|
|
110
|
+
const blobTag = blob.subarray(5 + SALT_LENGTH + IV_LENGTH, HEADER_LENGTH);
|
|
111
|
+
if (info.salt !== Buffer.from(blobSalt).toString('base64') ||
|
|
112
|
+
info.iv !== Buffer.from(blobIv).toString('base64') ||
|
|
113
|
+
info.authTag !== Buffer.from(blobTag).toString('base64')) {
|
|
114
|
+
throw new SecurityError('TAMPERED', 'secrets.enc 与 manifest 加密参数不一致(可能被篡改)');
|
|
115
|
+
}
|
|
116
|
+
let key;
|
|
117
|
+
try {
|
|
118
|
+
key = await deriveKey(password, blobSalt, info.kdfParams);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
throw new SecurityError('BAD_PASSWORD', `密钥派生失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
122
|
+
}
|
|
123
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, blobIv);
|
|
124
|
+
decipher.setAuthTag(blobTag);
|
|
125
|
+
try {
|
|
126
|
+
const decrypted = Buffer.concat([
|
|
127
|
+
decipher.update(blob.subarray(HEADER_LENGTH)),
|
|
128
|
+
decipher.final(),
|
|
129
|
+
]);
|
|
130
|
+
return decrypted.toString('utf8');
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw new SecurityError('BAD_PASSWORD', '解密认证失败:密码错误或密文被篡改');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 创建 core `EncryptionProvider`(对齐 core/types.ts 契约)。
|
|
138
|
+
* encrypt 使用闭包持有密码;decrypt 使用调用方传入的密码(支持换密码解密)。
|
|
139
|
+
*/
|
|
140
|
+
export function createEncryptionProvider(password) {
|
|
141
|
+
return {
|
|
142
|
+
async encrypt(plaintext) {
|
|
143
|
+
return encryptCredentials(plaintext, password);
|
|
144
|
+
},
|
|
145
|
+
async decrypt(blob, info, pw) {
|
|
146
|
+
return decryptCredentials(blob, info, pw);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/* ---------------- 整体备份容器加密(§7.4:加密整个 ZIP,而非仅 secrets) ---------------- */
|
|
151
|
+
/**
|
|
152
|
+
* 加密完整 ZIP 字节 → 加密容器 blob。
|
|
153
|
+
*
|
|
154
|
+
* 布局(DCA1,与 secrets.enc 同构,kdf 参数用默认常量):
|
|
155
|
+
* magic "DCA1"(4B) + version(1B) + salt(16B) + iv(12B) + authTag(16B) + ciphertext(plainZIP)
|
|
156
|
+
*
|
|
157
|
+
* 语义:整个备份(manifest / 各分区 JSON / sessions / 插件文件 / secrets.enc……)
|
|
158
|
+
* 全部进入 ciphertext,磁盘上无法看到任何明文内容。verifyEncryptedBlob 用于
|
|
159
|
+
* 校验密码正确(GCM authTag 通过)且返回真实解密用的随机参数。
|
|
160
|
+
*/
|
|
161
|
+
export async function encryptArchive(plainZip, password) {
|
|
162
|
+
if (password === '')
|
|
163
|
+
throw new SecurityError('BAD_PASSWORD', '加密密码不能为空');
|
|
164
|
+
// 产品决策(2026-09-13):加密**不做任何密码强度校验**——用户提供的任何密码都必须可用。
|
|
165
|
+
// 请勿在此重新加入强度闸门;用户自定的密码策略不由本插件约束。
|
|
166
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
167
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
168
|
+
const key = await deriveKey(password, salt);
|
|
169
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
170
|
+
const encrypted = Buffer.concat([cipher.update(plainZip), cipher.final()]);
|
|
171
|
+
const authTag = cipher.getAuthTag();
|
|
172
|
+
const header = Buffer.alloc(HEADER_LENGTH);
|
|
173
|
+
header.write(ARCHIVE_MAGIC, 0, 'ascii');
|
|
174
|
+
header[4] = ARCHIVE_VERSION;
|
|
175
|
+
salt.copy(header, 5);
|
|
176
|
+
iv.copy(header, 5 + SALT_LENGTH);
|
|
177
|
+
authTag.copy(header, 5 + SALT_LENGTH + IV_LENGTH);
|
|
178
|
+
const info = {
|
|
179
|
+
algorithm: 'aes-256-gcm',
|
|
180
|
+
kdf: 'scrypt',
|
|
181
|
+
kdfParams: { ...SCRYPT_PARAMS },
|
|
182
|
+
salt: salt.toString('base64'),
|
|
183
|
+
iv: iv.toString('base64'),
|
|
184
|
+
authTag: authTag.toString('base64'),
|
|
185
|
+
version: ARCHIVE_VERSION,
|
|
186
|
+
};
|
|
187
|
+
return { blob: Buffer.concat([header, encrypted]), info, kdf: { salt, iv } };
|
|
188
|
+
}
|
|
189
|
+
/** 容器版解密:用「先验真实参数」校验密码并解密(—— 调用方必须先用 verifyEncryptedBlob 拿到 info/kdf) */
|
|
190
|
+
export async function decryptArchive(blob, info, kdfParam, password) {
|
|
191
|
+
if (blob.length < HEADER_LENGTH) {
|
|
192
|
+
throw new SecurityError('TAMPERED', '加密备份容器体积过小(截断或损坏)');
|
|
193
|
+
}
|
|
194
|
+
const magic = Buffer.from(blob.subarray(0, 4)).toString('ascii');
|
|
195
|
+
if (magic !== ARCHIVE_MAGIC) {
|
|
196
|
+
throw new SecurityError('UNSUPPORTED_FORMAT', `未知文件格式 magic "${magic}",不是本插件产物`);
|
|
197
|
+
}
|
|
198
|
+
const version = blob[4];
|
|
199
|
+
if (version !== ARCHIVE_VERSION) {
|
|
200
|
+
throw new SecurityError('UNSUPPORTED_FORMAT', `不支持的加密备份版本 ${version}`);
|
|
201
|
+
}
|
|
202
|
+
const blobSalt = Buffer.from(blob.subarray(5, 5 + SALT_LENGTH));
|
|
203
|
+
const blobIv = Buffer.from(blob.subarray(5 + SALT_LENGTH, 5 + SALT_LENGTH + IV_LENGTH));
|
|
204
|
+
const blobTag = Buffer.from(blob.subarray(5 + SALT_LENGTH + IV_LENGTH, HEADER_LENGTH));
|
|
205
|
+
const paramSalt = Buffer.isBuffer(kdfParam.salt) ? kdfParam.salt : Buffer.from(kdfParam.salt);
|
|
206
|
+
const paramIv = Buffer.isBuffer(kdfParam.iv) ? kdfParam.iv : Buffer.from(kdfParam.iv);
|
|
207
|
+
// 与此时真实的随机参数对齐(参数来自 verify 阶段读取,防调用方换参数)
|
|
208
|
+
if (!blobSalt.equals(paramSalt) ||
|
|
209
|
+
!blobIv.equals(paramIv)) {
|
|
210
|
+
throw new SecurityError('TAMPERED', '加密备份参数不一致(可能被篡改)');
|
|
211
|
+
}
|
|
212
|
+
let key;
|
|
213
|
+
try {
|
|
214
|
+
key = await deriveKey(password, blobSalt, info.kdfParams);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
throw new SecurityError('BAD_PASSWORD', `密钥派生失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
218
|
+
}
|
|
219
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, blobIv);
|
|
220
|
+
decipher.setAuthTag(blobTag);
|
|
221
|
+
try {
|
|
222
|
+
return Buffer.concat([
|
|
223
|
+
decipher.update(blob.subarray(HEADER_LENGTH)),
|
|
224
|
+
decipher.final(),
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
throw new SecurityError('BAD_PASSWORD', '解密认证失败:密码错误或密文被篡改');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 校验加密备份容器并验证密码(只读,零解密返回内容)。
|
|
233
|
+
* - 容器不合法(非 DCA1 / 截断 / 参数异常)→ valid=false + code
|
|
234
|
+
* - 密码正确 → ok=true + 返回随机参数(后续 decryptArchive 用之)
|
|
235
|
+
* - 密码错误 → ok=false + code=BAD_PASSWORD
|
|
236
|
+
*/
|
|
237
|
+
export async function verifyEncryptedBlob(blob, password) {
|
|
238
|
+
if (blob.length < HEADER_LENGTH) {
|
|
239
|
+
return { valid: false, ok: false, info: null, kdf: null, code: 'TAMPERED' };
|
|
240
|
+
}
|
|
241
|
+
const magic = Buffer.from(blob.subarray(0, 4)).toString('ascii');
|
|
242
|
+
if (magic !== ARCHIVE_MAGIC) {
|
|
243
|
+
return { valid: false, ok: false, info: null, kdf: null, code: 'UNSUPPORTED_FORMAT' };
|
|
244
|
+
}
|
|
245
|
+
const version = blob[4];
|
|
246
|
+
if (version !== ARCHIVE_VERSION) {
|
|
247
|
+
return { valid: false, ok: false, info: null, kdf: null, code: 'UNSUPPORTED_FORMAT' };
|
|
248
|
+
}
|
|
249
|
+
const salt = Buffer.from(blob.subarray(5, 5 + SALT_LENGTH));
|
|
250
|
+
const iv = Buffer.from(blob.subarray(5 + SALT_LENGTH, 5 + SALT_LENGTH + IV_LENGTH));
|
|
251
|
+
const tag = Buffer.from(blob.subarray(5 + SALT_LENGTH + IV_LENGTH, HEADER_LENGTH));
|
|
252
|
+
const info = {
|
|
253
|
+
algorithm: 'aes-256-gcm',
|
|
254
|
+
kdf: 'scrypt',
|
|
255
|
+
kdfParams: { ...SCRYPT_PARAMS },
|
|
256
|
+
salt: salt.toString('base64'),
|
|
257
|
+
iv: iv.toString('base64'),
|
|
258
|
+
authTag: tag.toString('base64'),
|
|
259
|
+
version: ARCHIVE_VERSION,
|
|
260
|
+
};
|
|
261
|
+
let key;
|
|
262
|
+
try {
|
|
263
|
+
key = await deriveKey(password, salt, info.kdfParams);
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
return { valid: true, ok: false, info, kdf: { salt, iv }, code: 'BAD_PASSWORD' };
|
|
267
|
+
}
|
|
268
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
269
|
+
decipher.setAuthTag(tag);
|
|
270
|
+
try {
|
|
271
|
+
decipher.update(blob.subarray(HEADER_LENGTH));
|
|
272
|
+
decipher.final();
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return { valid: true, ok: false, info, kdf: { salt, iv }, code: 'BAD_PASSWORD' };
|
|
276
|
+
}
|
|
277
|
+
return { valid: true, ok: true, info, kdf: { salt, iv }, code: null };
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=encryption.js.map
|