@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,445 @@
|
|
|
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.ts';
|
|
28
|
+
import type { SecretScanner, SensitiveHit } from '../core/types.ts';
|
|
29
|
+
|
|
30
|
+
/** 剥离占位(与 core defaultSecretScanner 语义一致:空串 = 需补录提示) */
|
|
31
|
+
export const REDACTED_PLACEHOLDER = '';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 敏感字段名单(规范化名:小写、无分隔符)。
|
|
35
|
+
* 覆盖规范 §6 原始清单 + 设计 §7.2 + core logger 名单,并保持克制以防误伤
|
|
36
|
+
* (不收录 `key`/`session` 等过宽子串——`sessionIds`/`monkey` 不得中招)。
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_SECRET_FIELD_NAMES: readonly string[] = [
|
|
39
|
+
// 规范 §6 原文
|
|
40
|
+
'password', 'passwd', 'token', 'accesstoken', 'refreshtoken',
|
|
41
|
+
'apikey', 'secret', 'credential', 'authorization', 'cookie',
|
|
42
|
+
'privatekey', 'clientsecret',
|
|
43
|
+
// 设计 §7.2 / core logger 扩充
|
|
44
|
+
'pwd', 'passphrase', 'authtoken', 'sessionkey', 'apisecret',
|
|
45
|
+
'authheader', 'bearer', 'webhooksecret',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/** 引用字段(值只是「名字」而非秘密):规范化精确命中即豁免 */
|
|
49
|
+
export const DEFAULT_REFERENCE_FIELDS: readonly string[] = [
|
|
50
|
+
'apikeyenv', 'apikeyname', 'apikeyref',
|
|
51
|
+
'tokenenv', 'accesstokenenv', 'refreshtokenenv',
|
|
52
|
+
'clientsecretenv', 'passwordenv', 'secretref', 'credentialref',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** 敏感后缀(规范化后以这些结尾 → 命中;`key` 故意不收,避免 monkey/whiskey 误伤) */
|
|
56
|
+
const SENSITIVE_SUFFIXES = ['token', 'secret', 'password', 'passwd', 'credential'] as const;
|
|
57
|
+
|
|
58
|
+
/** 敏感前缀(规范化后以这些开头 → 命中;token/secret 等过宽词不收入) */
|
|
59
|
+
const SENSITIVE_PREFIXES = [
|
|
60
|
+
'apikey', 'accesstoken', 'refreshtoken', 'authtoken',
|
|
61
|
+
'clientsecret', 'privatekey', 'authorization', 'authheader',
|
|
62
|
+
'webhooksecret', 'apisecret',
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
/** 值形状强模式(默认开):一眼可辨的密钥/凭据形态(包含式:值内任意位置出现即命中) */
|
|
66
|
+
export interface ValuePattern { name: string; re: RegExp }
|
|
67
|
+
export const SECRET_VALUE_PATTERNS: readonly ValuePattern[] = [
|
|
68
|
+
{ name: 'openai-style-key', re: /sk-[A-Za-z0-9_-]{8,}/ },
|
|
69
|
+
{ name: 'jwt', re: /eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/ },
|
|
70
|
+
{ name: 'aws-access-key', re: /AKIA[0-9A-Z]{16}/ },
|
|
71
|
+
{ name: 'github-token', re: /gh[pousr]_[A-Za-z0-9]{20,}/ },
|
|
72
|
+
{ name: 'github-pat', re: /github_pat_[A-Za-z0-9_]{20,}/ },
|
|
73
|
+
{ name: 'pem-private-key', re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----/ },
|
|
74
|
+
{ name: 'bearer-token', re: /Bearer [A-Za-z0-9._~+/=-]{8,}/ },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 值形状命中片段中的「示例/占位」词:匹配到的片段含其中任一 → 视为示例形态放行。
|
|
79
|
+
* 覆盖 `sk-your-*`、`sk-example-*`、`sk-test-*`、`Bearer example-*`、`AKIA...EXAMPLE`(AWS 官方示例)、
|
|
80
|
+
* JWT 教学串(第三段 signature)等;真实随机密钥串几乎不可能包含这些完整单词,安全不回退。
|
|
81
|
+
*/
|
|
82
|
+
const SECRET_SHAPE_PLACEHOLDER_WORDS = [
|
|
83
|
+
'your', 'their', 'example', 'sample', 'dummy', 'placeholder',
|
|
84
|
+
'test', 'demo', 'signature', 'secret', 'value', 'xxxx',
|
|
85
|
+
] as const;
|
|
86
|
+
|
|
87
|
+
/** 值形状命中是否「示例/占位」形态(含占位词或尖括号包裹)→ 放行 */
|
|
88
|
+
function isExampleSecretShape(matched: string): boolean {
|
|
89
|
+
if (/<[^>]+>/.test(matched)) return true;
|
|
90
|
+
const lower = matched.toLowerCase();
|
|
91
|
+
return SECRET_SHAPE_PLACEHOLDER_WORDS.some((w) => lower.includes(w));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 值形状命中详情(模式名 + 实际匹配片段;示例降噪用) */
|
|
95
|
+
interface ValuePatternHit { name: string; matched: string }
|
|
96
|
+
|
|
97
|
+
function matchSecretValuePatternHit(
|
|
98
|
+
value: string,
|
|
99
|
+
extraPatterns: readonly ValuePattern[] = [],
|
|
100
|
+
): ValuePatternHit | null {
|
|
101
|
+
// 默认内置模式优先,个人化附加模式其次(返回的模式名语义以内置为准,附加模式不抢占)
|
|
102
|
+
for (const p of SECRET_VALUE_PATTERNS) {
|
|
103
|
+
const m = p.re.exec(value);
|
|
104
|
+
if (m !== null) return { name: p.name, matched: m[0] };
|
|
105
|
+
}
|
|
106
|
+
for (const p of extraPatterns) {
|
|
107
|
+
const m = p.re.exec(value);
|
|
108
|
+
if (m !== null) return { name: p.name, matched: m[0] };
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 值是否命中强模式 secret 形状(示例/占位形态 → null;返回命中模式名;未命中返回 null)。
|
|
114
|
+
* extraPatterns 为可选的个人化附加值形状模式(与 SECRET_VALUE_PATTERNS 合并判定,默认内置优先);
|
|
115
|
+
* 单参调用行为与历史完全一致(向后兼容)。 */
|
|
116
|
+
export function matchSecretValuePattern(
|
|
117
|
+
value: string,
|
|
118
|
+
extraPatterns: readonly ValuePattern[] = [],
|
|
119
|
+
): string | null {
|
|
120
|
+
const hit = matchSecretValuePatternHit(value, extraPatterns);
|
|
121
|
+
if (hit === null) return null;
|
|
122
|
+
if (isExampleSecretShape(hit.matched)) return null;
|
|
123
|
+
return hit.name;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface SecretScannerOptions {
|
|
127
|
+
/** 附加敏感字段名(任意原始形态,内部会规范化) */
|
|
128
|
+
extraFieldNames?: string[];
|
|
129
|
+
/** 附加引用字段名(规范化后精确命中即豁免) */
|
|
130
|
+
extraReferenceFields?: string[];
|
|
131
|
+
/**
|
|
132
|
+
* 附加值形状强模式(个人化规则,如昵称/用户名格式;与 SECRET_VALUE_PATTERNS 合并参与值形状判定,
|
|
133
|
+
* 默认内置模式优先;示例/占位形态同样降噪放行)。来自部署配置 personalPatterns,不进开源代码。
|
|
134
|
+
*/
|
|
135
|
+
extraValuePatterns?: ValuePattern[];
|
|
136
|
+
/** 值形状启发式开关(默认 true) */
|
|
137
|
+
valuePatterns?: boolean;
|
|
138
|
+
/** 高熵长串启发式(默认 false:误伤风险高,需显式开启) */
|
|
139
|
+
highEntropy?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* 宽松档(默认 false = 保守档)。
|
|
142
|
+
* 保守档(导出/同步脱敏):字段名敏感即命中,值形状优先、引用/env 名豁免;
|
|
143
|
+
* 宽松档(市场发布扫描):字段名敏感**且**值像真实字面量凭据才命中——占位符 /
|
|
144
|
+
* 模板引用 / 代码表达式 / 类型词 / 短标识符放行,消除「值不是秘密」的误报。
|
|
145
|
+
*/
|
|
146
|
+
literalValueOnly?: boolean;
|
|
147
|
+
/** 最大递归深度(默认 64,与 JSON 深度上限一致) */
|
|
148
|
+
maxDepth?: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 规范化字段名:小写 + 去 `_-. ` 分隔符(用于名单匹配) */
|
|
152
|
+
export function normalizeFieldName(name: string): string {
|
|
153
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** 是否为「环境变量名」形态(全大写下划线,如 DEEPSEEK_API_KEY)→ 视为引用名 */
|
|
157
|
+
export function isEnvVarName(value: string): boolean {
|
|
158
|
+
return /^[A-Z][A-Z0-9_]{2,}$/.test(value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 是否为模板/环境变量引用形态(${VAR} / {{VAR}} / %VAR% / $VAR)→ 值是名字不是秘密。
|
|
162
|
+
* 注意:文本扫描的 `field: value` 正则用 `[^\s,;}]` 取值,`${...}` / `{{...}}` 的右花括号
|
|
163
|
+
* 会被截断,因此除完整闭合形态外,也接受「以 ${ / {{ / % 开头、内容全为变量名字符」的截断形态。 */
|
|
164
|
+
export function isTemplateReference(value: string): boolean {
|
|
165
|
+
const v = value.trim();
|
|
166
|
+
// 完整闭合形态
|
|
167
|
+
if (/^\$\{[A-Za-z0-9_.]+\}$/.test(v)) return true;
|
|
168
|
+
if (/^\{\{[A-Za-z0-9_.]+\}\}$/.test(v)) return true;
|
|
169
|
+
if (/^%[A-Za-z0-9_.]+%$/.test(v)) return true;
|
|
170
|
+
if (/^\$[A-Z][A-Z0-9_]{2,}$/.test(v)) return true;
|
|
171
|
+
// 截断形态(右花括号被取值正则排除):真实凭据不会以 ${ / {{ / % 开头,放行安全
|
|
172
|
+
if (/^\$\{[A-Za-z0-9_.]*$/.test(v)) return true;
|
|
173
|
+
if (/^\{\{[A-Za-z0-9_.]*$/.test(v)) return true;
|
|
174
|
+
if (/^%[A-Za-z0-9_.]*$/.test(v)) return true;
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** 字段名是否命中敏感名单(规范化后精确 / 敏感后缀 / 敏感前缀) */
|
|
179
|
+
export function isSensitiveFieldName(
|
|
180
|
+
field: string,
|
|
181
|
+
extra: readonly string[] = [],
|
|
182
|
+
): boolean {
|
|
183
|
+
const norm = normalizeFieldName(field);
|
|
184
|
+
if (norm === '') return false;
|
|
185
|
+
if ([...DEFAULT_SECRET_FIELD_NAMES, ...extra].includes(norm)) return true;
|
|
186
|
+
if (SENSITIVE_SUFFIXES.some((s) => norm.endsWith(s) && norm.length > s.length)) return true;
|
|
187
|
+
return SENSITIVE_PREFIXES.some((p) => norm.startsWith(p));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** 是否为引用字段(值只是名字,不剥离) */
|
|
191
|
+
export function isReferenceField(
|
|
192
|
+
field: string,
|
|
193
|
+
extra: readonly string[] = [],
|
|
194
|
+
): boolean {
|
|
195
|
+
const norm = normalizeFieldName(field);
|
|
196
|
+
return [...DEFAULT_REFERENCE_FIELDS, ...extra].includes(norm);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** 高熵启发式:长 base64/hex 串且 Shannon 熵高(默认关) */
|
|
200
|
+
function isHighEntropySecret(value: string): boolean {
|
|
201
|
+
if (!/^[A-Za-z0-9+/=_-]{32,}$/.test(value)) return false;
|
|
202
|
+
if (/^[A-Za-z0-9_-]+$/.test(value) && !/[0-9]/.test(value)) return false; // 纯单词串(如文件名)
|
|
203
|
+
const counts = new Map<string, number>();
|
|
204
|
+
for (const ch of value) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
205
|
+
let entropy = 0;
|
|
206
|
+
for (const n of counts.values()) {
|
|
207
|
+
const p = n / value.length;
|
|
208
|
+
entropy -= p * Math.log2(p);
|
|
209
|
+
}
|
|
210
|
+
return entropy > 4.2;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ---------------- 值形态判定(结构化扫描与文本扫描共用) ---------------- */
|
|
214
|
+
|
|
215
|
+
/** 占位符 / 示例形态:xxx、your-token、<...>、example、sample、dummy、placeholder、... */
|
|
216
|
+
const PLACEHOLDER_RE = /^(x{3,}|your[-_ ]?[\w]+|their[-_ ]?[\w]+|<[^>]+>|example|examples?|sample|dummy|placeholder|som[e]thing|^\u2026)/i;
|
|
217
|
+
|
|
218
|
+
/** 类型/关键字形态(代码声明,如 `authToken: string`、`token: null`) */
|
|
219
|
+
const TYPE_KEYWORD_RE = /^(string|number|boolean|object|array|any|unknown|null|undefined|void|true|false|required|optional)$/i;
|
|
220
|
+
|
|
221
|
+
/** 代码表达式/引用形态:值里出现调用/成员访问/引号/运算符/空白等 → 是代码而非字面量密钥 */
|
|
222
|
+
function isCodeExpressionValue(value: string): boolean {
|
|
223
|
+
// 方法调用 / 成员访问:data.token、process.env.X、wx.getStorageSync(...)、a.b(...)
|
|
224
|
+
if (/[.([\]'"`]/.test(value)) return true;
|
|
225
|
+
// 赋值/比较/模板:a=b、a&&b 等
|
|
226
|
+
if (/[=+*/%<>!&|?]+/.test(value)) return true;
|
|
227
|
+
// 含空白 → 短语/语句而非单个字面量
|
|
228
|
+
if (/\s/.test(value)) return true;
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** 混合字符长串是否「疑似真实凭据」:含数字、或含字母数字连字符下划线以外的符号、或大小写混合
|
|
233
|
+
* (纯小写字母+连字符的示例值如 `abc-def-ghi-jkl` → 放行;`s3cretP@ssw0rd` / `abc123-def456` → 拦截) */
|
|
234
|
+
function looksLikeCredential(value: string): boolean {
|
|
235
|
+
if (value.length < 8) return false;
|
|
236
|
+
if (/[0-9]/.test(value)) return true;
|
|
237
|
+
if (/[^A-Za-z0-9_-]/.test(value)) return true;
|
|
238
|
+
if (/[a-z]/.test(value) && /[A-Z]/.test(value)) return true;
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** 值是否为「字面量密钥」:既不是代码表达式,也不是占位符/类型词/短标识符/环境引用。
|
|
243
|
+
* 只有字段名敏感 **且** 值像真实字面量凭据时才报告 —— 消除技能文档中
|
|
244
|
+
* `authToken: string`、`password: process.env.X`、`token: data.token` 等代码示例误报。
|
|
245
|
+
* extraPatterns 为个人化附加值形状模式,与内置模式一同参与「值形状强信号」判定。 */
|
|
246
|
+
function isLiteralSecretValue(value: string, extraPatterns: readonly ValuePattern[] = []): boolean {
|
|
247
|
+
if (value === '') return false;
|
|
248
|
+
// 值形状强信号(sk- / ghp_ / AKIA / JWT / PEM / Bearer / 个人化附加形状,示例/占位形态已降噪)—— 任何字段都视为真凭据
|
|
249
|
+
if (matchSecretValuePattern(value, extraPatterns) !== null) return true;
|
|
250
|
+
if (PLACEHOLDER_RE.test(value)) return false;
|
|
251
|
+
if (TYPE_KEYWORD_RE.test(value)) return false;
|
|
252
|
+
if (isCodeExpressionValue(value)) return false;
|
|
253
|
+
if (isEnvVarName(value)) return false;
|
|
254
|
+
if (isTemplateReference(value)) return false;
|
|
255
|
+
// 纯字母数字短标识符(变量名/普通词,如 authToken、data、key)→ 非密钥
|
|
256
|
+
if (/^[A-Za-z0-9_]+$/.test(value) && value.length < 20) return false;
|
|
257
|
+
// 其余:混合字符串,仅当像真实凭据(含数字/符号/大小写混合)才疑似
|
|
258
|
+
return looksLikeCredential(value);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 判定单个「字段名 + 字符串值」:strip | keep(含引用豁免与值形状)
|
|
262
|
+
* 顺序:值形状优先(AKIA 等全大写 secret 形状不能被 env 名豁免误放)→ 引用字段 → env 名 → 字段名。
|
|
263
|
+
* 宽松档(literalValueOnly,市场发布):字段名命中还要求值像真实字面量凭据(非占位/引用/代码)。 */
|
|
264
|
+
function judgeFieldValue(field: string, value: string, opts: Required<Pick<SecretScannerOptions, 'extraFieldNames' | 'extraReferenceFields' | 'extraValuePatterns' | 'valuePatterns' | 'highEntropy' | 'literalValueOnly'>>): boolean {
|
|
265
|
+
if (value === '') return false;
|
|
266
|
+
if (opts.literalValueOnly) {
|
|
267
|
+
// 宽松档(市场发布扫描):值形状强信号字段无关必拦;字段名敏感 + 值像真实字面量凭据才拦
|
|
268
|
+
if (opts.valuePatterns && matchSecretValuePattern(value, opts.extraValuePatterns) !== null) return true;
|
|
269
|
+
if (isSensitiveFieldName(field, opts.extraFieldNames) && isLiteralSecretValue(value, opts.extraValuePatterns)) return true;
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
// 保守档(导出/同步脱敏):值形状优先 → 引用字段/env 名豁免 → 字段名敏感即剥
|
|
273
|
+
if (opts.valuePatterns && matchSecretValuePattern(value, opts.extraValuePatterns) !== null) return true;
|
|
274
|
+
if (isReferenceField(field, opts.extraReferenceFields)) return false;
|
|
275
|
+
if (isEnvVarName(value)) return false; // 值是 env 变量名 → 引用
|
|
276
|
+
if (isSensitiveFieldName(field, opts.extraFieldNames)) return true;
|
|
277
|
+
if (opts.highEntropy && isHighEntropySecret(value)) return true;
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** 递归扫描 + 剥离(纯函数,不改原数据;深度保护;返回清洗后数据与命中清单) */
|
|
282
|
+
export function scanAndRedact(data: unknown, opts: SecretScannerOptions = {}): { sanitized: unknown; hits: SensitiveHit[] } {
|
|
283
|
+
const maxDepth = opts.maxDepth ?? 64;
|
|
284
|
+
const cfg: Required<Pick<SecretScannerOptions, 'extraFieldNames' | 'extraReferenceFields' | 'extraValuePatterns' | 'valuePatterns' | 'highEntropy' | 'literalValueOnly'>> = {
|
|
285
|
+
extraFieldNames: opts.extraFieldNames ?? [],
|
|
286
|
+
extraReferenceFields: opts.extraReferenceFields ?? [],
|
|
287
|
+
extraValuePatterns: opts.extraValuePatterns ?? [],
|
|
288
|
+
valuePatterns: opts.valuePatterns ?? true,
|
|
289
|
+
highEntropy: opts.highEntropy ?? false,
|
|
290
|
+
literalValueOnly: opts.literalValueOnly ?? false,
|
|
291
|
+
};
|
|
292
|
+
const hits: SensitiveHit[] = [];
|
|
293
|
+
|
|
294
|
+
if (data === null || typeof data !== 'object') return { sanitized: data, hits };
|
|
295
|
+
if (data instanceof Uint8Array) return { sanitized: data, hits };
|
|
296
|
+
|
|
297
|
+
const rootOut: unknown = Array.isArray(data) ? [] : {};
|
|
298
|
+
interface Frame { src: unknown; dst: unknown; path: string; depth: number }
|
|
299
|
+
const stack: Frame[] = [{ src: data, dst: rootOut, path: '', depth: 1 }];
|
|
300
|
+
const visited = new WeakSet<object>();
|
|
301
|
+
visited.add(data as object);
|
|
302
|
+
|
|
303
|
+
while (stack.length > 0) {
|
|
304
|
+
const frame = stack.pop()!;
|
|
305
|
+
const { src, dst, path, depth } = frame;
|
|
306
|
+
if (depth > maxDepth) throw new JsonDepthError(`扫描深度 ${depth} 超过上限 ${maxDepth}`);
|
|
307
|
+
if (src instanceof Uint8Array) continue;
|
|
308
|
+
|
|
309
|
+
if (Array.isArray(src)) {
|
|
310
|
+
const outArr = dst as unknown[];
|
|
311
|
+
for (let i = 0; i < src.length; i++) {
|
|
312
|
+
const v = src[i];
|
|
313
|
+
const childPath = `${path}[${i}]`;
|
|
314
|
+
if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
|
|
315
|
+
if (visited.has(v)) throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
|
|
316
|
+
visited.add(v);
|
|
317
|
+
const childOut: unknown = Array.isArray(v) ? [] : {};
|
|
318
|
+
outArr.push(childOut);
|
|
319
|
+
stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
|
|
320
|
+
} else {
|
|
321
|
+
outArr.push(v);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const outObj = dst as Record<string, unknown>;
|
|
328
|
+
for (const [k, v] of Object.entries(src as Record<string, unknown>)) {
|
|
329
|
+
const childPath = path === '' ? k : `${path}.${k}`;
|
|
330
|
+
if (typeof v === 'string') {
|
|
331
|
+
if (judgeFieldValue(k, v, cfg)) {
|
|
332
|
+
hits.push({ path: childPath, field: k });
|
|
333
|
+
outObj[k] = REDACTED_PLACEHOLDER;
|
|
334
|
+
} else {
|
|
335
|
+
outObj[k] = v;
|
|
336
|
+
}
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
|
|
340
|
+
if (visited.has(v)) throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
|
|
341
|
+
visited.add(v);
|
|
342
|
+
const childOut: unknown = Array.isArray(v) ? [] : {};
|
|
343
|
+
outObj[k] = childOut;
|
|
344
|
+
stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
|
|
345
|
+
} else {
|
|
346
|
+
outObj[k] = v;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return { sanitized: rootOut, hits };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* ---------------- 文本级扫描(scanText,文件类分区内容用) ---------------- */
|
|
355
|
+
|
|
356
|
+
const FIELD_VALUE_RE = [
|
|
357
|
+
// "field": "value"(JSON 形态,保留引号)
|
|
358
|
+
{ re: /"([A-Za-z0-9_.\-]+)"\s*:\s*"([^"]*)"/g, json: true },
|
|
359
|
+
// field=value
|
|
360
|
+
{ re: /([A-Za-z0-9_.\-]+)\s*=\s*([^\s,;]+)/g, json: false },
|
|
361
|
+
// field: value
|
|
362
|
+
{ re: /([A-Za-z0-9_.\-]+)\s*:\s*([^\s,;}]+)/g, json: false },
|
|
363
|
+
] as const;
|
|
364
|
+
|
|
365
|
+
/** 文本级扫描:逐行找「敏感字段名 + 疑似真实字面量凭据」与「值形状」命中(只报告,不修改文本)。
|
|
366
|
+
* 2026-08-20 优化:字段名命中不再是充分条件 —— 值必须是「非代码引用 / 非占位符 / 非类型词 /
|
|
367
|
+
* 非短标识符」的字面量,消除技能/代码文档中 `token:`/`password:` 等示例命名的误报;
|
|
368
|
+
* 2026-08-21 优化:值形状(sk-/ghp_/AKIA/JWT/PEM/Bearer)对示例/占位形态(含 your/example/
|
|
369
|
+
* xxx/test/<...> 等占位词)放行;混合字符串需像真实凭据(含数字/符号/大小写混合)才报告。 */
|
|
370
|
+
export function scanText(text: string, opts: SecretScannerOptions = {}): SensitiveHit[] {
|
|
371
|
+
const hits: SensitiveHit[] = [];
|
|
372
|
+
const extra = opts.extraFieldNames ?? [];
|
|
373
|
+
const extraPatterns = opts.extraValuePatterns ?? [];
|
|
374
|
+
const valuePatterns = opts.valuePatterns ?? true;
|
|
375
|
+
const lines = text.split(/\r?\n/);
|
|
376
|
+
|
|
377
|
+
for (let i = 0; i < lines.length; i++) {
|
|
378
|
+
const line = lines[i]!;
|
|
379
|
+
const linePath = `line:${i + 1}`;
|
|
380
|
+
// 字段名形态
|
|
381
|
+
for (const { re, json } of FIELD_VALUE_RE) {
|
|
382
|
+
re.lastIndex = 0;
|
|
383
|
+
let m: RegExpExecArray | null;
|
|
384
|
+
while ((m = re.exec(line)) !== null) {
|
|
385
|
+
const field = m[1]!;
|
|
386
|
+
const value = m[2]!;
|
|
387
|
+
if (!isSensitiveFieldName(field, extra)) continue;
|
|
388
|
+
// 值必须是「疑似字面量凭据」才报告(消除代码示例/占位符误报)
|
|
389
|
+
if (!isLiteralSecretValue(value, extraPatterns)) continue;
|
|
390
|
+
hits.push({ path: linePath, field });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// 值形状(字段名无关;整行 trim 后匹配 sk-/ghp_/JWT/PEM/Bearer 及个人化附加形状等强信号,示例形态已降噪)
|
|
394
|
+
if (valuePatterns) {
|
|
395
|
+
const trimmed = line.trim();
|
|
396
|
+
const name = matchSecretValuePattern(trimmed, extraPatterns);
|
|
397
|
+
if (name !== null) hits.push({ path: linePath, field: `(${name})` });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return hits;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** 强化版 SecretScanner(对齐 core SecretScanner 契约,可注入 Exporter.scanner) */
|
|
404
|
+
export function createSecretScanner(opts: SecretScannerOptions = {}): SecretScanner {
|
|
405
|
+
return {
|
|
406
|
+
scanAndRedact(data: unknown): { sanitized: unknown; hits: SensitiveHit[] } {
|
|
407
|
+
return scanAndRedact(data, opts);
|
|
408
|
+
},
|
|
409
|
+
scanText(text: string): SensitiveHit[] {
|
|
410
|
+
return scanText(text, opts);
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* 部署配置(F2 个人隐私规则注入,对齐 dsh-packer config.personalPatterns 设计):
|
|
417
|
+
* 通用隐私规则开源内置(DEFAULT_SECRET_FIELD_NAMES / SECRET_VALUE_PATTERNS 等),
|
|
418
|
+
* 个人化规则(昵称/用户名等)由部署者注入,不进开源代码。
|
|
419
|
+
*/
|
|
420
|
+
export interface ConfiguredSecretPatterns {
|
|
421
|
+
/** 附加敏感字段名(个人账号/昵称等字段) */
|
|
422
|
+
extraFieldNames?: string[];
|
|
423
|
+
/** 附加引用字段名(值只是名字,如引用环境变量名的个人字段) */
|
|
424
|
+
extraReferenceFields?: string[];
|
|
425
|
+
/** 附加值形状强模式(个人昵称/用户名等一眼可辨格式) */
|
|
426
|
+
extraValuePatterns?: ValuePattern[];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** 从部署配置构造扫描器;未配置(undefined 或全部为空)时返回默认 createSecretScanner(),
|
|
430
|
+
* 行为与默认完全一致。配置了个人化规则时将其作为附加字段名 / 引用字段 / 值形状模式合并进扫描器。 */
|
|
431
|
+
export function createConfiguredSecretScanner(
|
|
432
|
+
patterns: ConfiguredSecretPatterns | undefined,
|
|
433
|
+
): SecretScanner {
|
|
434
|
+
if (patterns === undefined) return createSecretScanner();
|
|
435
|
+
const isEmpty =
|
|
436
|
+
(patterns.extraFieldNames?.length ?? 0) === 0 &&
|
|
437
|
+
(patterns.extraReferenceFields?.length ?? 0) === 0 &&
|
|
438
|
+
(patterns.extraValuePatterns?.length ?? 0) === 0;
|
|
439
|
+
if (isEmpty) return createSecretScanner();
|
|
440
|
+
return createSecretScanner({
|
|
441
|
+
extraFieldNames: patterns.extraFieldNames,
|
|
442
|
+
extraReferenceFields: patterns.extraReferenceFields,
|
|
443
|
+
extraValuePatterns: patterns.extraValuePatterns,
|
|
444
|
+
});
|
|
445
|
+
}
|