dsh-dbhub-live 5.0.1 → 5.2.0-dev.3

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.
Files changed (50) hide show
  1. package/AGENTS.md +95 -209
  2. package/README.en.md +251 -169
  3. package/README.md +251 -169
  4. package/doc/ARCHITECTURE.md +108 -0
  5. package/doc/CLIENT.md +124 -0
  6. package/doc/DESENSITIZE.md +56 -0
  7. package/doc/DEVELOPMENT.md +139 -0
  8. package/doc/IMPACT-MATRIX.md +36 -0
  9. package/doc/RELEASE.md +93 -0
  10. package/doc/REQUIREMENTS.md +177 -105
  11. package/doc/images/chat-query.png +0 -0
  12. package/doc/images/row-editor.png +0 -0
  13. package/doc/images/settings-page.png +0 -0
  14. package/lib/adhoc.mjs +412 -34
  15. package/lib/capability.mjs +157 -0
  16. package/lib/client.js +1243 -80
  17. package/lib/config.mjs +370 -21
  18. package/lib/i18n.mjs +119 -4
  19. package/lib/index.mjs +401 -43
  20. package/lib/mcp.mjs +496 -391
  21. package/lib/meta.mjs +267 -0
  22. package/lib/options.mjs +25 -5
  23. package/lib/policy.mjs +92 -0
  24. package/lib/toml.mjs +188 -0
  25. package/lib/tools.mjs +1093 -827
  26. package/lib/update.mjs +244 -0
  27. package/package.json +34 -10
  28. package/scripts/desensitize-check.mjs +10 -3
  29. package/scripts/desensitize.config.mjs +255 -237
  30. package/scripts/docs-check.mjs +214 -0
  31. package/scripts/release-preflight.mjs +180 -0
  32. package/scripts/render-check.mjs +1366 -843
  33. package/test/adhoc-toml.test.mjs +365 -0
  34. package/test/bridge.test.mjs +227 -185
  35. package/test/capability.test.mjs +231 -0
  36. package/test/client-format.test.mjs +515 -352
  37. package/test/configure-options.test.mjs +244 -0
  38. package/test/configure-ssh.test.mjs +232 -0
  39. package/test/copy-from.test.mjs +155 -0
  40. package/test/env-options.test.mjs +456 -0
  41. package/test/meta.test.mjs +234 -0
  42. package/test/options.test.mjs +160 -123
  43. package/test/policy.test.mjs +132 -0
  44. package/test/row-cache.test.mjs +237 -138
  45. package/test/ssh-probe.test.mjs +834 -0
  46. package/test/test-op-fastpath.test.mjs +208 -0
  47. package/test/toml.test.mjs +352 -0
  48. package/test/update.test.mjs +270 -0
  49. package/test/util.test.mjs +17 -0
  50. package/test/zero-knowledge.test.mjs +120 -78
