@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,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 敏感字段扫描器(规范 §6 / 设计 §7.2 / core `SecretScanner` 契约的强化实现)。
|
|
3
|
+
*
|
|
4
|
+
* 定位:`ctx.settings.describe({redactSecrets:true})` 已剥离 DSH 已知秘密(设计 §7.2),
|
|
5
|
+
* 本扫描器是**第二道防线**,兜底插件自定义字段、MCP headers、patch config、文件类分区文本。
|
|
6
|
+
*
|
|
7
|
+
* 判定策略(两级,避免误伤/漏报):
|
|
8
|
+
* - 字段名:规范化(小写、去 `_-. ` 分隔符)后做「精确命中 / 敏感后缀 / 敏感前缀」三级匹配;
|
|
9
|
+
* 只剥离**字符串叶值**(对象/数组整体递归,不整块剥离——保证 `credentials: [...]` 状态数组存活)。
|
|
10
|
+
* - 引用豁免:`apiKeyEnv` 等「只存引用名」字段、形如 `DEEPSEEK_API_KEY` 的全大写环境变量名
|
|
11
|
+
* (`isEnvVarName`)以及 `${VAR}` / `{{VAR}}` / `%VAR%` / `$VAR` 模板引用(`isTemplateReference`)
|
|
12
|
+
* 一律保留——值是名字不是秘密。
|
|
13
|
+
* - 值形状启发式(规范 §6「不能只根据固定字段判断」):字段名未命中但值长得像 secret
|
|
14
|
+
* (sk- / JWT / AKIA / GitHub PAT / PEM 私钥 / Bearer)也剥离;**示例/占位形态**(含
|
|
15
|
+
* your/example/xxx/test/<...> 等占位词的 `sk-your-*`、`Bearer example-*`、JWT 教学串)放行。
|
|
16
|
+
* - 两种判定档位(2026-08-21 优化):
|
|
17
|
+
* * 保守档(默认,导出/同步脱敏用):字段名敏感**即**剥离(值形状优先、引用/env 名豁免);
|
|
18
|
+
* * 宽松档(`literalValueOnly`,市场发布扫描用):字段名敏感**且**值像真实字面量凭据
|
|
19
|
+
* (非占位符 / 非类型词 / 非代码表达式 / 非模板引用 / 非短标识符)才命中——消除
|
|
20
|
+
* `"token": "your-token"`、`"token": "${OPENAI_API_KEY}"`、`"Authorization": "Bearer <token>"`
|
|
21
|
+
* 等「值不是秘密」的误报。
|
|
22
|
+
* - 高熵 base64 默认关(误伤率高)。
|
|
23
|
+
*
|
|
24
|
+
* 实现不变量:返回 sanitized 为**全新对象**(不改原数据);迭代式显式栈(防爆栈)+ 深度上限;
|
|
25
|
+
* 命中记录只含路径与字段名,**值永不外泄**。
|
|
26
|
+
*/
|
|
27
|
+
import { JsonDepthError } from '../utils/json.js';
|
|
28
|
+
/** 剥离占位(与 core defaultSecretScanner 语义一致:空串 = 需补录提示) */
|
|
29
|
+
export const REDACTED_PLACEHOLDER = '';
|
|
30
|
+
/**
|
|
31
|
+
* 敏感字段名单(规范化名:小写、无分隔符)。
|
|
32
|
+
* 覆盖规范 §6 原始清单 + 设计 §7.2 + core logger 名单,并保持克制以防误伤
|
|
33
|
+
* (不收录 `key`/`session` 等过宽子串——`sessionIds`/`monkey` 不得中招)。
|
|
34
|
+
*/
|
|
35
|
+
export const DEFAULT_SECRET_FIELD_NAMES = [
|
|
36
|
+
// 规范 §6 原文
|
|
37
|
+
'password', 'passwd', 'token', 'accesstoken', 'refreshtoken',
|
|
38
|
+
'apikey', 'secret', 'credential', 'authorization', 'cookie',
|
|
39
|
+
'privatekey', 'clientsecret',
|
|
40
|
+
// 设计 §7.2 / core logger 扩充
|
|
41
|
+
'pwd', 'passphrase', 'authtoken', 'sessionkey', 'apisecret',
|
|
42
|
+
'authheader', 'bearer', 'webhooksecret',
|
|
43
|
+
];
|
|
44
|
+
/** 引用字段(值只是「名字」而非秘密):规范化精确命中即豁免 */
|
|
45
|
+
export const DEFAULT_REFERENCE_FIELDS = [
|
|
46
|
+
'apikeyenv', 'apikeyname', 'apikeyref',
|
|
47
|
+
'tokenenv', 'accesstokenenv', 'refreshtokenenv',
|
|
48
|
+
'clientsecretenv', 'passwordenv', 'secretref', 'credentialref',
|
|
49
|
+
];
|
|
50
|
+
/** 敏感后缀(规范化后以这些结尾 → 命中;`key` 故意不收,避免 monkey/whiskey 误伤) */
|
|
51
|
+
const SENSITIVE_SUFFIXES = ['token', 'secret', 'password', 'passwd', 'credential'];
|
|
52
|
+
/** 敏感前缀(规范化后以这些开头 → 命中;token/secret 等过宽词不收入) */
|
|
53
|
+
const SENSITIVE_PREFIXES = [
|
|
54
|
+
'apikey', 'accesstoken', 'refreshtoken', 'authtoken',
|
|
55
|
+
'clientsecret', 'privatekey', 'authorization', 'authheader',
|
|
56
|
+
'webhooksecret', 'apisecret',
|
|
57
|
+
];
|
|
58
|
+
export const SECRET_VALUE_PATTERNS = [
|
|
59
|
+
{ name: 'openai-style-key', re: /sk-[A-Za-z0-9_-]{8,}/ },
|
|
60
|
+
{ name: 'jwt', re: /eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/ },
|
|
61
|
+
{ name: 'aws-access-key', re: /AKIA[0-9A-Z]{16}/ },
|
|
62
|
+
{ name: 'github-token', re: /gh[pousr]_[A-Za-z0-9]{20,}/ },
|
|
63
|
+
{ name: 'github-pat', re: /github_pat_[A-Za-z0-9_]{20,}/ },
|
|
64
|
+
{ name: 'pem-private-key', re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----/ },
|
|
65
|
+
{ name: 'bearer-token', re: /Bearer [A-Za-z0-9._~+/=-]{8,}/ },
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* 值形状命中片段中的「示例/占位」词:匹配到的片段含其中任一 → 视为示例形态放行。
|
|
69
|
+
* 覆盖 `sk-your-*`、`sk-example-*`、`sk-test-*`、`Bearer example-*`、`AKIA...EXAMPLE`(AWS 官方示例)、
|
|
70
|
+
* JWT 教学串(第三段 signature)等;真实随机密钥串几乎不可能包含这些完整单词,安全不回退。
|
|
71
|
+
*/
|
|
72
|
+
const SECRET_SHAPE_PLACEHOLDER_WORDS = [
|
|
73
|
+
'your', 'their', 'example', 'sample', 'dummy', 'placeholder',
|
|
74
|
+
'test', 'demo', 'signature', 'secret', 'value', 'xxxx',
|
|
75
|
+
];
|
|
76
|
+
/** 值形状命中是否「示例/占位」形态(含占位词或尖括号包裹)→ 放行 */
|
|
77
|
+
function isExampleSecretShape(matched) {
|
|
78
|
+
if (/<[^>]+>/.test(matched))
|
|
79
|
+
return true;
|
|
80
|
+
const lower = matched.toLowerCase();
|
|
81
|
+
return SECRET_SHAPE_PLACEHOLDER_WORDS.some((w) => lower.includes(w));
|
|
82
|
+
}
|
|
83
|
+
function matchSecretValuePatternHit(value, extraPatterns = []) {
|
|
84
|
+
// 默认内置模式优先,个人化附加模式其次(返回的模式名语义以内置为准,附加模式不抢占)
|
|
85
|
+
for (const p of SECRET_VALUE_PATTERNS) {
|
|
86
|
+
const m = p.re.exec(value);
|
|
87
|
+
if (m !== null)
|
|
88
|
+
return { name: p.name, matched: m[0] };
|
|
89
|
+
}
|
|
90
|
+
for (const p of extraPatterns) {
|
|
91
|
+
const m = p.re.exec(value);
|
|
92
|
+
if (m !== null)
|
|
93
|
+
return { name: p.name, matched: m[0] };
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/** 值是否命中强模式 secret 形状(示例/占位形态 → null;返回命中模式名;未命中返回 null)。
|
|
98
|
+
* extraPatterns 为可选的个人化附加值形状模式(与 SECRET_VALUE_PATTERNS 合并判定,默认内置优先);
|
|
99
|
+
* 单参调用行为与历史完全一致(向后兼容)。 */
|
|
100
|
+
export function matchSecretValuePattern(value, extraPatterns = []) {
|
|
101
|
+
const hit = matchSecretValuePatternHit(value, extraPatterns);
|
|
102
|
+
if (hit === null)
|
|
103
|
+
return null;
|
|
104
|
+
if (isExampleSecretShape(hit.matched))
|
|
105
|
+
return null;
|
|
106
|
+
return hit.name;
|
|
107
|
+
}
|
|
108
|
+
/** 规范化字段名:小写 + 去 `_-. ` 分隔符(用于名单匹配) */
|
|
109
|
+
export function normalizeFieldName(name) {
|
|
110
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
111
|
+
}
|
|
112
|
+
/** 是否为「环境变量名」形态(全大写下划线,如 DEEPSEEK_API_KEY)→ 视为引用名 */
|
|
113
|
+
export function isEnvVarName(value) {
|
|
114
|
+
return /^[A-Z][A-Z0-9_]{2,}$/.test(value);
|
|
115
|
+
}
|
|
116
|
+
/** 是否为模板/环境变量引用形态(${VAR} / {{VAR}} / %VAR% / $VAR)→ 值是名字不是秘密。
|
|
117
|
+
* 注意:文本扫描的 `field: value` 正则用 `[^\s,;}]` 取值,`${...}` / `{{...}}` 的右花括号
|
|
118
|
+
* 会被截断,因此除完整闭合形态外,也接受「以 ${ / {{ / % 开头、内容全为变量名字符」的截断形态。 */
|
|
119
|
+
export function isTemplateReference(value) {
|
|
120
|
+
const v = value.trim();
|
|
121
|
+
// 完整闭合形态
|
|
122
|
+
if (/^\$\{[A-Za-z0-9_.]+\}$/.test(v))
|
|
123
|
+
return true;
|
|
124
|
+
if (/^\{\{[A-Za-z0-9_.]+\}\}$/.test(v))
|
|
125
|
+
return true;
|
|
126
|
+
if (/^%[A-Za-z0-9_.]+%$/.test(v))
|
|
127
|
+
return true;
|
|
128
|
+
if (/^\$[A-Z][A-Z0-9_]{2,}$/.test(v))
|
|
129
|
+
return true;
|
|
130
|
+
// 截断形态(右花括号被取值正则排除):真实凭据不会以 ${ / {{ / % 开头,放行安全
|
|
131
|
+
if (/^\$\{[A-Za-z0-9_.]*$/.test(v))
|
|
132
|
+
return true;
|
|
133
|
+
if (/^\{\{[A-Za-z0-9_.]*$/.test(v))
|
|
134
|
+
return true;
|
|
135
|
+
if (/^%[A-Za-z0-9_.]*$/.test(v))
|
|
136
|
+
return true;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
/** 字段名是否命中敏感名单(规范化后精确 / 敏感后缀 / 敏感前缀) */
|
|
140
|
+
export function isSensitiveFieldName(field, extra = []) {
|
|
141
|
+
const norm = normalizeFieldName(field);
|
|
142
|
+
if (norm === '')
|
|
143
|
+
return false;
|
|
144
|
+
if ([...DEFAULT_SECRET_FIELD_NAMES, ...extra].includes(norm))
|
|
145
|
+
return true;
|
|
146
|
+
if (SENSITIVE_SUFFIXES.some((s) => norm.endsWith(s) && norm.length > s.length))
|
|
147
|
+
return true;
|
|
148
|
+
return SENSITIVE_PREFIXES.some((p) => norm.startsWith(p));
|
|
149
|
+
}
|
|
150
|
+
/** 是否为引用字段(值只是名字,不剥离) */
|
|
151
|
+
export function isReferenceField(field, extra = []) {
|
|
152
|
+
const norm = normalizeFieldName(field);
|
|
153
|
+
return [...DEFAULT_REFERENCE_FIELDS, ...extra].includes(norm);
|
|
154
|
+
}
|
|
155
|
+
/** 高熵启发式:长 base64/hex 串且 Shannon 熵高(默认关) */
|
|
156
|
+
function isHighEntropySecret(value) {
|
|
157
|
+
if (!/^[A-Za-z0-9+/=_-]{32,}$/.test(value))
|
|
158
|
+
return false;
|
|
159
|
+
if (/^[A-Za-z0-9_-]+$/.test(value) && !/[0-9]/.test(value))
|
|
160
|
+
return false; // 纯单词串(如文件名)
|
|
161
|
+
const counts = new Map();
|
|
162
|
+
for (const ch of value)
|
|
163
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
164
|
+
let entropy = 0;
|
|
165
|
+
for (const n of counts.values()) {
|
|
166
|
+
const p = n / value.length;
|
|
167
|
+
entropy -= p * Math.log2(p);
|
|
168
|
+
}
|
|
169
|
+
return entropy > 4.2;
|
|
170
|
+
}
|
|
171
|
+
/* ---------------- 值形态判定(结构化扫描与文本扫描共用) ---------------- */
|
|
172
|
+
/** 占位符 / 示例形态:xxx、your-token、<...>、example、sample、dummy、placeholder、... */
|
|
173
|
+
const PLACEHOLDER_RE = /^(x{3,}|your[-_ ]?[\w]+|their[-_ ]?[\w]+|<[^>]+>|example|examples?|sample|dummy|placeholder|som[e]thing|^\u2026)/i;
|
|
174
|
+
/** 类型/关键字形态(代码声明,如 `authToken: string`、`token: null`) */
|
|
175
|
+
const TYPE_KEYWORD_RE = /^(string|number|boolean|object|array|any|unknown|null|undefined|void|true|false|required|optional)$/i;
|
|
176
|
+
/** 代码表达式/引用形态:值里出现调用/成员访问/引号/运算符/空白等 → 是代码而非字面量密钥 */
|
|
177
|
+
function isCodeExpressionValue(value) {
|
|
178
|
+
// 方法调用 / 成员访问:data.token、process.env.X、wx.getStorageSync(...)、a.b(...)
|
|
179
|
+
if (/[.([\]'"`]/.test(value))
|
|
180
|
+
return true;
|
|
181
|
+
// 赋值/比较/模板:a=b、a&&b 等
|
|
182
|
+
if (/[=+*/%<>!&|?]+/.test(value))
|
|
183
|
+
return true;
|
|
184
|
+
// 含空白 → 短语/语句而非单个字面量
|
|
185
|
+
if (/\s/.test(value))
|
|
186
|
+
return true;
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
/** 混合字符长串是否「疑似真实凭据」:含数字、或含字母数字连字符下划线以外的符号、或大小写混合
|
|
190
|
+
* (纯小写字母+连字符的示例值如 `abc-def-ghi-jkl` → 放行;`s3cretP@ssw0rd` / `abc123-def456` → 拦截) */
|
|
191
|
+
function looksLikeCredential(value) {
|
|
192
|
+
if (value.length < 8)
|
|
193
|
+
return false;
|
|
194
|
+
if (/[0-9]/.test(value))
|
|
195
|
+
return true;
|
|
196
|
+
if (/[^A-Za-z0-9_-]/.test(value))
|
|
197
|
+
return true;
|
|
198
|
+
if (/[a-z]/.test(value) && /[A-Z]/.test(value))
|
|
199
|
+
return true;
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
/** 值是否为「字面量密钥」:既不是代码表达式,也不是占位符/类型词/短标识符/环境引用。
|
|
203
|
+
* 只有字段名敏感 **且** 值像真实字面量凭据时才报告 —— 消除技能文档中
|
|
204
|
+
* `authToken: string`、`password: process.env.X`、`token: data.token` 等代码示例误报。
|
|
205
|
+
* extraPatterns 为个人化附加值形状模式,与内置模式一同参与「值形状强信号」判定。 */
|
|
206
|
+
function isLiteralSecretValue(value, extraPatterns = []) {
|
|
207
|
+
if (value === '')
|
|
208
|
+
return false;
|
|
209
|
+
// 值形状强信号(sk- / ghp_ / AKIA / JWT / PEM / Bearer / 个人化附加形状,示例/占位形态已降噪)—— 任何字段都视为真凭据
|
|
210
|
+
if (matchSecretValuePattern(value, extraPatterns) !== null)
|
|
211
|
+
return true;
|
|
212
|
+
if (PLACEHOLDER_RE.test(value))
|
|
213
|
+
return false;
|
|
214
|
+
if (TYPE_KEYWORD_RE.test(value))
|
|
215
|
+
return false;
|
|
216
|
+
if (isCodeExpressionValue(value))
|
|
217
|
+
return false;
|
|
218
|
+
if (isEnvVarName(value))
|
|
219
|
+
return false;
|
|
220
|
+
if (isTemplateReference(value))
|
|
221
|
+
return false;
|
|
222
|
+
// 纯字母数字短标识符(变量名/普通词,如 authToken、data、key)→ 非密钥
|
|
223
|
+
if (/^[A-Za-z0-9_]+$/.test(value) && value.length < 20)
|
|
224
|
+
return false;
|
|
225
|
+
// 其余:混合字符串,仅当像真实凭据(含数字/符号/大小写混合)才疑似
|
|
226
|
+
return looksLikeCredential(value);
|
|
227
|
+
}
|
|
228
|
+
/** 判定单个「字段名 + 字符串值」:strip | keep(含引用豁免与值形状)
|
|
229
|
+
* 顺序:值形状优先(AKIA 等全大写 secret 形状不能被 env 名豁免误放)→ 引用字段 → env 名 → 字段名。
|
|
230
|
+
* 宽松档(literalValueOnly,市场发布):字段名命中还要求值像真实字面量凭据(非占位/引用/代码)。 */
|
|
231
|
+
function judgeFieldValue(field, value, opts) {
|
|
232
|
+
if (value === '')
|
|
233
|
+
return false;
|
|
234
|
+
if (opts.literalValueOnly) {
|
|
235
|
+
// 宽松档(市场发布扫描):值形状强信号字段无关必拦;字段名敏感 + 值像真实字面量凭据才拦
|
|
236
|
+
if (opts.valuePatterns && matchSecretValuePattern(value, opts.extraValuePatterns) !== null)
|
|
237
|
+
return true;
|
|
238
|
+
if (isSensitiveFieldName(field, opts.extraFieldNames) && isLiteralSecretValue(value, opts.extraValuePatterns))
|
|
239
|
+
return true;
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
// 保守档(导出/同步脱敏):值形状优先 → 引用字段/env 名豁免 → 字段名敏感即剥
|
|
243
|
+
if (opts.valuePatterns && matchSecretValuePattern(value, opts.extraValuePatterns) !== null)
|
|
244
|
+
return true;
|
|
245
|
+
if (isReferenceField(field, opts.extraReferenceFields))
|
|
246
|
+
return false;
|
|
247
|
+
if (isEnvVarName(value))
|
|
248
|
+
return false; // 值是 env 变量名 → 引用
|
|
249
|
+
if (isSensitiveFieldName(field, opts.extraFieldNames))
|
|
250
|
+
return true;
|
|
251
|
+
if (opts.highEntropy && isHighEntropySecret(value))
|
|
252
|
+
return true;
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
/** 递归扫描 + 剥离(纯函数,不改原数据;深度保护;返回清洗后数据与命中清单) */
|
|
256
|
+
export function scanAndRedact(data, opts = {}) {
|
|
257
|
+
const maxDepth = opts.maxDepth ?? 64;
|
|
258
|
+
const cfg = {
|
|
259
|
+
extraFieldNames: opts.extraFieldNames ?? [],
|
|
260
|
+
extraReferenceFields: opts.extraReferenceFields ?? [],
|
|
261
|
+
extraValuePatterns: opts.extraValuePatterns ?? [],
|
|
262
|
+
valuePatterns: opts.valuePatterns ?? true,
|
|
263
|
+
highEntropy: opts.highEntropy ?? false,
|
|
264
|
+
literalValueOnly: opts.literalValueOnly ?? false,
|
|
265
|
+
};
|
|
266
|
+
const hits = [];
|
|
267
|
+
if (data === null || typeof data !== 'object')
|
|
268
|
+
return { sanitized: data, hits };
|
|
269
|
+
if (data instanceof Uint8Array)
|
|
270
|
+
return { sanitized: data, hits };
|
|
271
|
+
const rootOut = Array.isArray(data) ? [] : {};
|
|
272
|
+
const stack = [{ src: data, dst: rootOut, path: '', depth: 1 }];
|
|
273
|
+
const visited = new WeakSet();
|
|
274
|
+
visited.add(data);
|
|
275
|
+
while (stack.length > 0) {
|
|
276
|
+
const frame = stack.pop();
|
|
277
|
+
const { src, dst, path, depth } = frame;
|
|
278
|
+
if (depth > maxDepth)
|
|
279
|
+
throw new JsonDepthError(`扫描深度 ${depth} 超过上限 ${maxDepth}`);
|
|
280
|
+
if (src instanceof Uint8Array)
|
|
281
|
+
continue;
|
|
282
|
+
if (Array.isArray(src)) {
|
|
283
|
+
const outArr = dst;
|
|
284
|
+
for (let i = 0; i < src.length; i++) {
|
|
285
|
+
const v = src[i];
|
|
286
|
+
const childPath = `${path}[${i}]`;
|
|
287
|
+
if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
|
|
288
|
+
if (visited.has(v))
|
|
289
|
+
throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
|
|
290
|
+
visited.add(v);
|
|
291
|
+
const childOut = Array.isArray(v) ? [] : {};
|
|
292
|
+
outArr.push(childOut);
|
|
293
|
+
stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
outArr.push(v);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const outObj = dst;
|
|
302
|
+
for (const [k, v] of Object.entries(src)) {
|
|
303
|
+
const childPath = path === '' ? k : `${path}.${k}`;
|
|
304
|
+
if (typeof v === 'string') {
|
|
305
|
+
if (judgeFieldValue(k, v, cfg)) {
|
|
306
|
+
hits.push({ path: childPath, field: k });
|
|
307
|
+
outObj[k] = REDACTED_PLACEHOLDER;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
outObj[k] = v;
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
|
|
315
|
+
if (visited.has(v))
|
|
316
|
+
throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
|
|
317
|
+
visited.add(v);
|
|
318
|
+
const childOut = Array.isArray(v) ? [] : {};
|
|
319
|
+
outObj[k] = childOut;
|
|
320
|
+
stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
outObj[k] = v;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return { sanitized: rootOut, hits };
|
|
328
|
+
}
|
|
329
|
+
/* ---------------- 文本级扫描(scanText,文件类分区内容用) ---------------- */
|
|
330
|
+
const FIELD_VALUE_RE = [
|
|
331
|
+
// "field": "value"(JSON 形态,保留引号)
|
|
332
|
+
{ re: /"([A-Za-z0-9_.\-]+)"\s*:\s*"([^"]*)"/g, json: true },
|
|
333
|
+
// field=value
|
|
334
|
+
{ re: /([A-Za-z0-9_.\-]+)\s*=\s*([^\s,;]+)/g, json: false },
|
|
335
|
+
// field: value
|
|
336
|
+
{ re: /([A-Za-z0-9_.\-]+)\s*:\s*([^\s,;}]+)/g, json: false },
|
|
337
|
+
];
|
|
338
|
+
/** 文本级扫描:逐行找「敏感字段名 + 疑似真实字面量凭据」与「值形状」命中(只报告,不修改文本)。
|
|
339
|
+
* 2026-08-20 优化:字段名命中不再是充分条件 —— 值必须是「非代码引用 / 非占位符 / 非类型词 /
|
|
340
|
+
* 非短标识符」的字面量,消除技能/代码文档中 `token:`/`password:` 等示例命名的误报;
|
|
341
|
+
* 2026-08-21 优化:值形状(sk-/ghp_/AKIA/JWT/PEM/Bearer)对示例/占位形态(含 your/example/
|
|
342
|
+
* xxx/test/<...> 等占位词)放行;混合字符串需像真实凭据(含数字/符号/大小写混合)才报告。 */
|
|
343
|
+
export function scanText(text, opts = {}) {
|
|
344
|
+
const hits = [];
|
|
345
|
+
const extra = opts.extraFieldNames ?? [];
|
|
346
|
+
const extraPatterns = opts.extraValuePatterns ?? [];
|
|
347
|
+
const valuePatterns = opts.valuePatterns ?? true;
|
|
348
|
+
const lines = text.split(/\r?\n/);
|
|
349
|
+
for (let i = 0; i < lines.length; i++) {
|
|
350
|
+
const line = lines[i];
|
|
351
|
+
const linePath = `line:${i + 1}`;
|
|
352
|
+
// 字段名形态
|
|
353
|
+
for (const { re, json } of FIELD_VALUE_RE) {
|
|
354
|
+
re.lastIndex = 0;
|
|
355
|
+
let m;
|
|
356
|
+
while ((m = re.exec(line)) !== null) {
|
|
357
|
+
const field = m[1];
|
|
358
|
+
const value = m[2];
|
|
359
|
+
if (!isSensitiveFieldName(field, extra))
|
|
360
|
+
continue;
|
|
361
|
+
// 值必须是「疑似字面量凭据」才报告(消除代码示例/占位符误报)
|
|
362
|
+
if (!isLiteralSecretValue(value, extraPatterns))
|
|
363
|
+
continue;
|
|
364
|
+
hits.push({ path: linePath, field });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// 值形状(字段名无关;整行 trim 后匹配 sk-/ghp_/JWT/PEM/Bearer 及个人化附加形状等强信号,示例形态已降噪)
|
|
368
|
+
if (valuePatterns) {
|
|
369
|
+
const trimmed = line.trim();
|
|
370
|
+
const name = matchSecretValuePattern(trimmed, extraPatterns);
|
|
371
|
+
if (name !== null)
|
|
372
|
+
hits.push({ path: linePath, field: `(${name})` });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return hits;
|
|
376
|
+
}
|
|
377
|
+
/** 强化版 SecretScanner(对齐 core SecretScanner 契约,可注入 Exporter.scanner) */
|
|
378
|
+
export function createSecretScanner(opts = {}) {
|
|
379
|
+
return {
|
|
380
|
+
scanAndRedact(data) {
|
|
381
|
+
return scanAndRedact(data, opts);
|
|
382
|
+
},
|
|
383
|
+
scanText(text) {
|
|
384
|
+
return scanText(text, opts);
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/** 从部署配置构造扫描器;未配置(undefined 或全部为空)时返回默认 createSecretScanner(),
|
|
389
|
+
* 行为与默认完全一致。配置了个人化规则时将其作为附加字段名 / 引用字段 / 值形状模式合并进扫描器。 */
|
|
390
|
+
export function createConfiguredSecretScanner(patterns) {
|
|
391
|
+
if (patterns === undefined)
|
|
392
|
+
return createSecretScanner();
|
|
393
|
+
const isEmpty = (patterns.extraFieldNames?.length ?? 0) === 0 &&
|
|
394
|
+
(patterns.extraReferenceFields?.length ?? 0) === 0 &&
|
|
395
|
+
(patterns.extraValuePatterns?.length ?? 0) === 0;
|
|
396
|
+
if (isEmpty)
|
|
397
|
+
return createSecretScanner();
|
|
398
|
+
return createSecretScanner({
|
|
399
|
+
extraFieldNames: patterns.extraFieldNames,
|
|
400
|
+
extraReferenceFields: patterns.extraReferenceFields,
|
|
401
|
+
extraValuePatterns: patterns.extraValuePatterns,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=secret-scanner.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { FileSystemFacade } from '../core/types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* 默认敏感文件清单(相对 $DSH_HOME):当前 DSH 唯一「整文件即秘密」的文件
|
|
4
|
+
* (.credentials.yaml = 凭据明文 token/password/密钥)。
|
|
5
|
+
* 宿主可传入扩展清单(如 profiles/<p>/secrets.yaml、.env 等),默认清单保持最小。
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_SENSITIVE_RELS: readonly string[];
|
|
8
|
+
/** vault 根目录 = <dataDir>/vault */
|
|
9
|
+
export declare function vaultRootOf(dataDir: string): string;
|
|
10
|
+
/** refreshVault 结果 */
|
|
11
|
+
export interface VaultRefreshResult {
|
|
12
|
+
/** 本次实际镜像入库的敏感文件(相对 $DSH_HOME) */
|
|
13
|
+
mirrored: string[];
|
|
14
|
+
/** 因源文件已不存在 / rel 不再属于清单而被清理的旧镜像(相对 $DSH_HOME) */
|
|
15
|
+
cleaned: string[];
|
|
16
|
+
/** 单个文件镜像失败(非致命:读源 / 写镜像异常),reason 为错误消息 */
|
|
17
|
+
skipped: {
|
|
18
|
+
rel: string;
|
|
19
|
+
reason: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
/** restoreVaultFiles 结果 */
|
|
23
|
+
export interface VaultRestoreResult {
|
|
24
|
+
/** 已从 vault 回填到 $DSH_HOME 的文件(相对 $DSH_HOME) */
|
|
25
|
+
restored: string[];
|
|
26
|
+
/** vault 中缺失、需要用户重填的文件(跨机恢复 vault 为空 → 全部缺失) */
|
|
27
|
+
missing: string[];
|
|
28
|
+
/** 跳过项:reason 为 'targetExists'(目标已存在不覆盖)或错误消息(拷贝失败) */
|
|
29
|
+
skipped: {
|
|
30
|
+
rel: string;
|
|
31
|
+
reason: string;
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
/** listVault 条目 */
|
|
35
|
+
export interface VaultEntry {
|
|
36
|
+
/** 原始敏感文件路径(相对 $DSH_HOME) */
|
|
37
|
+
rel: string;
|
|
38
|
+
/** vault 内镜像的绝对路径 */
|
|
39
|
+
vaultPath: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 刷新 vault:把 $DSH_HOME 下现存敏感文件镜像到 <dataDir>/vault/<rel>。
|
|
43
|
+
* 幂等:重复调用结果一致(同字节覆盖写,不产生重复条目)。
|
|
44
|
+
* 清理:源文件已不存在、或 rel 不再属于传入清单的旧镜像会被删除。
|
|
45
|
+
* 返回实际入库清单(供报告 / 提示「已镜像到本机 vault」)。
|
|
46
|
+
*/
|
|
47
|
+
export declare function refreshVault(fs: FileSystemFacade, dataDir: string, dshHome: string, rels: readonly string[]): Promise<VaultRefreshResult>;
|
|
48
|
+
/**
|
|
49
|
+
* 从 vault 回填敏感文件到 $DSH_HOME(跨机恢复 / 本机误删后的恢复)。
|
|
50
|
+
* 不覆盖已存在的目标文件(安全:避免覆盖目标机更新后的凭据);
|
|
51
|
+
* vault 无对应文件(跨机恢复、从未镜像过)记入 missing,由调用方提示用户重填。
|
|
52
|
+
*/
|
|
53
|
+
export declare function restoreVaultFiles(fs: FileSystemFacade, dataDir: string, dshHome: string, rels: readonly string[]): Promise<VaultRestoreResult>;
|
|
54
|
+
/**
|
|
55
|
+
* 列出 vault 内容(镜像结构 = 相对 $DSH_HOME 的 rel 平铺在 vault 根下,
|
|
56
|
+
* 例如 vault/.credentials.yaml 即 $DSH_HOME/.credentials.yaml 的镜像)。
|
|
57
|
+
* vault 不存在 / 为空时返回空数组。
|
|
58
|
+
*/
|
|
59
|
+
export declare function listVault(fs: FileSystemFacade, dataDir: string, dshHome: string): Promise<VaultEntry[]>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件级 vault(敏感文件本机镜像,设计参考 dsh-backup 的「文件级 vault」思路)。
|
|
3
|
+
*
|
|
4
|
+
* 动机:字段级 Secret 扫描只能剥离结构化数据里的敏感值;像 .credentials.yaml 这类
|
|
5
|
+
* 「整文件即秘密」的文件,字节明文不进归档、不进同步,而是只镜像到本机
|
|
6
|
+
* <dataDir>/vault/ 目录(vault 目录本身绝不进入导出/同步,与 self 分区白名单互斥),
|
|
7
|
+
* 恢复(如跨机导入)时从 vault 回填 $DSH_HOME;vault 无对应文件(跨机恢复、
|
|
8
|
+
* 从未镜像过)则提示用户重填。比字段级扫描更彻底(整文件隔离)。
|
|
9
|
+
*
|
|
10
|
+
* 纯函数式设计:fs 一律经 FileSystemFacade 参数注入(对齐 core 层解耦原则),
|
|
11
|
+
* 不直接 import node:fs;node 环境可用内存 mock 全量测试。
|
|
12
|
+
* 路径约定:dshHome / dataDir 为绝对路径;敏感文件清单 rel 一律为相对 $DSH_HOME
|
|
13
|
+
* 的斜杠分隔相对路径(与 FileSystemFacade.listRecursive 的 home-relative 产出一致)。
|
|
14
|
+
*
|
|
15
|
+
* 安全不变量:
|
|
16
|
+
* - vault 内容 = 敏感文件明文,仅存在于本机 dataDir,永不进入 ZIP / 同步 / 日志;
|
|
17
|
+
* - rel 入参必须通过 isPathSafe(拒绝绝对路径 / .. / 盘符 / NUL),防路径穿越;
|
|
18
|
+
* - 恢复不覆盖已存在的目标文件(避免覆盖目标机更新后的凭据),缺失项记入 missing。
|
|
19
|
+
*
|
|
20
|
+
* 权限说明:FileSystemFacade 无 chmod 能力(宿主 mkdir 已递归),vault 目录权限
|
|
21
|
+
* 跟随宿主 dataDir 既有策略;Windows 不强制 POSIX 权限位。
|
|
22
|
+
*/
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { isPathSafe, normalizePath } from '../utils/paths.js';
|
|
25
|
+
/**
|
|
26
|
+
* 默认敏感文件清单(相对 $DSH_HOME):当前 DSH 唯一「整文件即秘密」的文件
|
|
27
|
+
* (.credentials.yaml = 凭据明文 token/password/密钥)。
|
|
28
|
+
* 宿主可传入扩展清单(如 profiles/<p>/secrets.yaml、.env 等),默认清单保持最小。
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_SENSITIVE_RELS = [
|
|
31
|
+
'.credentials.yaml',
|
|
32
|
+
];
|
|
33
|
+
/** vault 根目录 = <dataDir>/vault */
|
|
34
|
+
export function vaultRootOf(dataDir) {
|
|
35
|
+
return path.join(dataDir, 'vault');
|
|
36
|
+
}
|
|
37
|
+
/** rel 必须为安全相对路径(拒绝绝对路径 / .. / 盘符 / NUL),防路径穿越 */
|
|
38
|
+
function assertSafeRel(rel) {
|
|
39
|
+
if (!isPathSafe(rel))
|
|
40
|
+
throw new Error(`vault: 非法敏感文件路径 "${rel}"`);
|
|
41
|
+
}
|
|
42
|
+
/** 校验 + 归一化 rel 清单(统一 / 分隔,与 FileSystemFacade.listRecursive 产出对齐) */
|
|
43
|
+
function normalizeRels(rels) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const rel of rels) {
|
|
46
|
+
assertSafeRel(rel);
|
|
47
|
+
out.push(normalizePath(rel));
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 刷新 vault:把 $DSH_HOME 下现存敏感文件镜像到 <dataDir>/vault/<rel>。
|
|
53
|
+
* 幂等:重复调用结果一致(同字节覆盖写,不产生重复条目)。
|
|
54
|
+
* 清理:源文件已不存在、或 rel 不再属于传入清单的旧镜像会被删除。
|
|
55
|
+
* 返回实际入库清单(供报告 / 提示「已镜像到本机 vault」)。
|
|
56
|
+
*/
|
|
57
|
+
export async function refreshVault(fs, dataDir, dshHome, rels) {
|
|
58
|
+
const normRels = normalizeRels(rels);
|
|
59
|
+
const vaultRoot = vaultRootOf(dataDir);
|
|
60
|
+
await fs.mkdir(vaultRoot); // 宿主 mkdir 递归;失败向上抛(调用方按尽力而为处理)
|
|
61
|
+
const mirrored = [];
|
|
62
|
+
const skipped = [];
|
|
63
|
+
// 1. 镜像现存敏感文件(源不存在则跳过;读/写失败按文件记录,不中断整体)
|
|
64
|
+
for (const rel of normRels) {
|
|
65
|
+
const src = path.join(dshHome, rel);
|
|
66
|
+
const dst = path.join(vaultRoot, rel);
|
|
67
|
+
try {
|
|
68
|
+
if (!(await fs.exists(src)))
|
|
69
|
+
continue;
|
|
70
|
+
const data = await fs.readFile(src);
|
|
71
|
+
await fs.writeFile(dst, data);
|
|
72
|
+
mirrored.push(rel);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
skipped.push({ rel, reason: err instanceof Error ? err.message : String(err) });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// 2. 清理旧镜像:源已不存在 或 rel 不再属于清单(listRecursive 产出为 home-relative)
|
|
79
|
+
const cleaned = [];
|
|
80
|
+
try {
|
|
81
|
+
const vaultFiles = await fs.listRecursive(vaultRoot);
|
|
82
|
+
for (const homeRel of vaultFiles) {
|
|
83
|
+
const vaultRel = normalizePath(path.relative(vaultRoot, path.join(dshHome, homeRel)));
|
|
84
|
+
if (vaultRel === '' || vaultRel.startsWith('..') || path.isAbsolute(vaultRel))
|
|
85
|
+
continue; // 防御:理论上不会发生
|
|
86
|
+
if (!normRels.includes(vaultRel)) {
|
|
87
|
+
await fs.remove(path.join(vaultRoot, vaultRel));
|
|
88
|
+
cleaned.push(vaultRel);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!(await fs.exists(path.join(dshHome, vaultRel)))) {
|
|
92
|
+
await fs.remove(path.join(vaultRoot, vaultRel));
|
|
93
|
+
cleaned.push(vaultRel);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// 清理是尽力而为:listRecursive / remove 失败不影响镜像结果
|
|
99
|
+
}
|
|
100
|
+
return { mirrored, cleaned, skipped };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 从 vault 回填敏感文件到 $DSH_HOME(跨机恢复 / 本机误删后的恢复)。
|
|
104
|
+
* 不覆盖已存在的目标文件(安全:避免覆盖目标机更新后的凭据);
|
|
105
|
+
* vault 无对应文件(跨机恢复、从未镜像过)记入 missing,由调用方提示用户重填。
|
|
106
|
+
*/
|
|
107
|
+
export async function restoreVaultFiles(fs, dataDir, dshHome, rels) {
|
|
108
|
+
const normRels = normalizeRels(rels);
|
|
109
|
+
const vaultRoot = vaultRootOf(dataDir);
|
|
110
|
+
const restored = [];
|
|
111
|
+
const missing = [];
|
|
112
|
+
const skipped = [];
|
|
113
|
+
for (const rel of normRels) {
|
|
114
|
+
const vaultPath = path.join(vaultRoot, rel);
|
|
115
|
+
const target = path.join(dshHome, rel);
|
|
116
|
+
try {
|
|
117
|
+
if (!(await fs.exists(vaultPath))) {
|
|
118
|
+
missing.push(rel);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (await fs.exists(target)) {
|
|
122
|
+
skipped.push({ rel, reason: 'targetExists' });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
await fs.copy(vaultPath, target);
|
|
126
|
+
restored.push(rel);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
skipped.push({ rel, reason: err instanceof Error ? err.message : String(err) });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { restored, missing, skipped };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 列出 vault 内容(镜像结构 = 相对 $DSH_HOME 的 rel 平铺在 vault 根下,
|
|
136
|
+
* 例如 vault/.credentials.yaml 即 $DSH_HOME/.credentials.yaml 的镜像)。
|
|
137
|
+
* vault 不存在 / 为空时返回空数组。
|
|
138
|
+
*/
|
|
139
|
+
export async function listVault(fs, dataDir, dshHome) {
|
|
140
|
+
const vaultRoot = vaultRootOf(dataDir);
|
|
141
|
+
const homeRels = await fs.listRecursive(vaultRoot); // home-relative;vault 不存在时宿主返回 []
|
|
142
|
+
const entries = [];
|
|
143
|
+
for (const homeRel of homeRels) {
|
|
144
|
+
const rel = normalizePath(path.relative(vaultRoot, path.join(dshHome, homeRel)));
|
|
145
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel))
|
|
146
|
+
continue; // 防御
|
|
147
|
+
entries.push({ rel, vaultPath: path.join(vaultRoot, rel) });
|
|
148
|
+
}
|
|
149
|
+
return entries.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=vault.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ZipArchive, type ZipSafetyLimits } from '../utils/zip.ts';
|
|
2
|
+
export { isPathSafe } from '../utils/paths.ts';
|
|
3
|
+
/** 可执行文件扩展名黑名单(§19.6:只警告,本插件不执行任何脚本) */
|
|
4
|
+
export declare const EXECUTABLE_EXTENSIONS: Set<string>;
|
|
5
|
+
/** 条目名是否疑似可执行文件(按扩展名) */
|
|
6
|
+
export declare function isExecutableName(name: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* 强化版 ZIP 解析:在 core 全部安全校验(条目名/条目数/压缩体积)之上,
|
|
9
|
+
* 增加 symlink 拒绝与重复条目名拒绝。返回 core `ZipArchive`(解压校验完全复用)。
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseZipHardened(buf: Uint8Array, limits?: ZipSafetyLimits): ZipArchive;
|
|
12
|
+
/** 工厂:带默认限额的强化解析器(对齐 ImporterOptions.parseZipOverride 签名) */
|
|
13
|
+
export declare function createHardenedZipParser(defaultLimits?: ZipSafetyLimits): (buf: Uint8Array, limits?: ZipSafetyLimits) => ZipArchive;
|
|
14
|
+
export interface SafeExtractResult {
|
|
15
|
+
/** 已解压条目的 ZIP 相对路径(正斜杠) */
|
|
16
|
+
files: string[];
|
|
17
|
+
/** 非阻塞告警(可执行文件条目等) */
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 安全解压到受控目录(对齐 core safeExtract 语义 + 强化):
|
|
22
|
+
* - 条目名/限额/symlink/重复名检查(parseZipHardened);
|
|
23
|
+
* - 逐条 CRC32/尺寸/预算校验(ZipArchive.readEntry);
|
|
24
|
+
* - 解压后 lstat 复查「全部产物必须是普通文件」(防符号链接/非常规写入);
|
|
25
|
+
* - 任何异常 → 中止并**完整清理** destDir 后抛出(不残留部分落盘)。
|
|
26
|
+
*/
|
|
27
|
+
export declare function safeExtractHardened(zipPath: string, destDir: string, limits?: ZipSafetyLimits): Promise<SafeExtractResult>;
|