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.
- package/AGENTS.md +95 -209
- package/README.en.md +251 -169
- package/README.md +251 -169
- package/doc/ARCHITECTURE.md +108 -0
- package/doc/CLIENT.md +124 -0
- package/doc/DESENSITIZE.md +56 -0
- package/doc/DEVELOPMENT.md +139 -0
- package/doc/IMPACT-MATRIX.md +36 -0
- package/doc/RELEASE.md +93 -0
- package/doc/REQUIREMENTS.md +177 -105
- package/doc/images/chat-query.png +0 -0
- package/doc/images/row-editor.png +0 -0
- package/doc/images/settings-page.png +0 -0
- package/lib/adhoc.mjs +412 -34
- package/lib/capability.mjs +157 -0
- package/lib/client.js +1243 -80
- package/lib/config.mjs +370 -21
- package/lib/i18n.mjs +119 -4
- package/lib/index.mjs +401 -43
- package/lib/mcp.mjs +496 -391
- package/lib/meta.mjs +267 -0
- package/lib/options.mjs +25 -5
- package/lib/policy.mjs +92 -0
- package/lib/toml.mjs +188 -0
- package/lib/tools.mjs +1093 -827
- package/lib/update.mjs +244 -0
- package/package.json +34 -10
- package/scripts/desensitize-check.mjs +10 -3
- package/scripts/desensitize.config.mjs +255 -237
- package/scripts/docs-check.mjs +214 -0
- package/scripts/release-preflight.mjs +180 -0
- package/scripts/render-check.mjs +1366 -843
- package/test/adhoc-toml.test.mjs +365 -0
- package/test/bridge.test.mjs +227 -185
- package/test/capability.test.mjs +231 -0
- package/test/client-format.test.mjs +515 -352
- package/test/configure-options.test.mjs +244 -0
- package/test/configure-ssh.test.mjs +232 -0
- package/test/copy-from.test.mjs +155 -0
- package/test/env-options.test.mjs +456 -0
- package/test/meta.test.mjs +234 -0
- package/test/options.test.mjs +160 -123
- package/test/policy.test.mjs +132 -0
- package/test/row-cache.test.mjs +237 -138
- package/test/ssh-probe.test.mjs +834 -0
- package/test/test-op-fastpath.test.mjs +208 -0
- package/test/toml.test.mjs +352 -0
- package/test/update.test.mjs +270 -0
- package/test/util.test.mjs +17 -0
- package/test/zero-knowledge.test.mjs +120 -78
package/lib/update.mjs
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// dsh-dbhub-live: the plugin's own update check (npm registry, GitHub fallback).
|
|
2
|
+
//
|
|
3
|
+
// This is a small, self-contained network probe used by the settings card: the
|
|
4
|
+
// Host asks "is a newer version of dsh-dbhub-live published?" and publishes the
|
|
5
|
+
// answer as a JSON view field. It is deliberately isolated from the dbhub
|
|
6
|
+
// runtime: no binary, no store, no DSN, no credentials — the ONLY secrets this
|
|
7
|
+
// module could ever see are none (both endpoints are public reads).
|
|
8
|
+
//
|
|
9
|
+
// Contract: `check()` NEVER rejects. Every failure becomes a short `error`
|
|
10
|
+
// string in the snapshot, and a previously known result is kept (stale) instead
|
|
11
|
+
// of being wiped, so a flaky network never turns the UI's version row blank.
|
|
12
|
+
//
|
|
13
|
+
// Dependency direction: a LEAF that imports only ./meta.mjs (semver helpers).
|
|
14
|
+
// lib/index.mjs imports it; nothing imports back.
|
|
15
|
+
|
|
16
|
+
import { PLUGIN_NAME, isNewer, parseSemver } from './meta.mjs'
|
|
17
|
+
|
|
18
|
+
/** Public npm dist-tag endpoint: `{ version }` for the `latest` tag. */
|
|
19
|
+
export const NPM_LATEST_URL = 'https://registry.npmjs.org/' + PLUGIN_NAME + '/latest'
|
|
20
|
+
|
|
21
|
+
/** Public GitHub releases API: `{ tag_name: 'v5.2.0', ... }`. */
|
|
22
|
+
export const GITHUB_LATEST_URL = 'https://api.github.com/repos/mr-mihu/dsh-dbhub-live/releases/latest'
|
|
23
|
+
|
|
24
|
+
/** Per-request budget. Two sources => at most ~2x this for one full check. */
|
|
25
|
+
export const DEFAULT_TIMEOUT_MS = 5000
|
|
26
|
+
|
|
27
|
+
/** Without `force`, a result younger than this is reused without any request. */
|
|
28
|
+
export const DEFAULT_THROTTLE_MS = 30 * 60 * 1000
|
|
29
|
+
|
|
30
|
+
const REQUEST_HEADERS = {
|
|
31
|
+
accept: 'application/json',
|
|
32
|
+
// GitHub's API rejects requests without a User-Agent (undici sets one, but an
|
|
33
|
+
// explicit value names us in their logs).
|
|
34
|
+
'user-agent': PLUGIN_NAME + '-host',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Parse a body into an object, tolerating a raw JSON string (fetch stubs). */
|
|
38
|
+
function bodyObject(body) {
|
|
39
|
+
if (body && typeof body === 'object') return body
|
|
40
|
+
if (typeof body !== 'string') return undefined
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(body)
|
|
43
|
+
return parsed && typeof parsed === 'object' ? parsed : undefined
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return undefined
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extract a version from the npm registry body (`{ version: '5.2.0' }`).
|
|
51
|
+
* Pure; '' for any body without a semver-valid version.
|
|
52
|
+
*/
|
|
53
|
+
export function versionFromNpmBody(body) {
|
|
54
|
+
const obj = bodyObject(body)
|
|
55
|
+
const version = obj && typeof obj.version === 'string' ? obj.version.trim() : ''
|
|
56
|
+
return parseSemver(version) ? version : ''
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract a version from the GitHub release body (`{ tag_name: 'v5.2.0' }`).
|
|
61
|
+
* The release tag convention in this repo is `v<x.y.z>`, so one leading `v` is
|
|
62
|
+
* stripped before validation. Pure; '' for anything else.
|
|
63
|
+
*/
|
|
64
|
+
export function versionFromGithubBody(body) {
|
|
65
|
+
const obj = bodyObject(body)
|
|
66
|
+
const raw = obj && typeof obj.tag_name === 'string' ? obj.tag_name.trim() : ''
|
|
67
|
+
const version = raw.replace(/^[vV]/, '')
|
|
68
|
+
return parseSemver(version) ? version : ''
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Turn any thrown value into a SHORT, sanitized message suitable for the view
|
|
73
|
+
* (the card renders it inline). Never returns a URL: an error message from a
|
|
74
|
+
* fetch stack can embed the request URL, and the view is browser-facing.
|
|
75
|
+
*/
|
|
76
|
+
function shortError(error) {
|
|
77
|
+
if (error && (error.name === 'AbortError' || error.name === 'TimeoutError' || error.code === 'ABORT_ERR')) {
|
|
78
|
+
return 'timeout'
|
|
79
|
+
}
|
|
80
|
+
let text = String((error && error.message) || error || 'unknown error')
|
|
81
|
+
// Keep the low-level cause code (ENOTFOUND/ECONNREFUSED/...): it is the only
|
|
82
|
+
// actionable part of a Node fetch error ("fetch failed").
|
|
83
|
+
const cause = error && error.cause
|
|
84
|
+
const code = cause && typeof cause.code === 'string' ? cause.code : ''
|
|
85
|
+
text = text.replace(/https?:\/\/\S+/g, '').replace(/\s+/g, ' ').trim()
|
|
86
|
+
if (code && !text.includes(code)) text = text ? text + ' (' + code + ')' : code
|
|
87
|
+
if (text.length > 80) text = text.slice(0, 77) + '...'
|
|
88
|
+
return text || 'request failed'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a lazy update checker.
|
|
93
|
+
*
|
|
94
|
+
* @param {object} [options]
|
|
95
|
+
* @param {Function} [options.fetchImpl] fetch-compatible implementation
|
|
96
|
+
* @param {Function} [options.now] clock (ms); injectable for tests
|
|
97
|
+
* @param {string} [options.npmUrl] npm dist-tag endpoint
|
|
98
|
+
* @param {string} [options.githubUrl] GitHub latest-release endpoint
|
|
99
|
+
* @param {number} [options.timeoutMs] per-request budget
|
|
100
|
+
* @param {number} [options.throttleMs] reuse window for non-forced checks
|
|
101
|
+
* @param {string} [options.currentVersion] installed plugin version to compare against
|
|
102
|
+
* @returns {{ check: (force?: boolean) => Promise<object>, snapshot: () => object }}
|
|
103
|
+
*/
|
|
104
|
+
export function createUpdateChecker(options = {}) {
|
|
105
|
+
const fetchImpl = typeof options.fetchImpl === 'function'
|
|
106
|
+
? options.fetchImpl
|
|
107
|
+
: (typeof globalThis.fetch === 'function' ? globalThis.fetch.bind(globalThis) : undefined)
|
|
108
|
+
const now = typeof options.now === 'function' ? options.now : Date.now
|
|
109
|
+
const npmUrl = typeof options.npmUrl === 'string' && options.npmUrl ? options.npmUrl : NPM_LATEST_URL
|
|
110
|
+
const githubUrl = typeof options.githubUrl === 'string' && options.githubUrl
|
|
111
|
+
? options.githubUrl
|
|
112
|
+
: GITHUB_LATEST_URL
|
|
113
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
|
|
114
|
+
? options.timeoutMs
|
|
115
|
+
: DEFAULT_TIMEOUT_MS
|
|
116
|
+
const throttleMs = Number.isFinite(options.throttleMs) && options.throttleMs >= 0
|
|
117
|
+
? options.throttleMs
|
|
118
|
+
: DEFAULT_THROTTLE_MS
|
|
119
|
+
const currentVersion = typeof options.currentVersion === 'string' ? options.currentVersion : ''
|
|
120
|
+
|
|
121
|
+
// Last known result. `latest`/`source` survive a failed re-check on purpose
|
|
122
|
+
// (see the module contract above).
|
|
123
|
+
let state = { latest: '', latestSpec: '', hasUpdate: false, checkedAt: 0, source: '', error: '' }
|
|
124
|
+
let inFlight
|
|
125
|
+
|
|
126
|
+
const snapshot = () => ({ ...state })
|
|
127
|
+
|
|
128
|
+
const safeNow = () => {
|
|
129
|
+
try {
|
|
130
|
+
const value = Number(now())
|
|
131
|
+
return Number.isFinite(value) ? value : Date.now()
|
|
132
|
+
} catch (e) {
|
|
133
|
+
return Date.now()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const throttled = () => state.checkedAt > 0 && safeNow() - state.checkedAt < throttleMs
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* One request, resolved to a parsed JSON body. A per-request AbortController
|
|
141
|
+
* enforces the budget; the timer stays referenced until the request settles
|
|
142
|
+
* (an unref'd timer would let the process drain before it fires) and is
|
|
143
|
+
* always cleared.
|
|
144
|
+
*/
|
|
145
|
+
const requestJson = async (url) => {
|
|
146
|
+
if (typeof fetchImpl !== 'function') throw new Error('fetch unavailable')
|
|
147
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : undefined
|
|
148
|
+
const timer = controller
|
|
149
|
+
? setTimeout(() => {
|
|
150
|
+
try {
|
|
151
|
+
controller.abort()
|
|
152
|
+
} catch (e) {
|
|
153
|
+
/* already aborted */
|
|
154
|
+
}
|
|
155
|
+
}, timeoutMs)
|
|
156
|
+
: undefined
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetchImpl(url, {
|
|
159
|
+
headers: REQUEST_HEADERS,
|
|
160
|
+
signal: controller ? controller.signal : undefined,
|
|
161
|
+
})
|
|
162
|
+
if (!response || typeof response !== 'object') throw new Error('empty response')
|
|
163
|
+
const status = typeof response.status === 'number' ? response.status : 0
|
|
164
|
+
const ok = typeof response.ok === 'boolean'
|
|
165
|
+
? response.ok
|
|
166
|
+
: status === 0 || (status >= 200 && status < 300)
|
|
167
|
+
if (!ok) throw new Error('HTTP ' + (status || 'error'))
|
|
168
|
+
if (typeof response.json === 'function') return await response.json()
|
|
169
|
+
if (typeof response.text === 'function') {
|
|
170
|
+
const text = await response.text()
|
|
171
|
+
return text ? JSON.parse(text) : undefined
|
|
172
|
+
}
|
|
173
|
+
return undefined
|
|
174
|
+
} finally {
|
|
175
|
+
if (timer) clearTimeout(timer)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Try one source; return the version or '' after recording its error. */
|
|
180
|
+
const attempt = async (label, url, extract, errors) => {
|
|
181
|
+
try {
|
|
182
|
+
const version = extract(await requestJson(url))
|
|
183
|
+
if (!version) throw new Error('invalid version in response')
|
|
184
|
+
return version
|
|
185
|
+
} catch (e) {
|
|
186
|
+
errors.push(label + ': ' + shortError(e))
|
|
187
|
+
return ''
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const runCheck = async () => {
|
|
192
|
+
const errors = []
|
|
193
|
+
let latest = ''
|
|
194
|
+
let source = ''
|
|
195
|
+
try {
|
|
196
|
+
latest = await attempt('npm', npmUrl, versionFromNpmBody, errors)
|
|
197
|
+
if (latest) source = 'npm'
|
|
198
|
+
if (!latest) {
|
|
199
|
+
latest = await attempt('github', githubUrl, versionFromGithubBody, errors)
|
|
200
|
+
if (latest) source = 'github'
|
|
201
|
+
}
|
|
202
|
+
} catch (e) {
|
|
203
|
+
// Belt and braces: `attempt` already absorbs its own failures.
|
|
204
|
+
errors.push(shortError(e))
|
|
205
|
+
}
|
|
206
|
+
const checkedAt = safeNow()
|
|
207
|
+
if (latest) {
|
|
208
|
+
state = {
|
|
209
|
+
latest,
|
|
210
|
+
latestSpec: PLUGIN_NAME + '@' + latest,
|
|
211
|
+
hasUpdate: isNewer(latest, currentVersion),
|
|
212
|
+
checkedAt,
|
|
213
|
+
source,
|
|
214
|
+
error: '',
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
state = {
|
|
218
|
+
// Keep whatever we knew: a transient failure is not "no release".
|
|
219
|
+
latest: state.latest,
|
|
220
|
+
latestSpec: state.latestSpec,
|
|
221
|
+
hasUpdate: state.hasUpdate,
|
|
222
|
+
checkedAt,
|
|
223
|
+
source: state.source,
|
|
224
|
+
error: errors.join('; ') || 'no version found',
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return snapshot()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Run (or reuse) the check. Concurrent calls share one in-flight promise, so
|
|
232
|
+
* a double click or a page reload cannot double the request burst.
|
|
233
|
+
*/
|
|
234
|
+
const check = (force) => {
|
|
235
|
+
if (inFlight) return inFlight
|
|
236
|
+
if (!force && throttled()) return Promise.resolve(snapshot())
|
|
237
|
+
inFlight = runCheck().finally(() => {
|
|
238
|
+
inFlight = undefined
|
|
239
|
+
})
|
|
240
|
+
return inFlight
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { check, snapshot }
|
|
244
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dbhub-live",
|
|
3
|
-
"version": "5.0.
|
|
4
|
-
"description": "DSH 数据库 MCP
|
|
3
|
+
"version": "5.2.0-dev.3",
|
|
4
|
+
"description": "DSH 数据库 MCP 插件:把 DBHub 接入 DeepSeek Harness,让 AI 在对话里直接查询 MySQL / PostgreSQL / MariaDB / SQLite / SQL Server。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"lib",
|
|
15
|
-
"doc",
|
|
15
|
+
"doc/REQUIREMENTS.md",
|
|
16
|
+
"doc/ARCHITECTURE.md",
|
|
17
|
+
"doc/DEVELOPMENT.md",
|
|
18
|
+
"doc/CLIENT.md",
|
|
19
|
+
"doc/RELEASE.md",
|
|
20
|
+
"doc/DESENSITIZE.md",
|
|
21
|
+
"doc/IMPACT-MATRIX.md",
|
|
22
|
+
"doc/images",
|
|
16
23
|
"test",
|
|
17
24
|
"scripts",
|
|
18
25
|
"cordis.patch.yml",
|
|
@@ -23,22 +30,36 @@
|
|
|
23
30
|
],
|
|
24
31
|
"keywords": [
|
|
25
32
|
"dsh",
|
|
26
|
-
"
|
|
33
|
+
"dsh-plugin",
|
|
34
|
+
"deepseek-harness",
|
|
27
35
|
"mcp",
|
|
36
|
+
"dbhub",
|
|
28
37
|
"database",
|
|
38
|
+
"sql",
|
|
29
39
|
"mysql",
|
|
40
|
+
"mariadb",
|
|
30
41
|
"postgres",
|
|
42
|
+
"postgresql",
|
|
31
43
|
"sqlite",
|
|
32
|
-
"mariadb",
|
|
33
44
|
"sqlserver",
|
|
45
|
+
"mssql",
|
|
46
|
+
"ai",
|
|
47
|
+
"agent",
|
|
34
48
|
"plugin"
|
|
35
49
|
],
|
|
36
50
|
"scripts": {
|
|
37
|
-
"check": "node --check ./lib/index.mjs && node --check ./lib/bridge.mjs && node --check ./lib/config.mjs && node --check ./lib/state.mjs && node --check ./lib/options.mjs && node --check ./lib/runtime.mjs && node --check ./lib/mcp.mjs && node --check ./lib/adhoc.mjs && node --check ./lib/collect.mjs && node --check ./lib/tools.mjs && node --check ./lib/client.js",
|
|
51
|
+
"check": "node --check ./lib/index.mjs && node --check ./lib/meta.mjs && node --check ./lib/update.mjs && node --check ./lib/bridge.mjs && node --check ./lib/config.mjs && node --check ./lib/i18n.mjs && node --check ./lib/state.mjs && node --check ./lib/options.mjs && node --check ./lib/runtime.mjs && node --check ./lib/mcp.mjs && node --check ./lib/adhoc.mjs && node --check ./lib/collect.mjs && node --check ./lib/tools.mjs && node --check ./lib/policy.mjs && node --check ./lib/toml.mjs && node --check ./lib/capability.mjs && node --check ./lib/client.js",
|
|
38
52
|
"test": "node --test test/",
|
|
39
53
|
"check:ui": "node scripts/render-check.mjs",
|
|
40
54
|
"check:secrets": "node scripts/desensitize-check.mjs",
|
|
41
|
-
"
|
|
55
|
+
"check:docs": "node scripts/docs-check.mjs",
|
|
56
|
+
"test:release": "node release-test/run.mjs --tier declared",
|
|
57
|
+
"test:release:all": "node release-test/run.mjs",
|
|
58
|
+
"release:preflight": "node scripts/release-preflight.mjs",
|
|
59
|
+
"release:verdict": "node release-test/run.mjs verdict --check",
|
|
60
|
+
"support:matrix": "node release-test/support-matrix.mjs --write",
|
|
61
|
+
"support:check": "node release-test/support-matrix.mjs --check",
|
|
62
|
+
"prepublishOnly": "npm run check:docs && npm run check:secrets && npm run release:preflight"
|
|
42
63
|
},
|
|
43
64
|
"dsh": {
|
|
44
65
|
"bundle": {
|
|
@@ -49,16 +70,19 @@
|
|
|
49
70
|
},
|
|
50
71
|
"compatibility": {
|
|
51
72
|
"dshReleases": {
|
|
73
|
+
"0.1.5-rc.3": "compatible",
|
|
52
74
|
"0.1.6-alpha.2": "compatible",
|
|
75
|
+
"0.1.7-rc.2": "compatible",
|
|
53
76
|
"0.2.0-rc.2": "compatible",
|
|
54
|
-
"0.2.1-alpha.1": "compatible"
|
|
77
|
+
"0.2.1-alpha.1": "compatible",
|
|
78
|
+
"0.2.1-rc.2": "compatible"
|
|
55
79
|
}
|
|
56
80
|
}
|
|
57
81
|
},
|
|
58
82
|
"peerDependencies": {
|
|
59
83
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
-
"@deepseek-ai/dsh-client-connection": ">=0.1.5-
|
|
61
|
-
"@deepseek-ai/dsh-settings": ">=0.1.5-
|
|
84
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.5-0 <0.1.6-0 || >=0.1.6-0 <0.1.7-0 || >=0.1.7-0 <0.2.0-0 || >=0.2.0-0 <0.2.1-0 || >=0.2.1-0 <0.3.0-0",
|
|
85
|
+
"@deepseek-ai/dsh-settings": ">=0.1.5-0 <0.1.6-0 || >=0.1.6-0 <0.1.7-0 || >=0.1.7-0 <0.2.0-0 || >=0.2.0-0 <0.2.1-0 || >=0.2.1-0 <0.3.0-0",
|
|
62
86
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
63
87
|
},
|
|
64
88
|
"repository": {
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// * a BACKSTOP over every scanned file (IP literals, secret formats, entropy)
|
|
5
5
|
// so an unregistered new surface is still caught;
|
|
6
6
|
// * the registered SLOTS' targeted rules (credentialed URLs, secret-keyword
|
|
7
|
-
// assignments, internal host suffixes, local
|
|
8
|
-
// values are actually plausible.
|
|
7
|
+
// assignments, internal host suffixes, absolute local paths, local denylist
|
|
8
|
+
// names) where real values are actually plausible.
|
|
9
9
|
//
|
|
10
10
|
// The repository is treated as content that ships publicly (npm tarball and the
|
|
11
11
|
// public repository), so anything that looks like a real host, account,
|
|
@@ -20,7 +20,7 @@ import { dirname, join, relative, sep } from 'node:path'
|
|
|
20
20
|
import { fileURLToPath } from 'node:url'
|
|
21
21
|
import {
|
|
22
22
|
ALL_RULES, BACKSTOP_RULES, DENYLIST_VAR, ENTROPY, IP_ALLOWLIST, INTERNAL_HOST_SUFFIXES,
|
|
23
|
-
MAX_FILE_BYTES, PLACEHOLDER_HOSTS, PLACEHOLDER_PASSWORDS, PLACEHOLDER_VALUE_RE, PRAGMA,
|
|
23
|
+
LOCAL_PATH_RE, MAX_FILE_BYTES, PLACEHOLDER_HOSTS, PLACEHOLDER_PASSWORDS, PLACEHOLDER_VALUE_RE, PRAGMA,
|
|
24
24
|
SECRET_FORMATS, SECRET_KEYWORDS, SKIP_DIRS, SKIP_EXT_RE, SKIP_FILES, SLOTS,
|
|
25
25
|
} from './desensitize.config.mjs'
|
|
26
26
|
|
|
@@ -207,6 +207,13 @@ function checkLine(line, rules, denylist) {
|
|
|
207
207
|
if (hit) add('name', `matches the local denylist ("${hit[0]}")`)
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
if (rules.includes('localpath')) {
|
|
211
|
+
const hit = line.match(LOCAL_PATH_RE)
|
|
212
|
+
if (hit) {
|
|
213
|
+
add('localpath', `"${hit[0]}" looks like an absolute local path — use a placeholder such as <repo>`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
210
217
|
if (rules.includes('entropy')) {
|
|
211
218
|
for (const match of line.matchAll(QUOTED_TOKEN_RE)) {
|
|
212
219
|
const token = match[2]
|