@goplus/agentguard 1.1.14 → 1.1.20
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/README.md +8 -2
- package/dist/adapters/engine.d.ts.map +1 -1
- package/dist/adapters/engine.js +10 -0
- package/dist/adapters/engine.js.map +1 -1
- package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
- package/dist/adapters/openclaw-plugin.js +6 -18
- package/dist/adapters/openclaw-plugin.js.map +1 -1
- package/dist/cli.js +525 -42
- package/dist/cli.js.map +1 -1
- package/dist/cloud/client.d.ts +16 -2
- package/dist/cloud/client.d.ts.map +1 -1
- package/dist/cloud/client.js +56 -9
- package/dist/cloud/client.js.map +1 -1
- package/dist/cloud/openclaw-notify.d.ts +18 -0
- package/dist/cloud/openclaw-notify.d.ts.map +1 -0
- package/dist/cloud/openclaw-notify.js +69 -0
- package/dist/cloud/openclaw-notify.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -1
- package/dist/feed/cron.d.ts +18 -0
- package/dist/feed/cron.d.ts.map +1 -1
- package/dist/feed/cron.js +499 -25
- package/dist/feed/cron.js.map +1 -1
- package/dist/feed/selfcheck.d.ts.map +1 -1
- package/dist/feed/selfcheck.js +470 -23
- package/dist/feed/selfcheck.js.map +1 -1
- package/dist/feed/types.d.ts +7 -8
- package/dist/feed/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/installers.d.ts.map +1 -1
- package/dist/installers.js +101 -2
- package/dist/installers.js.map +1 -1
- package/dist/runtime/evaluator.d.ts.map +1 -1
- package/dist/runtime/evaluator.js +45 -3
- package/dist/runtime/evaluator.js.map +1 -1
- package/dist/runtime/protect.d.ts.map +1 -1
- package/dist/runtime/protect.js +15 -2
- package/dist/runtime/protect.js.map +1 -1
- package/dist/runtime/self-command.d.ts +2 -0
- package/dist/runtime/self-command.d.ts.map +1 -0
- package/dist/runtime/self-command.js +73 -0
- package/dist/runtime/self-command.js.map +1 -0
- package/dist/tests/cli-checkup.test.js +1 -1
- package/dist/tests/cli-checkup.test.js.map +1 -1
- package/dist/tests/cli-connect.test.d.ts +2 -0
- package/dist/tests/cli-connect.test.d.ts.map +1 -0
- package/dist/tests/cli-connect.test.js +326 -0
- package/dist/tests/cli-connect.test.js.map +1 -0
- package/dist/tests/cli-init.test.js +141 -0
- package/dist/tests/cli-init.test.js.map +1 -1
- package/dist/tests/cli-policy.test.js +72 -0
- package/dist/tests/cli-policy.test.js.map +1 -1
- package/dist/tests/cli-subscribe.test.js +295 -2
- package/dist/tests/cli-subscribe.test.js.map +1 -1
- package/dist/tests/feed-cloud.test.js +45 -1
- package/dist/tests/feed-cloud.test.js.map +1 -1
- package/dist/tests/feed-cron.test.js +506 -10
- package/dist/tests/feed-cron.test.js.map +1 -1
- package/dist/tests/feed-selfcheck.test.js +61 -13
- package/dist/tests/feed-selfcheck.test.js.map +1 -1
- package/dist/tests/installer.test.js +69 -0
- package/dist/tests/installer.test.js.map +1 -1
- package/dist/tests/integration.test.js +41 -9
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/runtime-cloud.test.js +148 -0
- package/dist/tests/runtime-cloud.test.js.map +1 -1
- package/openclaw.plugin.json +4 -0
- package/package.json +1 -1
- package/skills/agentguard/SKILL.md +11 -5
package/dist/feed/selfcheck.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.safeRegexTest = safeRegexTest;
|
|
|
13
13
|
exports.globMatch = globMatch;
|
|
14
14
|
const node_fs_1 = require("node:fs");
|
|
15
15
|
const promises_1 = require("node:fs/promises");
|
|
16
|
+
const glob_1 = require("glob");
|
|
16
17
|
const node_os_1 = require("node:os");
|
|
17
18
|
const node_path_1 = require("node:path");
|
|
18
19
|
const hash_js_1 = require("../utils/hash.js");
|
|
@@ -78,6 +79,15 @@ async function runSelfCheckForAdvisory(advisory, options = {}) {
|
|
|
78
79
|
if (advisory.withdrawnAt) {
|
|
79
80
|
return { advisoryId: advisory.id, matchedArtifacts: [], elapsedMs: 0, warnings };
|
|
80
81
|
}
|
|
82
|
+
const matchers = getSelfCheckMatchers(advisory);
|
|
83
|
+
if (matchers.length === 0) {
|
|
84
|
+
return {
|
|
85
|
+
advisoryId: advisory.id,
|
|
86
|
+
matchedArtifacts: [],
|
|
87
|
+
elapsedMs: Date.now() - startedAt,
|
|
88
|
+
warnings,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
81
91
|
const artifacts = await listArtifactsForAdvisory(advisory, options, warnings);
|
|
82
92
|
const cap = options.maxArtifacts ?? 500;
|
|
83
93
|
const considered = artifacts.slice(0, cap);
|
|
@@ -86,7 +96,7 @@ async function runSelfCheckForAdvisory(advisory, options = {}) {
|
|
|
86
96
|
}
|
|
87
97
|
for (const artifact of considered) {
|
|
88
98
|
try {
|
|
89
|
-
const m = await matchArtifact(artifact, advisory.
|
|
99
|
+
const m = await matchArtifact(artifact, advisory.ecosystem, matchers);
|
|
90
100
|
if (m)
|
|
91
101
|
matches.push(m);
|
|
92
102
|
}
|
|
@@ -104,7 +114,7 @@ async function runSelfCheckForAdvisory(advisory, options = {}) {
|
|
|
104
114
|
async function listArtifactsForAdvisory(advisory, options, warnings) {
|
|
105
115
|
const overridePaths = advisory.selfCheck?.inspectPaths?.filter((p) => typeof p === 'string') ?? [];
|
|
106
116
|
if (overridePaths.length > 0) {
|
|
107
|
-
return listExplicitArtifacts(overridePaths);
|
|
117
|
+
return listExplicitArtifacts(advisory.ecosystem, overridePaths, options);
|
|
108
118
|
}
|
|
109
119
|
switch (advisory.ecosystem) {
|
|
110
120
|
case 'skill':
|
|
@@ -128,24 +138,28 @@ async function listArtifactsForAdvisory(advisory, options, warnings) {
|
|
|
128
138
|
return [];
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
|
-
async function listExplicitArtifacts(paths) {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
path
|
|
138
|
-
name: (0, node_path_1.basename)(path),
|
|
139
|
-
bodyPath: firstExisting([
|
|
140
|
-
(0, node_path_1.join)(path, 'SKILL.md'),
|
|
141
|
-
(0, node_path_1.join)(path, 'openclaw.plugin.json'),
|
|
142
|
-
(0, node_path_1.join)(path, 'package.json'),
|
|
143
|
-
(0, node_path_1.join)(path, 'plugin.json'),
|
|
141
|
+
async function listExplicitArtifacts(ecosystem, paths, options) {
|
|
142
|
+
const expanded = await expandInspectPaths(paths);
|
|
143
|
+
if (expanded.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
switch (ecosystem) {
|
|
146
|
+
case 'skill':
|
|
147
|
+
return (await listSkillDirs(expanded)).map((path) => ({
|
|
144
148
|
path,
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
name: (0, node_path_1.basename)(path),
|
|
150
|
+
bodyPath: (0, node_path_1.join)(path, 'SKILL.md'),
|
|
151
|
+
}));
|
|
152
|
+
case 'plugin':
|
|
153
|
+
return listPluginArtifacts(expanded);
|
|
154
|
+
case 'prompt_injection':
|
|
155
|
+
return listPromptInjectionArtifacts({ ...options, promptInjectionRoots: expanded });
|
|
156
|
+
case 'mcp_server':
|
|
157
|
+
case 'supply_chain':
|
|
158
|
+
case 'url':
|
|
159
|
+
return listFileArtifacts(expanded);
|
|
160
|
+
default:
|
|
161
|
+
return [];
|
|
147
162
|
}
|
|
148
|
-
return artifacts;
|
|
149
163
|
}
|
|
150
164
|
/** Enumerate every immediate subdirectory of `roots` that contains a SKILL.md. */
|
|
151
165
|
async function listSkillDirs(roots) {
|
|
@@ -153,6 +167,11 @@ async function listSkillDirs(roots) {
|
|
|
153
167
|
for (const root of roots) {
|
|
154
168
|
if (!(0, node_fs_1.existsSync)(root))
|
|
155
169
|
continue;
|
|
170
|
+
const rootManifest = (0, node_path_1.join)(root, 'SKILL.md');
|
|
171
|
+
if ((0, node_fs_1.existsSync)(rootManifest)) {
|
|
172
|
+
found.push(root);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
156
175
|
let entries;
|
|
157
176
|
try {
|
|
158
177
|
entries = await (0, promises_1.readdir)(root, { withFileTypes: true });
|
|
@@ -176,6 +195,18 @@ async function listPluginArtifacts(roots) {
|
|
|
176
195
|
for (const root of roots) {
|
|
177
196
|
if (!(0, node_fs_1.existsSync)(root))
|
|
178
197
|
continue;
|
|
198
|
+
const rootManifest = firstExisting([
|
|
199
|
+
(0, node_path_1.join)(root, 'openclaw.plugin.json'),
|
|
200
|
+
(0, node_path_1.join)(root, 'package.json'),
|
|
201
|
+
(0, node_path_1.join)(root, '.claude-plugin', 'plugin.json'),
|
|
202
|
+
(0, node_path_1.join)(root, 'plugin.json'),
|
|
203
|
+
(0, node_path_1.join)(root, 'index.js'),
|
|
204
|
+
(0, node_path_1.join)(root, 'index.ts'),
|
|
205
|
+
]);
|
|
206
|
+
if (rootManifest) {
|
|
207
|
+
found.push({ path: root, name: (0, node_path_1.basename)(root), bodyPath: rootManifest });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
179
210
|
let entries;
|
|
180
211
|
try {
|
|
181
212
|
entries = await (0, promises_1.readdir)(root, { withFileTypes: true });
|
|
@@ -224,6 +255,26 @@ async function listPromptInjectionArtifacts(options) {
|
|
|
224
255
|
const plugins = await listPluginArtifacts(roots);
|
|
225
256
|
return dedupeArtifacts([...skills, ...plugins]);
|
|
226
257
|
}
|
|
258
|
+
async function expandInspectPaths(paths) {
|
|
259
|
+
const expanded = [];
|
|
260
|
+
for (const rawPath of paths) {
|
|
261
|
+
const pattern = expandHomeDir(rawPath);
|
|
262
|
+
if (!pattern)
|
|
263
|
+
continue;
|
|
264
|
+
if (hasGlobMagic(pattern)) {
|
|
265
|
+
const matches = await (0, glob_1.glob)(pattern, { absolute: true, nodir: false });
|
|
266
|
+
for (const match of matches) {
|
|
267
|
+
if ((0, node_fs_1.existsSync)(match))
|
|
268
|
+
expanded.push(match);
|
|
269
|
+
}
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const resolved = (0, node_path_1.isAbsolute)(pattern) ? pattern : (0, node_path_1.resolve)(pattern);
|
|
273
|
+
if ((0, node_fs_1.existsSync)(resolved))
|
|
274
|
+
expanded.push(resolved);
|
|
275
|
+
}
|
|
276
|
+
return dedupePaths(expanded);
|
|
277
|
+
}
|
|
227
278
|
function firstExisting(paths) {
|
|
228
279
|
for (const path of paths) {
|
|
229
280
|
if ((0, node_fs_1.existsSync)(path))
|
|
@@ -231,6 +282,21 @@ function firstExisting(paths) {
|
|
|
231
282
|
}
|
|
232
283
|
return null;
|
|
233
284
|
}
|
|
285
|
+
function expandHomeDir(path) {
|
|
286
|
+
if (!path.startsWith('~'))
|
|
287
|
+
return path;
|
|
288
|
+
if (path === '~')
|
|
289
|
+
return (0, node_os_1.homedir)();
|
|
290
|
+
if (path.startsWith('~/'))
|
|
291
|
+
return (0, node_path_1.join)((0, node_os_1.homedir)(), path.slice(2));
|
|
292
|
+
return path;
|
|
293
|
+
}
|
|
294
|
+
function hasGlobMagic(path) {
|
|
295
|
+
return /[*?[\]{}]/.test(path);
|
|
296
|
+
}
|
|
297
|
+
function dedupePaths(paths) {
|
|
298
|
+
return [...new Set(paths)];
|
|
299
|
+
}
|
|
234
300
|
function dedupeArtifacts(artifacts) {
|
|
235
301
|
const seen = new Set();
|
|
236
302
|
const result = [];
|
|
@@ -243,11 +309,11 @@ function dedupeArtifacts(artifacts) {
|
|
|
243
309
|
return result;
|
|
244
310
|
}
|
|
245
311
|
/**
|
|
246
|
-
* Match one local artifact against an advisory
|
|
247
|
-
* Returns the first match found (per matcher precedence: hash > regex > name).
|
|
312
|
+
* Match one local artifact against an advisory matcher set.
|
|
313
|
+
* Returns the first match found (per matcher precedence: hash > version > regex > name).
|
|
248
314
|
* Returns null when nothing matched.
|
|
249
315
|
*/
|
|
250
|
-
async function matchArtifact(artifact, affected) {
|
|
316
|
+
async function matchArtifact(artifact, ecosystem, affected) {
|
|
251
317
|
const bodyPath = artifact.bodyPath ?? artifact.path;
|
|
252
318
|
let localHash = null;
|
|
253
319
|
const wantsHash = affected.some((m) => m.sha256);
|
|
@@ -260,14 +326,21 @@ async function matchArtifact(artifact, affected) {
|
|
|
260
326
|
}
|
|
261
327
|
}
|
|
262
328
|
let body = null;
|
|
263
|
-
const wantsBody = affected.some((m) => m.bodyRegex || m.urlPattern || m.domainExact);
|
|
329
|
+
const wantsBody = affected.some((m) => m.bodyRegex || m.urlPattern || m.domainExact || m.namePattern || m.versionRange);
|
|
264
330
|
if (wantsBody) {
|
|
265
331
|
body = await readArtifactBody(artifact, bodyPath);
|
|
266
332
|
}
|
|
333
|
+
const coordinates = body !== null ? extractPackageCoordinates(artifact, ecosystem, body) : [];
|
|
267
334
|
for (const matcher of affected) {
|
|
335
|
+
if (!matcherMatchesCoordinateSet(matcher, artifact, coordinates)) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
268
338
|
if (matcher.sha256 && localHash && matcher.sha256.toLowerCase() === localHash.toLowerCase()) {
|
|
269
339
|
return { path: artifact.path, matchedBy: 'sha256', hash: localHash };
|
|
270
340
|
}
|
|
341
|
+
if (matcher.versionRange && coordinates.some((candidate) => versionSatisfiesRange(candidate.version, matcher.versionRange))) {
|
|
342
|
+
return { path: artifact.path, matchedBy: 'versionRange' };
|
|
343
|
+
}
|
|
271
344
|
if (matcher.bodyRegex && body !== null) {
|
|
272
345
|
if (safeRegexTest(matcher.bodyRegex, body)) {
|
|
273
346
|
return { path: artifact.path, matchedBy: 'bodyRegex' };
|
|
@@ -279,9 +352,12 @@ async function matchArtifact(artifact, affected) {
|
|
|
279
352
|
if (matcher.domainExact && body !== null && bodyContainsDomain(body, matcher.domainExact)) {
|
|
280
353
|
return { path: artifact.path, matchedBy: 'domainExact' };
|
|
281
354
|
}
|
|
282
|
-
if (matcher.namePattern
|
|
355
|
+
if (matcher.namePattern) {
|
|
283
356
|
return { path: artifact.path, matchedBy: 'namePattern' };
|
|
284
357
|
}
|
|
358
|
+
if (matcher.sha256 || matcher.versionRange || matcher.bodyRegex || matcher.urlPattern || matcher.domainExact) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
285
361
|
}
|
|
286
362
|
return null;
|
|
287
363
|
}
|
|
@@ -327,6 +403,377 @@ function containsBareDomain(body, domain) {
|
|
|
327
403
|
const re = new RegExp(`(^|[^A-Za-z0-9.-])${escaped}([^A-Za-z0-9.-]|$)`, 'i');
|
|
328
404
|
return re.test(body);
|
|
329
405
|
}
|
|
406
|
+
function getSelfCheckMatchers(advisory) {
|
|
407
|
+
if (advisory.selfCheck && Array.isArray(advisory.selfCheck.matchers)) {
|
|
408
|
+
return advisory.selfCheck.matchers.filter(isMatcherLike).map((matcher) => ({
|
|
409
|
+
namePattern: matcher.namePattern,
|
|
410
|
+
sha256: matcher.sha256,
|
|
411
|
+
versionRange: matcher.versionRange,
|
|
412
|
+
bodyRegex: matcher.bodyRegex,
|
|
413
|
+
urlPattern: matcher.urlPattern,
|
|
414
|
+
domainExact: matcher.domainExact,
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
417
|
+
return Array.isArray(advisory.affected) ? advisory.affected.filter(isMatcherLike) : [];
|
|
418
|
+
}
|
|
419
|
+
function isMatcherLike(value) {
|
|
420
|
+
if (!value || typeof value !== 'object')
|
|
421
|
+
return false;
|
|
422
|
+
const matcher = value;
|
|
423
|
+
return [
|
|
424
|
+
'namePattern',
|
|
425
|
+
'sha256',
|
|
426
|
+
'versionRange',
|
|
427
|
+
'bodyRegex',
|
|
428
|
+
'urlPattern',
|
|
429
|
+
'domainExact',
|
|
430
|
+
].some((key) => typeof matcher[key] === 'string' && matcher[key].length > 0);
|
|
431
|
+
}
|
|
432
|
+
function matcherMatchesCoordinateSet(matcher, artifact, coordinates) {
|
|
433
|
+
const needsCoordinate = Boolean(matcher.namePattern || matcher.versionRange);
|
|
434
|
+
if (!needsCoordinate)
|
|
435
|
+
return true;
|
|
436
|
+
const candidates = dedupeCoordinates([
|
|
437
|
+
...coordinates,
|
|
438
|
+
{ name: artifact.name, version: undefined },
|
|
439
|
+
{ name: stripExtension(artifact.name), version: undefined },
|
|
440
|
+
]);
|
|
441
|
+
return candidates.some((candidate) => {
|
|
442
|
+
if (matcher.namePattern && !globMatch(matcher.namePattern, candidate.name)) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
if (matcher.versionRange && !versionSatisfiesRange(candidate.version, matcher.versionRange)) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
return true;
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function extractPackageCoordinates(artifact, ecosystem, body) {
|
|
452
|
+
const coordinates = [
|
|
453
|
+
...extractManifestCoordinate(artifact, body),
|
|
454
|
+
...extractSupplyChainCoordinates(artifact, ecosystem, body),
|
|
455
|
+
];
|
|
456
|
+
return dedupeCoordinates(coordinates);
|
|
457
|
+
}
|
|
458
|
+
function extractManifestCoordinate(artifact, body) {
|
|
459
|
+
const json = tryParseJson(body);
|
|
460
|
+
if (json && typeof json === 'object' && !Array.isArray(json)) {
|
|
461
|
+
const name = typeof json.name === 'string' ? json.name : artifact.name;
|
|
462
|
+
const version = typeof json.version === 'string' ? json.version : undefined;
|
|
463
|
+
return [{ name, version }];
|
|
464
|
+
}
|
|
465
|
+
const skillName = body.match(/^\s*name:\s*["']?([^\n"']+)["']?\s*$/im)?.[1]?.trim();
|
|
466
|
+
const skillVersion = body.match(/^\s*version:\s*["']?([^\n"']+)["']?\s*$/im)?.[1]?.trim();
|
|
467
|
+
if (skillName || skillVersion) {
|
|
468
|
+
return [{
|
|
469
|
+
name: skillName || artifact.name,
|
|
470
|
+
version: skillVersion,
|
|
471
|
+
}];
|
|
472
|
+
}
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
function extractSupplyChainCoordinates(artifact, ecosystem, body) {
|
|
476
|
+
if (ecosystem !== 'supply_chain')
|
|
477
|
+
return [];
|
|
478
|
+
const filename = (0, node_path_1.basename)(artifact.path);
|
|
479
|
+
switch (filename) {
|
|
480
|
+
case 'package.json':
|
|
481
|
+
return extractPackageJsonDependencies(body);
|
|
482
|
+
case 'package-lock.json':
|
|
483
|
+
case 'npm-shrinkwrap.json':
|
|
484
|
+
return extractPackageLockDependencies(body);
|
|
485
|
+
case 'requirements.txt':
|
|
486
|
+
return extractRequirementsDependencies(body);
|
|
487
|
+
case 'Cargo.toml':
|
|
488
|
+
return extractCargoTomlDependencies(body);
|
|
489
|
+
case 'Cargo.lock':
|
|
490
|
+
return extractCargoLockDependencies(body);
|
|
491
|
+
case 'go.mod':
|
|
492
|
+
return extractGoModDependencies(body);
|
|
493
|
+
case 'go.sum':
|
|
494
|
+
return extractGoSumDependencies(body);
|
|
495
|
+
default:
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function extractPackageJsonDependencies(body) {
|
|
500
|
+
const json = tryParseJson(body);
|
|
501
|
+
if (!json || typeof json !== 'object' || Array.isArray(json))
|
|
502
|
+
return [];
|
|
503
|
+
const fields = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies', 'overrides', 'resolutions'];
|
|
504
|
+
const coordinates = [];
|
|
505
|
+
for (const field of fields) {
|
|
506
|
+
const deps = json[field];
|
|
507
|
+
if (!deps || typeof deps !== 'object' || Array.isArray(deps))
|
|
508
|
+
continue;
|
|
509
|
+
for (const [name, rawVersion] of Object.entries(deps)) {
|
|
510
|
+
if (typeof rawVersion !== 'string')
|
|
511
|
+
continue;
|
|
512
|
+
coordinates.push({ name, version: normalizeVersionCandidate(rawVersion) ?? rawVersion.trim() });
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return coordinates;
|
|
516
|
+
}
|
|
517
|
+
function extractPackageLockDependencies(body) {
|
|
518
|
+
const json = tryParseJson(body);
|
|
519
|
+
if (!json || typeof json !== 'object' || Array.isArray(json))
|
|
520
|
+
return [];
|
|
521
|
+
const coordinates = [];
|
|
522
|
+
const root = json;
|
|
523
|
+
collectPackageLockDeps(root.dependencies, coordinates);
|
|
524
|
+
if (root.packages && typeof root.packages === 'object' && !Array.isArray(root.packages)) {
|
|
525
|
+
for (const [packagePath, meta] of Object.entries(root.packages)) {
|
|
526
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta))
|
|
527
|
+
continue;
|
|
528
|
+
const pkg = meta;
|
|
529
|
+
const version = typeof pkg.version === 'string' ? pkg.version : undefined;
|
|
530
|
+
const name = typeof pkg.name === 'string'
|
|
531
|
+
? pkg.name
|
|
532
|
+
: packagePath.startsWith('node_modules/')
|
|
533
|
+
? packagePath.slice('node_modules/'.length)
|
|
534
|
+
: '';
|
|
535
|
+
if (name)
|
|
536
|
+
coordinates.push({ name, version });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return coordinates;
|
|
540
|
+
}
|
|
541
|
+
function collectPackageLockDeps(node, coordinates) {
|
|
542
|
+
if (!node || typeof node !== 'object' || Array.isArray(node))
|
|
543
|
+
return;
|
|
544
|
+
for (const [name, meta] of Object.entries(node)) {
|
|
545
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta))
|
|
546
|
+
continue;
|
|
547
|
+
const pkg = meta;
|
|
548
|
+
const version = typeof pkg.version === 'string' ? pkg.version : undefined;
|
|
549
|
+
coordinates.push({ name, version });
|
|
550
|
+
collectPackageLockDeps(pkg.dependencies, coordinates);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function extractRequirementsDependencies(body) {
|
|
554
|
+
const coordinates = [];
|
|
555
|
+
for (const line of body.split(/\r?\n/)) {
|
|
556
|
+
const trimmed = line.trim();
|
|
557
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
558
|
+
continue;
|
|
559
|
+
const match = trimmed.match(/^([A-Za-z0-9_.-]+)\s*(?:==|>=|<=|~=|!=|>|<)\s*([A-Za-z0-9+_.-]+)/);
|
|
560
|
+
if (match) {
|
|
561
|
+
coordinates.push({ name: match[1], version: normalizeVersionCandidate(match[2]) ?? match[2] });
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return coordinates;
|
|
565
|
+
}
|
|
566
|
+
function extractCargoTomlDependencies(body) {
|
|
567
|
+
const coordinates = [];
|
|
568
|
+
let inDependencies = false;
|
|
569
|
+
for (const line of body.split(/\r?\n/)) {
|
|
570
|
+
const trimmed = line.trim();
|
|
571
|
+
if (trimmed.startsWith('[')) {
|
|
572
|
+
inDependencies = /^\[(?:workspace\.)?(?:target\.[^.\]]+\.)?(?:dev-|build-)?dependencies\]/.test(trimmed);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (!inDependencies || !trimmed || trimmed.startsWith('#'))
|
|
576
|
+
continue;
|
|
577
|
+
const match = trimmed.match(/^([A-Za-z0-9_.-]+)\s*=\s*(?:\{\s*version\s*=\s*"([^"]+)"[^}]*\}|"([^"]+)")/);
|
|
578
|
+
if (match) {
|
|
579
|
+
coordinates.push({ name: match[1], version: normalizeVersionCandidate(match[2] || match[3]) ?? (match[2] || match[3]) });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return coordinates;
|
|
583
|
+
}
|
|
584
|
+
function extractCargoLockDependencies(body) {
|
|
585
|
+
const coordinates = [];
|
|
586
|
+
const re = /\[\[package\]\][\s\S]*?name = "([^"]+)"[\s\S]*?version = "([^"]+)"/g;
|
|
587
|
+
for (const match of body.matchAll(re)) {
|
|
588
|
+
coordinates.push({ name: match[1], version: normalizeVersionCandidate(match[2]) ?? match[2] });
|
|
589
|
+
}
|
|
590
|
+
return coordinates;
|
|
591
|
+
}
|
|
592
|
+
function extractGoModDependencies(body) {
|
|
593
|
+
const coordinates = [];
|
|
594
|
+
for (const line of body.split(/\r?\n/)) {
|
|
595
|
+
const trimmed = line.trim();
|
|
596
|
+
if (!trimmed || trimmed.startsWith('//'))
|
|
597
|
+
continue;
|
|
598
|
+
const match = trimmed.match(/^(?:require\s+)?(\S+)\s+v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
|
|
599
|
+
if (match) {
|
|
600
|
+
coordinates.push({ name: match[1], version: normalizeVersionCandidate(match[2]) ?? match[2] });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return coordinates;
|
|
604
|
+
}
|
|
605
|
+
function extractGoSumDependencies(body) {
|
|
606
|
+
const coordinates = [];
|
|
607
|
+
for (const line of body.split(/\r?\n/)) {
|
|
608
|
+
const trimmed = line.trim();
|
|
609
|
+
if (!trimmed)
|
|
610
|
+
continue;
|
|
611
|
+
const match = trimmed.match(/^(\S+)\s+v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
|
|
612
|
+
if (match) {
|
|
613
|
+
coordinates.push({ name: match[1], version: normalizeVersionCandidate(match[2]) ?? match[2] });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return coordinates;
|
|
617
|
+
}
|
|
618
|
+
function tryParseJson(body) {
|
|
619
|
+
try {
|
|
620
|
+
return JSON.parse(body);
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function stripExtension(name) {
|
|
627
|
+
const extension = (0, node_path_1.extname)(name);
|
|
628
|
+
return extension ? name.slice(0, -extension.length) : name;
|
|
629
|
+
}
|
|
630
|
+
function dedupeCoordinates(coordinates) {
|
|
631
|
+
const seen = new Set();
|
|
632
|
+
const result = [];
|
|
633
|
+
for (const coordinate of coordinates) {
|
|
634
|
+
const normalizedName = coordinate.name.trim();
|
|
635
|
+
if (!normalizedName)
|
|
636
|
+
continue;
|
|
637
|
+
const key = `${normalizedName}\u0000${coordinate.version ?? ''}`;
|
|
638
|
+
if (seen.has(key))
|
|
639
|
+
continue;
|
|
640
|
+
seen.add(key);
|
|
641
|
+
result.push({ name: normalizedName, version: coordinate.version?.trim() });
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
function versionSatisfiesRange(version, range) {
|
|
646
|
+
if (!range)
|
|
647
|
+
return true;
|
|
648
|
+
const parsedVersion = parseSemver(version);
|
|
649
|
+
if (!parsedVersion)
|
|
650
|
+
return false;
|
|
651
|
+
const comparators = parseComparators(range);
|
|
652
|
+
if (comparators.length === 0)
|
|
653
|
+
return false;
|
|
654
|
+
return comparators.every((comparator) => compareSemver(parsedVersion, comparator.version, comparator.operator));
|
|
655
|
+
}
|
|
656
|
+
function normalizeVersionCandidate(value) {
|
|
657
|
+
if (!value)
|
|
658
|
+
return null;
|
|
659
|
+
const exact = value.trim().match(/^v?(\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/);
|
|
660
|
+
return exact ? exact[1] : null;
|
|
661
|
+
}
|
|
662
|
+
function parseSemver(input) {
|
|
663
|
+
if (!input)
|
|
664
|
+
return null;
|
|
665
|
+
const match = input.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
666
|
+
if (!match)
|
|
667
|
+
return null;
|
|
668
|
+
return {
|
|
669
|
+
major: Number(match[1]),
|
|
670
|
+
minor: Number(match[2] ?? '0'),
|
|
671
|
+
patch: Number(match[3] ?? '0'),
|
|
672
|
+
prerelease: match[4],
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function parseComparators(range) {
|
|
676
|
+
const trimmed = range.trim();
|
|
677
|
+
if (!trimmed || trimmed === '*')
|
|
678
|
+
return [];
|
|
679
|
+
if (trimmed.startsWith('^')) {
|
|
680
|
+
const base = parseSemver(trimmed.slice(1));
|
|
681
|
+
if (!base)
|
|
682
|
+
return [];
|
|
683
|
+
return [
|
|
684
|
+
{ operator: '>=', version: base },
|
|
685
|
+
{ operator: '<', version: caretUpperBound(base) },
|
|
686
|
+
];
|
|
687
|
+
}
|
|
688
|
+
if (trimmed.startsWith('~')) {
|
|
689
|
+
const base = parseSemver(trimmed.slice(1));
|
|
690
|
+
if (!base)
|
|
691
|
+
return [];
|
|
692
|
+
return [
|
|
693
|
+
{ operator: '>=', version: base },
|
|
694
|
+
{ operator: '<', version: { major: base.major, minor: base.minor + 1, patch: 0 } },
|
|
695
|
+
];
|
|
696
|
+
}
|
|
697
|
+
const wildcard = parseWildcardRange(trimmed);
|
|
698
|
+
if (wildcard)
|
|
699
|
+
return wildcard;
|
|
700
|
+
const tokens = trimmed.split(/[\s,]+/).filter(Boolean);
|
|
701
|
+
const comparators = [];
|
|
702
|
+
for (const token of tokens) {
|
|
703
|
+
const match = token.match(/^(<=|>=|<|>|=)?(.+)$/);
|
|
704
|
+
if (!match)
|
|
705
|
+
return [];
|
|
706
|
+
const operator = (match[1] ?? '=');
|
|
707
|
+
const version = parseSemver(match[2]);
|
|
708
|
+
if (!version)
|
|
709
|
+
return [];
|
|
710
|
+
comparators.push({ operator, version });
|
|
711
|
+
}
|
|
712
|
+
return comparators;
|
|
713
|
+
}
|
|
714
|
+
function parseWildcardRange(range) {
|
|
715
|
+
const match = range.match(/^v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?$/i);
|
|
716
|
+
if (!match)
|
|
717
|
+
return null;
|
|
718
|
+
if (![match[2], match[3]].some((part) => part && /^(x|\*)$/i.test(part))) {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
const major = Number(match[1]);
|
|
722
|
+
const minorPart = match[2];
|
|
723
|
+
const patchPart = match[3];
|
|
724
|
+
if (!minorPart || /^(x|\*)$/i.test(minorPart)) {
|
|
725
|
+
return [
|
|
726
|
+
{ operator: '>=', version: { major, minor: 0, patch: 0 } },
|
|
727
|
+
{ operator: '<', version: { major: major + 1, minor: 0, patch: 0 } },
|
|
728
|
+
];
|
|
729
|
+
}
|
|
730
|
+
const minor = Number(minorPart);
|
|
731
|
+
if (!patchPart || /^(x|\*)$/i.test(patchPart)) {
|
|
732
|
+
return [
|
|
733
|
+
{ operator: '>=', version: { major, minor, patch: 0 } },
|
|
734
|
+
{ operator: '<', version: { major, minor: minor + 1, patch: 0 } },
|
|
735
|
+
];
|
|
736
|
+
}
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
function caretUpperBound(version) {
|
|
740
|
+
if (version.major > 0) {
|
|
741
|
+
return { major: version.major + 1, minor: 0, patch: 0 };
|
|
742
|
+
}
|
|
743
|
+
if (version.minor > 0) {
|
|
744
|
+
return { major: 0, minor: version.minor + 1, patch: 0 };
|
|
745
|
+
}
|
|
746
|
+
return { major: 0, minor: 0, patch: version.patch + 1 };
|
|
747
|
+
}
|
|
748
|
+
function compareSemver(left, right, operator) {
|
|
749
|
+
const order = semverCompare(left, right);
|
|
750
|
+
switch (operator) {
|
|
751
|
+
case '<':
|
|
752
|
+
return order < 0;
|
|
753
|
+
case '<=':
|
|
754
|
+
return order <= 0;
|
|
755
|
+
case '>':
|
|
756
|
+
return order > 0;
|
|
757
|
+
case '>=':
|
|
758
|
+
return order >= 0;
|
|
759
|
+
case '=':
|
|
760
|
+
return order === 0;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function semverCompare(left, right) {
|
|
764
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
765
|
+
const delta = left[key] - right[key];
|
|
766
|
+
if (delta !== 0)
|
|
767
|
+
return delta;
|
|
768
|
+
}
|
|
769
|
+
if (!left.prerelease && !right.prerelease)
|
|
770
|
+
return 0;
|
|
771
|
+
if (!left.prerelease)
|
|
772
|
+
return 1;
|
|
773
|
+
if (!right.prerelease)
|
|
774
|
+
return -1;
|
|
775
|
+
return left.prerelease.localeCompare(right.prerelease);
|
|
776
|
+
}
|
|
330
777
|
/**
|
|
331
778
|
* Defense against catastrophic backtracking and malformed regex coming
|
|
332
779
|
* from upstream advisory data:
|