@@ -1,237 +1,255 @@
1
- // dsh-dbhub-live — desensitization slot registry (single source of truth).
2
- //
3
- // Every place a real host, account, password, database or project name COULD be
4
- // written down is a *slot*. The gate `npm run check:secrets` resolves each slot
5
- // against this registry, so the check is high-precision at the places we know
6
- // and still has a generic backstop for places nobody registered yet.
7
- //
8
- // The registry is verified: a slot whose globs match no file fails the gate
9
- // (`slot-stale`), so it cannot silently rot while the tree moves.
10
- //
11
- // Two rule sets:
12
- // * backstop — runs on EVERY scanned file (unknown surfaces): IP literals,
13
- // secret formats, high-entropy strings.
14
- // * slot rules — run inside the registered slots only (targeted, lower noise):
15
- // credentialed-URL password/host, secret-keyword assignments, internal host
16
- // suffixes, and the local denylist of bare names.
17
- //
18
- // Rules for editing content (not just for the gate):
19
- // * example host -> 127.0.0.1, localhost, or an RFC 5737 range
20
- // (192.0.2.x / 198.51.100.x / 203.0.113.x)
21
- // * example password -> CHANGE_ME
22
- // * test fixtures -> short placeholders are fine (`u:p`, `root:secret`),
23
- // but the host still obeys the example-host rule
24
- // * real values -> ONLY $DSH_HOME/storages/dsh-dbhub-live/credentials.json
25
- // (outside the repository) and the local .env denylist
26
- //
27
- // Adding a feature that introduces a new example, default, copy string or
28
- // fixture means adding/updating its slot HERE in the same change (AGENTS.md
29
- // "质量门" states the obligation).
30
-
31
- /** Placeholder passwords allowed in an example or fixture DSN. */
32
- export const PLACEHOLDER_PASSWORDS = [
33
- 'CHANGE_ME',
34
- 'changeme',
35
- 'your-password',
36
- '<password>',
37
- '****',
38
- 'p',
39
- 'pass',
40
- 'password',
41
- 'secret',
42
- 'secret.pw',
43
- 'pw',
44
- ]
45
-
46
- /** Placeholder hostnames allowed in an example or fixture DSN. */
47
- export const PLACEHOLDER_HOSTS = [
48
- 'localhost',
49
- 'host',
50
- 'h',
51
- 'db',
52
- 'dbhost',
53
- 'mysql',
54
- 'postgres',
55
- 'x',
56
- 'alice',
57
- 'bob',
58
- 'u',
59
- 'user',
60
- 'root',
61
- 'evil',
62
- 'nope',
63
- 'example.com',
64
- 'db.example.com',
65
- ]
66
-
67
- /**
68
- * IP literals that may appear in the repository. Anything else is a real
69
- * address and must be replaced by a loopback or RFC 5737 documentation range.
70
- */
71
- export const IP_ALLOWLIST = [
72
- { label: 'IPv4 loopback 127.0.0.0/8', test: /^127\./ },
73
- { label: 'IPv4 bind-any 0.0.0.0', test: /^0\.0\.0\.0$/ },
74
- { label: 'RFC 5737 documentation 192.0.2.0/24', test: /^192\.0\.2\./ },
75
- { label: 'RFC 5737 documentation 198.51.100.0/24', test: /^198\.51\.100\./ },
76
- { label: 'RFC 5737 documentation 203.0.113.0/24', test: /^203\.0\.113\./ },
77
- { label: 'IPv6 loopback ::1', test: /^::1$/ },
78
- { label: 'IPv6 unspecified ::', test: /^::$/ },
79
- ]
80
-
81
- /** Hostname suffixes that reveal an internal/private network. */
82
- export const INTERNAL_HOST_SUFFIXES = [
83
- '.local',
84
- '.internal',
85
- '.intranet',
86
- '.lan',
87
- '.corp',
88
- '.intra',
89
- '.home',
90
- '.localdomain',
91
- ]
92
-
93
- /** Property names whose assigned literal must be a placeholder or a reference. */
94
- export const SECRET_KEYWORDS = [
95
- 'password',
96
- 'passwd',
97
- 'pwd',
98
- 'secret',
99
- 'token',
100
- 'auth_token',
101
- 'access_token',
102
- 'api_key',
103
- 'apikey',
104
- 'access_key',
105
- 'private_key',
106
- 'client_secret',
107
- ]
108
-
109
- /** Values that are obviously not a real secret (references or placeholders). */
110
- export const PLACEHOLDER_VALUE_RE =
111
- /^(\$\{?[A-Za-z_][A-Za-z0-9_]*\}?|process\.env\.[A-Za-z0-9_]+|env\.[A-Za-z0-9_]+|CHANGE_ME|changeme|your-password|<[^>]+>|\*+|-+|''|""|)$/
112
-
113
- /** Well-known credential formats: never legitimate in this repository. */
114
- export const SECRET_FORMATS = [
115
- { label: 'GitHub token', re: /\bgh[pousr]_[A-Za-z0-9]{20,}/ },
116
- { label: 'npm token', re: /\bnpm_[A-Za-z0-9]{30,}/ },
117
- { label: 'OpenAI-style key', re: /\bsk-[A-Za-z0-9]{20,}/ },
118
- { label: 'Slack token', re: /\bxox[baprs]-[A-Za-z0-9-]{10,}/ },
119
- { label: 'AWS access key id', re: /\bAKIA[0-9A-Z]{16}\b/ },
120
- { label: 'JWT', re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\./ },
121
- { label: 'PEM private key', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
122
- { label: 'Google API key', re: /\bAIza[0-9A-Za-z_-]{35}\b/ },
123
- ]
124
-
125
- /**
126
- * High-entropy literal detection (catches random passwords nobody listed, e.g.
127
- * the short random strings this repository used to keep in its blacklist).
128
- * Only quoted single tokens are considered; hashes and ids are allowlisted.
129
- */
130
- export const ENTROPY = {
131
- minLength: 12,
132
- minBitsPerChar: 3.5,
133
- /**
134
- * Structural requirement before entropy even applies: a random credential
135
- * mixes letter case or digits, while ordinary code identifiers and copy
136
- * strings are single-case words (`dbhub_configure`, `dbhub_search_objects`,
137
- * `/plugins/dsh-dbhub-live/client.js`).
138
- */
139
- requireMixed: true,
140
- /** Tokens that are structurally not secrets. */
141
- allowPatterns: [
142
- /^[0-9a-fA-F]{16,}$/, // hex digest / sha fragment
143
- /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/, // uuid
144
- /^[A-Za-z0-9]+\.(mjs|js|json|md|yml|yaml|ts|tsx|png|tgz)$/, // file name
145
- /^[a-z0-9]+(?:[-_][a-z0-9]+)+$/, // lowercase slug / identifier (kebab or snake)
146
- /^Icon[A-Za-z0-9]*$/, // platform icon component name (IconCopyOutline16, IconChevronDownOutline14)
147
- /^[A-Za-z0-9_.-]+=[^\s]*$/, // `KEY=value` fragment (env/config line)
148
- /^[A-Za-z0-9_-]+:[A-Za-z0-9_./-]+$/, // `key: value` fragment
149
- /^https?/, // url fragment
150
- /^[A-Za-z0-9_./-]*[./][A-Za-z0-9_./-]*$/, // path-like token (contains . or /)
151
- /^[A-Z][A-Z0-9_]{7,}$/, // SCREAMING_SNAKE env/const name
152
- /^[A-Za-z]+$/, // a single word
153
- ],
154
- }
155
-
156
- /** Inline escape hatch: a line carrying this marker is skipped. */
157
- export const PRAGMA = 'desensitize:allow'
158
-
159
- /** Rules the checker understands. */
160
- export const ALL_RULES = ['ip', 'secret', 'entropy', 'password', 'dsn', 'host', 'name']
161
-
162
- /**
163
- * Backstop rules: applied to every scanned file, including files no slot
164
- * registers yet — they are cheap and high-signal (IP literals, credential
165
- * formats, entropy, internal hostnames, the local denylist of bare names).
166
- */
167
- export const BACKSTOP_RULES = ['ip', 'secret', 'entropy', 'host', 'name']
168
-
169
- /**
170
- * Registered slots. `globs` are repository-relative and must match at least one
171
- * file. `rules` are the targeted rules applied inside the slot.
172
- */
173
- export const SLOTS = [
174
- {
175
- id: 'host-copy',
176
- description: '宿主侧模型可见文案:工具描述、i18n 结果与标签(会打进 npm 包、直接进模型上下文)',
177
- globs: ['lib/i18n.mjs', 'lib/tools.mjs', 'lib/index.mjs', 'lib/mcp.mjs', 'lib/state.mjs', 'lib/bridge.mjs'],
178
- rules: ['password', 'dsn', 'host', 'name'],
179
- },
180
- {
181
- id: 'client-copy',
182
- description: '浏览器半区文案与 UI 占位符(设置页/插件页展示,会打进 npm 包)',
183
- globs: ['lib/client.js'],
184
- rules: ['password', 'dsn', 'host', 'name'],
185
- },
186
- {
187
- id: 'connection-code',
188
- description: '连接构造与解析代码:不得有硬编码 host/user/password 默认值',
189
- globs: ['lib/config.mjs', 'lib/adhoc.mjs', 'lib/options.mjs', 'lib/runtime.mjs'],
190
- rules: ['password', 'dsn', 'host', 'name'],
191
- },
192
- {
193
- id: 'scan-code',
194
- description: '项目文件扫描器:只允许文件名模式,不得出现具体路径/库名',
195
- globs: ['lib/collect.mjs'],
196
- rules: ['password', 'host', 'name'],
197
- },
198
- {
199
- id: 'tests',
200
- description: '测试夹具:DSN/IP/账号只能是回环或 RFC 5737 文档段 + 占位符',
201
- globs: ['test/**/*.mjs'],
202
- rules: ['password', 'dsn', 'host', 'name'],
203
- },
204
- {
205
- id: 'docs',
206
- description: '用户文档示例:安装、配置、故障排查里的连接示例',
207
- globs: ['README.md', 'README.en.md', 'doc/**/*.md'],
208
- rules: ['password', 'dsn', 'host', 'name'],
209
- },
210
- {
211
- id: 'package-meta',
212
- description: '包元数据与 bundle patch:description/keywords 不得含内部项目名',
213
- globs: ['package.json', 'cordis.patch.yml'],
214
- rules: ['password', 'host', 'name'],
215
- },
216
- {
217
- id: 'agent-prompt',
218
- description: '开发规范(提示词):只写规则与变量名,绝不写真实值',
219
- globs: ['AGENTS.md'],
220
- rules: ['password', 'dsn', 'host', 'name'],
221
- },
222
- {
223
- id: 'tooling',
224
- description: '仓库维护脚本自身(不含黑名单值;.env 由 .gitignore 排除、不参与扫描)',
225
- globs: ['scripts/*.mjs'],
226
- rules: ['password', 'host'],
227
- },
228
- ]
229
-
230
- /** The local denylist variable read from the gitignored `.env`. */
231
- export const DENYLIST_VAR = 'DSH_DBHUB_DESENSITIZE_RE'
232
-
233
- /** Paths the walk never enters and files it never reads. */
234
- export const SKIP_DIRS = ['.git', 'node_modules', '.dsh-test', 'dist', 'coverage']
235
- export const SKIP_FILES = ['.env', '.env.local']
236
- export const SKIP_EXT_RE = /\.(tgz|png|jpe?g|gif|webp|ico|zip|gz|exe|dll|node|woff2?|pdf)$/i
237
- export const MAX_FILE_BYTES = 2 * 1024 * 1024
1
+ // dsh-dbhub-live — desensitization slot registry (single source of truth).
2
+ //
3
+ // Every place a real host, account, password, database or project name COULD be
4
+ // written down is a *slot*. The gate `npm run check:secrets` resolves each slot
5
+ // against this registry, so the check is high-precision at the places we know
6
+ // and still has a generic backstop for places nobody registered yet.
7
+ //
8
+ // The registry is verified: a slot whose globs match no file fails the gate
9
+ // (`slot-stale`), so it cannot silently rot while the tree moves.
10
+ //
11
+ // Two rule sets:
12
+ // * backstop — runs on EVERY scanned file (unknown surfaces): IP literals,
13
+ // secret formats, high-entropy strings.
14
+ // * slot rules — run inside the registered slots only (targeted, lower noise):
15
+ // credentialed-URL password/host, secret-keyword assignments, internal host
16
+ // suffixes, absolute local paths, and the local denylist of bare names.
17
+ //
18
+ // Rules for editing content (not just for the gate):
19
+ // * example host -> 127.0.0.1, localhost, or an RFC 5737 range
20
+ // (192.0.2.x / 198.51.100.x / 203.0.113.x)
21
+ // * example password -> CHANGE_ME
22
+ // * test fixtures -> short placeholders are fine (`u:p`, `root:secret`),
23
+ // but the host still obeys the example-host rule
24
+ // * real values -> ONLY $DSH_HOME/storages/dsh-dbhub-live/credentials.json
25
+ // (outside the repository) and the local .env denylist
26
+ //
27
+ // Adding a feature that introduces a new example, default, copy string or
28
+ // fixture means adding/updating its slot HERE in the same change (AGENTS.md
29
+ // "质量门" states the obligation).
30
+
31
+ /** Placeholder passwords allowed in an example or fixture DSN. */
32
+ export const PLACEHOLDER_PASSWORDS = [
33
+ 'CHANGE_ME',
34
+ 'changeme',
35
+ 'your-password',
36
+ '<password>',
37
+ '****',
38
+ 'p',
39
+ 'pass',
40
+ 'password',
41
+ 'secret',
42
+ 'secret.pw',
43
+ 'pw',
44
+ ]
45
+
46
+ /** Placeholder hostnames allowed in an example or fixture DSN. */
47
+ export const PLACEHOLDER_HOSTS = [
48
+ 'localhost',
49
+ 'host',
50
+ 'h',
51
+ 'db',
52
+ 'dbhost',
53
+ 'mysql',
54
+ 'postgres',
55
+ 'x',
56
+ 'alice',
57
+ 'bob',
58
+ 'u',
59
+ 'user',
60
+ 'root',
61
+ 'evil',
62
+ 'nope',
63
+ 'example.com',
64
+ 'db.example.com',
65
+ ]
66
+
67
+ /**
68
+ * IP literals that may appear in the repository. Anything else is a real
69
+ * address and must be replaced by a loopback or RFC 5737 documentation range.
70
+ */
71
+ export const IP_ALLOWLIST = [
72
+ { label: 'IPv4 loopback 127.0.0.0/8', test: /^127\./ },
73
+ { label: 'IPv4 bind-any 0.0.0.0', test: /^0\.0\.0\.0$/ },
74
+ { label: 'RFC 5737 documentation 192.0.2.0/24', test: /^192\.0\.2\./ },
75
+ { label: 'RFC 5737 documentation 198.51.100.0/24', test: /^198\.51\.100\./ },
76
+ { label: 'RFC 5737 documentation 203.0.113.0/24', test: /^203\.0\.113\./ },
77
+ { label: 'IPv6 loopback ::1', test: /^::1$/ },
78
+ { label: 'IPv6 unspecified ::', test: /^::$/ },
79
+ ]
80
+
81
+ /** Hostname suffixes that reveal an internal/private network. */
82
+ export const INTERNAL_HOST_SUFFIXES = [
83
+ '.local',
84
+ '.internal',
85
+ '.intranet',
86
+ '.lan',
87
+ '.corp',
88
+ '.intra',
89
+ '.home',
90
+ '.localdomain',
91
+ ]
92
+
93
+ /** Property names whose assigned literal must be a placeholder or a reference. */
94
+ export const SECRET_KEYWORDS = [
95
+ 'password',
96
+ 'passwd',
97
+ 'pwd',
98
+ 'secret',
99
+ 'token',
100
+ 'auth_token',
101
+ 'access_token',
102
+ 'api_key',
103
+ 'apikey',
104
+ 'access_key',
105
+ 'private_key',
106
+ 'client_secret',
107
+ ]
108
+
109
+ /** Values that are obviously not a real secret (references or placeholders). */
110
+ export const PLACEHOLDER_VALUE_RE =
111
+ /^(\$\{?[A-Za-z_][A-Za-z0-9_]*\}?|process\.env\.[A-Za-z0-9_]+|env\.[A-Za-z0-9_]+|CHANGE_ME|changeme|your-password|<[^>]+>|\*+|-+|''|""|)$/
112
+
113
+ /** Well-known credential formats: never legitimate in this repository. */
114
+ export const SECRET_FORMATS = [
115
+ { label: 'GitHub token', re: /\bgh[pousr]_[A-Za-z0-9]{20,}/ },
116
+ { label: 'npm token', re: /\bnpm_[A-Za-z0-9]{30,}/ },
117
+ { label: 'OpenAI-style key', re: /\bsk-[A-Za-z0-9]{20,}/ },
118
+ { label: 'Slack token', re: /\bxox[baprs]-[A-Za-z0-9-]{10,}/ },
119
+ { label: 'AWS access key id', re: /\bAKIA[0-9A-Z]{16}\b/ },
120
+ { label: 'JWT', re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\./ },
121
+ { label: 'PEM private key', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
122
+ { label: 'Google API key', re: /\bAIza[0-9A-Za-z_-]{35}\b/ },
123
+ ]
124
+
125
+ /**
126
+ * High-entropy literal detection (catches random passwords nobody listed, e.g.
127
+ * the short random strings this repository used to keep in its blacklist).
128
+ * Only quoted single tokens are considered; hashes and ids are allowlisted.
129
+ */
130
+ export const ENTROPY = {
131
+ minLength: 12,
132
+ minBitsPerChar: 3.5,
133
+ /**
134
+ * Structural requirement before entropy even applies: a random credential
135
+ * mixes letter case or digits, while ordinary code identifiers and copy
136
+ * strings are single-case words (`dbhub_configure`, `dbhub_search_objects`,
137
+ * `/plugins/dsh-dbhub-live/client.js`).
138
+ */
139
+ requireMixed: true,
140
+ /** Tokens that are structurally not secrets. */
141
+ allowPatterns: [
142
+ /^[0-9a-fA-F]{16,}$/, // hex digest / sha fragment
143
+ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/, // uuid
144
+ /^[A-Za-z0-9]+\.(mjs|js|json|md|yml|yaml|ts|tsx|png|tgz)$/, // file name
145
+ /^[a-z0-9]+(?:[-_][a-z0-9]+)+$/, // lowercase slug / identifier (kebab or snake)
146
+ /^Icon[A-Za-z0-9]*$/, // platform icon component name (IconCopyOutline16, IconChevronDownOutline14)
147
+ /^[A-Za-z0-9_.-]+=[^\s]*$/, // `KEY=value` fragment (env/config line)
148
+ /^[A-Za-z0-9_-]+:[A-Za-z0-9_./-]+$/, // `key: value` fragment
149
+ /^https?/, // url fragment
150
+ /^[A-Za-z0-9_./-]*[./][A-Za-z0-9_./-]*$/, // path-like token (contains . or /)
151
+ /^[A-Z][A-Z0-9_]{7,}$/, // SCREAMING_SNAKE env/const name
152
+ /^[A-Za-z]+$/, // a single word
153
+ ],
154
+ }
155
+
156
+ /** Inline escape hatch: a line carrying this marker is skipped. */
157
+ export const PRAGMA = 'desensitize:allow'
158
+
159
+ /**
160
+ * Absolute local filesystem paths (a developer machine layout: drive-letter
161
+ * paths and POSIX home directories). The repository ships publicly, so no real
162
+ * machine path may appear in it. Applied as a SLOT rule rather than a backstop:
163
+ * test fixtures legitimately use synthetic drive paths (`C:\\ws\\good`).
164
+ */
165
+ export const LOCAL_PATH_RE = /(?<![A-Za-z])[A-Za-z]:[\\/]|\/(?:Users|home)\/[A-Za-z0-9._-]+\//
166
+
167
+ /** Rules the checker understands. */
168
+ export const ALL_RULES = ['ip', 'secret', 'entropy', 'password', 'dsn', 'host', 'name', 'localpath']
169
+
170
+ /**
171
+ * Backstop rules: applied to every scanned file, including files no slot
172
+ * registers yet — they are cheap and high-signal (IP literals, credential
173
+ * formats, entropy, internal hostnames, the local denylist of bare names).
174
+ */
175
+ export const BACKSTOP_RULES = ['ip', 'secret', 'entropy', 'host', 'name']
176
+
177
+ /**
178
+ * Registered slots. `globs` are repository-relative and must match at least one
179
+ * file. `rules` are the targeted rules applied inside the slot.
180
+ */
181
+ export const SLOTS = [
182
+ {
183
+ id: 'host-copy',
184
+ description: '宿主侧模型可见文案:工具描述、i18n 结果与标签(会打进 npm 包、直接进模型上下文)',
185
+ globs: ['lib/i18n.mjs', 'lib/tools.mjs', 'lib/policy.mjs', 'lib/index.mjs', 'lib/mcp.mjs', 'lib/state.mjs', 'lib/bridge.mjs'],
186
+ rules: ['password', 'dsn', 'host', 'name'],
187
+ },
188
+ {
189
+ id: 'client-copy',
190
+ description: '浏览器半区文案与 UI 占位符(设置页/插件页展示,会打进 npm 包)',
191
+ globs: ['lib/client.js'],
192
+ rules: ['password', 'dsn', 'host', 'name'],
193
+ },
194
+ {
195
+ id: 'connection-code',
196
+ description: '连接构造与解析代码:不得有硬编码 host/user/password 默认值',
197
+ globs: ['lib/config.mjs', 'lib/adhoc.mjs', 'lib/options.mjs', 'lib/runtime.mjs', 'lib/toml.mjs', 'lib/capability.mjs'],
198
+ rules: ['password', 'dsn', 'host', 'name'],
199
+ },
200
+ {
201
+ id: 'scan-code',
202
+ description: '项目文件扫描器:只允许文件名模式,不得出现具体路径/库名',
203
+ globs: ['lib/collect.mjs'],
204
+ rules: ['password', 'host', 'name'],
205
+ },
206
+ {
207
+ id: 'tests',
208
+ description: '测试夹具:DSN/IP/账号只能是回环或 RFC 5737 文档段 + 占位符',
209
+ globs: ['test/**/*.mjs'],
210
+ rules: ['password', 'dsn', 'host', 'name'],
211
+ },
212
+ {
213
+ id: 'docs',
214
+ description: '用户文档示例:安装、配置、故障排查里的连接示例(不得含真实机器路径)',
215
+ globs: ['README.md', 'README.en.md', 'doc/**/*.md'],
216
+ rules: ['password', 'dsn', 'host', 'name', 'localpath'],
217
+ },
218
+ {
219
+ id: 'package-meta',
220
+ description: '包元数据与 bundle patch:description/keywords 不得含内部项目名或机器路径',
221
+ globs: ['package.json', 'cordis.patch.yml'],
222
+ rules: ['password', 'host', 'name', 'localpath'],
223
+ },
224
+ {
225
+ id: 'agent-prompt',
226
+ description: '开发规范(提示词):只写规则与变量名,绝不写真实值或本机绝对路径',
227
+ globs: ['AGENTS.md'],
228
+ rules: ['password', 'dsn', 'host', 'name', 'localpath'],
229
+ },
230
+ {
231
+ id: 'tooling',
232
+ description: '仓库维护脚本自身(不含黑名单值;.env 由 .gitignore 排除、不参与扫描)',
233
+ globs: ['scripts/*.mjs'],
234
+ rules: ['password', 'host'],
235
+ },
236
+ ]
237
+
238
+ /** The local denylist variable read from the gitignored `.env`. */
239
+ export const DENYLIST_VAR = 'DSH_DBHUB_DESENSITIZE_RE'
240
+
241
+ /** Paths the walk never enters and files it never reads. */
242
+ export const SKIP_DIRS = ['.git', 'node_modules', '.dsh-test', '.release-test', 'dist', 'coverage']
243
+ // The two local-dev documents are never committed and never published (see
244
+ // package.json → files), so the gate has no reason to scan them: they legitimately
245
+ // carry plan-era placeholders (`REAL_PASSWORD`, `host:port`) that are not shipped
246
+ // anywhere. The published REQUIREMENTS.md stays in scope. NOTE: this list is
247
+ // matched against the BASENAME (see the walker), so entries are file names.
248
+ export const SKIP_FILES = [
249
+ '.env',
250
+ '.env.local',
251
+ 'PLAN-5.1-readonly-and-ssh-tunnel.md',
252
+ 'implementation-notes.md',
253
+ ]
254
+ export const SKIP_EXT_RE = /\.(tgz|png|jpe?g|gif|webp|ico|zip|gz|exe|dll|node|woff2?|pdf)$/i
255
+ export const MAX_FILE_BYTES = 2 * 1024 * 1024