skill-checker 0.1.11 → 0.1.13
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/dist/cli.js +297 -108
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +297 -108
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -328,6 +328,70 @@ var structuralChecks = {
|
|
|
328
328
|
}
|
|
329
329
|
};
|
|
330
330
|
|
|
331
|
+
// src/types.ts
|
|
332
|
+
var SEVERITY_SCORES = {
|
|
333
|
+
CRITICAL: 25,
|
|
334
|
+
HIGH: 10,
|
|
335
|
+
MEDIUM: 3,
|
|
336
|
+
LOW: 1
|
|
337
|
+
};
|
|
338
|
+
function computeGrade(score) {
|
|
339
|
+
if (score >= 90) return "A";
|
|
340
|
+
if (score >= 75) return "B";
|
|
341
|
+
if (score >= 60) return "C";
|
|
342
|
+
if (score >= 40) return "D";
|
|
343
|
+
return "F";
|
|
344
|
+
}
|
|
345
|
+
var REDUCE_MAP = {
|
|
346
|
+
CRITICAL: "HIGH",
|
|
347
|
+
HIGH: "MEDIUM",
|
|
348
|
+
MEDIUM: "LOW",
|
|
349
|
+
LOW: "LOW"
|
|
350
|
+
};
|
|
351
|
+
function reduceSeverity(original, reason) {
|
|
352
|
+
let reduced = REDUCE_MAP[original];
|
|
353
|
+
if (original === "CRITICAL" && reduced === "LOW") {
|
|
354
|
+
reduced = "MEDIUM";
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
severity: reduced,
|
|
358
|
+
reducedFrom: original,
|
|
359
|
+
annotation: `[reduced: ${reason}]`
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
var DEFAULT_CONFIG = {
|
|
363
|
+
policy: "balanced",
|
|
364
|
+
overrides: {},
|
|
365
|
+
ignore: []
|
|
366
|
+
};
|
|
367
|
+
function getHookAction(policy, severity) {
|
|
368
|
+
const matrix = {
|
|
369
|
+
strict: {
|
|
370
|
+
CRITICAL: "deny",
|
|
371
|
+
HIGH: "deny",
|
|
372
|
+
MEDIUM: "ask",
|
|
373
|
+
LOW: "report"
|
|
374
|
+
},
|
|
375
|
+
balanced: {
|
|
376
|
+
CRITICAL: "deny",
|
|
377
|
+
HIGH: "ask",
|
|
378
|
+
MEDIUM: "report",
|
|
379
|
+
LOW: "report"
|
|
380
|
+
},
|
|
381
|
+
permissive: {
|
|
382
|
+
CRITICAL: "ask",
|
|
383
|
+
HIGH: "report",
|
|
384
|
+
MEDIUM: "report",
|
|
385
|
+
LOW: "report"
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
const row = matrix[policy];
|
|
389
|
+
if (!row) {
|
|
390
|
+
return matrix.balanced[severity];
|
|
391
|
+
}
|
|
392
|
+
return row[severity];
|
|
393
|
+
}
|
|
394
|
+
|
|
331
395
|
// src/utils/context.ts
|
|
332
396
|
function isInCodeBlock(lines, lineIndex) {
|
|
333
397
|
let inBlock = false;
|
|
@@ -391,6 +455,49 @@ function isLicenseFile(filePath) {
|
|
|
391
455
|
function isLocalhostURL(url) {
|
|
392
456
|
return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])/i.test(url);
|
|
393
457
|
}
|
|
458
|
+
function isInEducationalContext(lines, lineIndex) {
|
|
459
|
+
const line = lines[lineIndex];
|
|
460
|
+
if (/^#{1,6}\s+/.test(line)) return true;
|
|
461
|
+
if (/^\s*[-*]?\s*(\*\*[^*]+\*\*\s*:|[A-Z][^:]{0,40}:)\s/.test(line))
|
|
462
|
+
return true;
|
|
463
|
+
for (let i = lineIndex; i >= Math.max(0, lineIndex - 15); i--) {
|
|
464
|
+
if (/^#{1,4}\s+.*(strateg|guide|framework|structure|model|overview|comparison|concept|principle|example|tutorial|reference|approach|method)/i.test(
|
|
465
|
+
lines[i]
|
|
466
|
+
)) {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
var PROMOTIONAL_INTENT_PATTERNS = [
|
|
473
|
+
/\d+%\s*off\b/i,
|
|
474
|
+
/\blimited\s+time\b/i,
|
|
475
|
+
/\bact\s+now\b/i,
|
|
476
|
+
/\bhurry\b/i,
|
|
477
|
+
/\btoday\s+only\b/i,
|
|
478
|
+
/\bdon'?t\s+miss\b/i,
|
|
479
|
+
/\bsave\s+\d+%/i,
|
|
480
|
+
/\bexclusive\s+(offer|deal)\b/i,
|
|
481
|
+
/\bsign\s+up\s+(now|today)\b/i,
|
|
482
|
+
/\bget\s+started\b/i,
|
|
483
|
+
/\bclaim\s+(now|yours?)\b/i,
|
|
484
|
+
/\boffer\s+ends?\b/i,
|
|
485
|
+
/\blast\s+chance\b/i,
|
|
486
|
+
/\bonly\s+\d+\s+left\b/i,
|
|
487
|
+
/\bends?\s+in\s+\d+/i,
|
|
488
|
+
/\bstart\s+(your\s+)?free\s+trial\b/i
|
|
489
|
+
];
|
|
490
|
+
function hasPromotionalIntent(line) {
|
|
491
|
+
return PROMOTIONAL_INTENT_PATTERNS.some((p) => p.test(line));
|
|
492
|
+
}
|
|
493
|
+
function hasPromotionalIntentNearby(lines, lineIndex, window = 3) {
|
|
494
|
+
const start = Math.max(0, lineIndex - window);
|
|
495
|
+
const end = Math.min(lines.length - 1, lineIndex + window);
|
|
496
|
+
for (let i = start; i <= end; i++) {
|
|
497
|
+
if (hasPromotionalIntent(lines[i])) return true;
|
|
498
|
+
}
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
394
501
|
function parseURLPath(url) {
|
|
395
502
|
try {
|
|
396
503
|
const u = new URL(url);
|
|
@@ -422,16 +529,18 @@ var LOREM_PATTERNS = [
|
|
|
422
529
|
/dolor\s+sit\s+amet/i,
|
|
423
530
|
/consectetur\s+adipiscing/i
|
|
424
531
|
];
|
|
425
|
-
var
|
|
532
|
+
var STRONG_AD_PATTERNS = [
|
|
426
533
|
/\bbuy\s+now\b/i,
|
|
427
|
-
/\
|
|
534
|
+
/\bclick\s+here\s+to\s+(buy|subscribe|download)/i,
|
|
535
|
+
/\buse\s+code\b.*\b\d+%?\s*off\b/i
|
|
536
|
+
];
|
|
537
|
+
var SOFT_AD_PATTERNS = [
|
|
428
538
|
/\bdiscount\b/i,
|
|
539
|
+
/\bfree\s+trial\b/i,
|
|
429
540
|
/\bpromo\s*code\b/i,
|
|
430
541
|
/\bsubscribe\s+(to|now)\b/i,
|
|
431
542
|
/\bsponsored\s+by\b/i,
|
|
432
543
|
/\baffiliate\s+link\b/i,
|
|
433
|
-
/\bclick\s+here\s+to\s+(buy|subscribe|download)/i,
|
|
434
|
-
/\buse\s+code\b.*\b\d+%?\s*off\b/i,
|
|
435
544
|
/\bcheck\s+out\s+my\b/i
|
|
436
545
|
];
|
|
437
546
|
var contentChecks = {
|
|
@@ -496,7 +605,8 @@ var contentChecks = {
|
|
|
496
605
|
checkDescriptionMismatch(results, skill);
|
|
497
606
|
for (let i = 0; i < skill.bodyLines.length; i++) {
|
|
498
607
|
const line = skill.bodyLines[i];
|
|
499
|
-
|
|
608
|
+
let matched = false;
|
|
609
|
+
for (const pattern of STRONG_AD_PATTERNS) {
|
|
500
610
|
if (pattern.test(line)) {
|
|
501
611
|
results.push({
|
|
502
612
|
id: "CONT-005",
|
|
@@ -505,8 +615,43 @@ var contentChecks = {
|
|
|
505
615
|
title: "Promotional/advertising content",
|
|
506
616
|
message: `Line ${skill.bodyStartLine + i}: Contains ad-like content.`,
|
|
507
617
|
line: skill.bodyStartLine + i,
|
|
508
|
-
snippet: line.trim().slice(0, 120)
|
|
618
|
+
snippet: line.trim().slice(0, 120),
|
|
619
|
+
source: "SKILL.md"
|
|
509
620
|
});
|
|
621
|
+
matched = true;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (matched) continue;
|
|
626
|
+
for (const pattern of SOFT_AD_PATTERNS) {
|
|
627
|
+
if (pattern.test(line)) {
|
|
628
|
+
const inCode = isInCodeBlock(skill.bodyLines, i);
|
|
629
|
+
const inEducational = isInEducationalContext(skill.bodyLines, i);
|
|
630
|
+
if ((inCode || inEducational) && !hasPromotionalIntentNearby(skill.bodyLines, i)) {
|
|
631
|
+
const reduction = reduceSeverity("HIGH", "educational/descriptive context");
|
|
632
|
+
results.push({
|
|
633
|
+
id: "CONT-005",
|
|
634
|
+
category: "CONT",
|
|
635
|
+
severity: reduction.severity,
|
|
636
|
+
title: "Promotional/advertising content",
|
|
637
|
+
message: `Line ${skill.bodyStartLine + i}: Contains ad-like content. ${reduction.annotation}`,
|
|
638
|
+
line: skill.bodyStartLine + i,
|
|
639
|
+
snippet: line.trim().slice(0, 120),
|
|
640
|
+
reducedFrom: reduction.reducedFrom,
|
|
641
|
+
source: "SKILL.md"
|
|
642
|
+
});
|
|
643
|
+
} else {
|
|
644
|
+
results.push({
|
|
645
|
+
id: "CONT-005",
|
|
646
|
+
category: "CONT",
|
|
647
|
+
severity: "HIGH",
|
|
648
|
+
title: "Promotional/advertising content",
|
|
649
|
+
message: `Line ${skill.bodyStartLine + i}: Contains ad-like content.`,
|
|
650
|
+
line: skill.bodyStartLine + i,
|
|
651
|
+
snippet: line.trim().slice(0, 120),
|
|
652
|
+
source: "SKILL.md"
|
|
653
|
+
});
|
|
654
|
+
}
|
|
510
655
|
break;
|
|
511
656
|
}
|
|
512
657
|
}
|
|
@@ -985,70 +1130,6 @@ function dedup(results) {
|
|
|
985
1130
|
});
|
|
986
1131
|
}
|
|
987
1132
|
|
|
988
|
-
// src/types.ts
|
|
989
|
-
var SEVERITY_SCORES = {
|
|
990
|
-
CRITICAL: 25,
|
|
991
|
-
HIGH: 10,
|
|
992
|
-
MEDIUM: 3,
|
|
993
|
-
LOW: 1
|
|
994
|
-
};
|
|
995
|
-
function computeGrade(score) {
|
|
996
|
-
if (score >= 90) return "A";
|
|
997
|
-
if (score >= 75) return "B";
|
|
998
|
-
if (score >= 60) return "C";
|
|
999
|
-
if (score >= 40) return "D";
|
|
1000
|
-
return "F";
|
|
1001
|
-
}
|
|
1002
|
-
var REDUCE_MAP = {
|
|
1003
|
-
CRITICAL: "HIGH",
|
|
1004
|
-
HIGH: "MEDIUM",
|
|
1005
|
-
MEDIUM: "LOW",
|
|
1006
|
-
LOW: "LOW"
|
|
1007
|
-
};
|
|
1008
|
-
function reduceSeverity(original, reason) {
|
|
1009
|
-
let reduced = REDUCE_MAP[original];
|
|
1010
|
-
if (original === "CRITICAL" && reduced === "LOW") {
|
|
1011
|
-
reduced = "MEDIUM";
|
|
1012
|
-
}
|
|
1013
|
-
return {
|
|
1014
|
-
severity: reduced,
|
|
1015
|
-
reducedFrom: original,
|
|
1016
|
-
annotation: `[reduced: ${reason}]`
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
var DEFAULT_CONFIG = {
|
|
1020
|
-
policy: "balanced",
|
|
1021
|
-
overrides: {},
|
|
1022
|
-
ignore: []
|
|
1023
|
-
};
|
|
1024
|
-
function getHookAction(policy, severity) {
|
|
1025
|
-
const matrix = {
|
|
1026
|
-
strict: {
|
|
1027
|
-
CRITICAL: "deny",
|
|
1028
|
-
HIGH: "deny",
|
|
1029
|
-
MEDIUM: "ask",
|
|
1030
|
-
LOW: "report"
|
|
1031
|
-
},
|
|
1032
|
-
balanced: {
|
|
1033
|
-
CRITICAL: "deny",
|
|
1034
|
-
HIGH: "ask",
|
|
1035
|
-
MEDIUM: "report",
|
|
1036
|
-
LOW: "report"
|
|
1037
|
-
},
|
|
1038
|
-
permissive: {
|
|
1039
|
-
CRITICAL: "ask",
|
|
1040
|
-
HIGH: "report",
|
|
1041
|
-
MEDIUM: "report",
|
|
1042
|
-
LOW: "report"
|
|
1043
|
-
}
|
|
1044
|
-
};
|
|
1045
|
-
const row = matrix[policy];
|
|
1046
|
-
if (!row) {
|
|
1047
|
-
return matrix.balanced[severity];
|
|
1048
|
-
}
|
|
1049
|
-
return row[severity];
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
1133
|
// src/checks/code-safety.ts
|
|
1053
1134
|
var EVAL_PATTERNS = [
|
|
1054
1135
|
/\beval\s*\(/,
|
|
@@ -1525,8 +1606,8 @@ import { homedir } from "os";
|
|
|
1525
1606
|
|
|
1526
1607
|
// src/ioc/indicators.ts
|
|
1527
1608
|
var DEFAULT_IOC = {
|
|
1528
|
-
version: "2026.03.
|
|
1529
|
-
updated: "2026-03-
|
|
1609
|
+
version: "2026.03.16",
|
|
1610
|
+
updated: "2026-03-16",
|
|
1530
1611
|
c2_ips: [
|
|
1531
1612
|
"91.92.242.30",
|
|
1532
1613
|
"91.92.242.39",
|
|
@@ -1535,36 +1616,65 @@ var DEFAULT_IOC = {
|
|
|
1535
1616
|
"45.155.205.233"
|
|
1536
1617
|
],
|
|
1537
1618
|
malicious_hashes: {
|
|
1538
|
-
// NOTE: Never add the
|
|
1619
|
+
// NOTE: Never add the SHA256 of an empty file (e3b0c44298fc...b855)
|
|
1539
1620
|
// as it causes false positives on any empty file.
|
|
1540
1621
|
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2": "clawhavoc-exfiltrator"
|
|
1541
1622
|
},
|
|
1542
|
-
malicious_domains:
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1623
|
+
malicious_domains: {
|
|
1624
|
+
exfiltration: [
|
|
1625
|
+
"webhook.site",
|
|
1626
|
+
"requestbin.com",
|
|
1627
|
+
"requestcatcher.com",
|
|
1628
|
+
"pipedream.com",
|
|
1629
|
+
"pipedream.net",
|
|
1630
|
+
"hookbin.com",
|
|
1631
|
+
"beeceptor.com",
|
|
1632
|
+
"postb.in",
|
|
1633
|
+
"webhook.lol",
|
|
1634
|
+
"requestinspector.com",
|
|
1635
|
+
"mockbin.org"
|
|
1636
|
+
],
|
|
1637
|
+
tunnel: [
|
|
1638
|
+
"ngrok.io",
|
|
1639
|
+
"ngrok-free.app",
|
|
1640
|
+
"serveo.net",
|
|
1641
|
+
"localtunnel.me",
|
|
1642
|
+
"bore.pub",
|
|
1643
|
+
"localhost.run",
|
|
1644
|
+
"loca.lt",
|
|
1645
|
+
"telebit.cloud",
|
|
1646
|
+
"playit.gg",
|
|
1647
|
+
"portmap.io",
|
|
1648
|
+
"pagekite.me"
|
|
1649
|
+
],
|
|
1650
|
+
oast: [
|
|
1651
|
+
"interact.sh",
|
|
1652
|
+
"oast.fun",
|
|
1653
|
+
"oastify.com",
|
|
1654
|
+
"dnslog.cn",
|
|
1655
|
+
"ceye.io",
|
|
1656
|
+
"burpcollaborator.net",
|
|
1657
|
+
"canarytokens.com",
|
|
1658
|
+
"requestrepo.com"
|
|
1659
|
+
],
|
|
1660
|
+
paste: [
|
|
1661
|
+
"pastebin.com",
|
|
1662
|
+
"paste.ee",
|
|
1663
|
+
"hastebin.com",
|
|
1664
|
+
"ghostbin.com",
|
|
1665
|
+
"dpaste.org",
|
|
1666
|
+
"rentry.co",
|
|
1667
|
+
"0bin.net",
|
|
1668
|
+
"privatebin.net",
|
|
1669
|
+
"paste.mozilla.org"
|
|
1670
|
+
],
|
|
1671
|
+
c2: [
|
|
1672
|
+
"evil.com",
|
|
1673
|
+
"malware.com",
|
|
1674
|
+
"exploit.in",
|
|
1675
|
+
"darkweb.onion"
|
|
1676
|
+
]
|
|
1677
|
+
},
|
|
1568
1678
|
typosquat: {
|
|
1569
1679
|
known_patterns: [
|
|
1570
1680
|
"clawhub1",
|
|
@@ -1621,10 +1731,21 @@ function mergeIOC(base, ext) {
|
|
|
1621
1731
|
Object.assign(base.malicious_hashes, ext.malicious_hashes);
|
|
1622
1732
|
}
|
|
1623
1733
|
if (ext.malicious_domains) {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1734
|
+
const categories = [
|
|
1735
|
+
"exfiltration",
|
|
1736
|
+
"tunnel",
|
|
1737
|
+
"oast",
|
|
1738
|
+
"paste",
|
|
1739
|
+
"c2"
|
|
1740
|
+
];
|
|
1741
|
+
for (const cat of categories) {
|
|
1742
|
+
if (ext.malicious_domains[cat]) {
|
|
1743
|
+
base.malicious_domains[cat] = dedupe([
|
|
1744
|
+
...base.malicious_domains[cat],
|
|
1745
|
+
...ext.malicious_domains[cat]
|
|
1746
|
+
]);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1628
1749
|
}
|
|
1629
1750
|
if (ext.typosquat) {
|
|
1630
1751
|
if (ext.typosquat.known_patterns) {
|
|
@@ -1649,6 +1770,22 @@ function mergeIOC(base, ext) {
|
|
|
1649
1770
|
if (ext.version) base.version = ext.version;
|
|
1650
1771
|
if (ext.updated) base.updated = ext.updated;
|
|
1651
1772
|
}
|
|
1773
|
+
function getAllDomains(ioc) {
|
|
1774
|
+
const { exfiltration, tunnel, oast, paste, c2 } = ioc.malicious_domains;
|
|
1775
|
+
return [...exfiltration, ...tunnel, ...oast, ...paste, ...c2];
|
|
1776
|
+
}
|
|
1777
|
+
function getDomainCategory(ioc, domain) {
|
|
1778
|
+
const d = domain.toLowerCase();
|
|
1779
|
+
const categories = ["exfiltration", "tunnel", "oast", "paste", "c2"];
|
|
1780
|
+
for (const cat of categories) {
|
|
1781
|
+
if (ioc.malicious_domains[cat].some(
|
|
1782
|
+
(entry) => d === entry || d.endsWith("." + entry)
|
|
1783
|
+
)) {
|
|
1784
|
+
return cat;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return void 0;
|
|
1788
|
+
}
|
|
1652
1789
|
function dedupe(arr) {
|
|
1653
1790
|
return [...new Set(arr)];
|
|
1654
1791
|
}
|
|
@@ -1657,6 +1794,24 @@ function dedupe(arr) {
|
|
|
1657
1794
|
var FALLBACK_SUSPICIOUS_DOMAINS = [
|
|
1658
1795
|
"darkweb.onion"
|
|
1659
1796
|
];
|
|
1797
|
+
function isSensitiveDomainCombo(line) {
|
|
1798
|
+
if (/curl\b[^\n]*(?:-d|--data|--data-binary|--data-raw|--data-urlencode)\s+@/i.test(line)) {
|
|
1799
|
+
return true;
|
|
1800
|
+
}
|
|
1801
|
+
if (/curl\b[^\n]*(?:-F|--form)\s+[^\s=]+=@/i.test(line)) {
|
|
1802
|
+
return true;
|
|
1803
|
+
}
|
|
1804
|
+
if (/wget\b[^\n]*--post-file/i.test(line)) {
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
if (/\|\s*(?:sh|bash|zsh|python|node)\b/i.test(line)) {
|
|
1808
|
+
return true;
|
|
1809
|
+
}
|
|
1810
|
+
if (/(?:\.env|\.ssh|id_rsa|\.aws|credentials|\.netrc|\.git-credentials)/i.test(line)) {
|
|
1811
|
+
return true;
|
|
1812
|
+
}
|
|
1813
|
+
return false;
|
|
1814
|
+
}
|
|
1660
1815
|
var MCP_SERVER_PATTERN = /\bmcp[-_]?server\b/i;
|
|
1661
1816
|
var NPX_Y_PATTERN = /\bnpx\s+-y\s+/;
|
|
1662
1817
|
var NPM_INSTALL_PATTERN = /\bnpm\s+install\b/;
|
|
@@ -1681,7 +1836,10 @@ var supplyChainChecks = {
|
|
|
1681
1836
|
const results = [];
|
|
1682
1837
|
const allText = getAllText(skill);
|
|
1683
1838
|
const ioc = loadIOC();
|
|
1684
|
-
const suspiciousDomains = ioc
|
|
1839
|
+
const suspiciousDomains = getAllDomains(ioc);
|
|
1840
|
+
if (suspiciousDomains.length === 0) {
|
|
1841
|
+
suspiciousDomains.push(...FALLBACK_SUSPICIOUS_DOMAINS);
|
|
1842
|
+
}
|
|
1685
1843
|
for (let i = 0; i < allText.length; i++) {
|
|
1686
1844
|
const { line, lineNum, source } = allText[i];
|
|
1687
1845
|
if (MCP_SERVER_PATTERN.test(line)) {
|
|
@@ -1844,15 +2002,46 @@ var supplyChainChecks = {
|
|
|
1844
2002
|
const hostname = extractHostname(url);
|
|
1845
2003
|
for (const domain of suspiciousDomains) {
|
|
1846
2004
|
if (hostnameMatchesDomain(hostname, domain)) {
|
|
2005
|
+
const category = getDomainCategory(ioc, domain);
|
|
2006
|
+
const categoryLabel = category ? ` (${category})` : "";
|
|
2007
|
+
let severity = "HIGH";
|
|
2008
|
+
let reducedFrom;
|
|
2009
|
+
let msgSuffix = "";
|
|
2010
|
+
if (isSensitiveDomainCombo(line)) {
|
|
2011
|
+
severity = "CRITICAL";
|
|
2012
|
+
msgSuffix = " [escalated: combined with sensitive operation]";
|
|
2013
|
+
} else {
|
|
2014
|
+
const srcLines = getLinesForSource(skill, source);
|
|
2015
|
+
const localIdx = getLocalIndex(source, lineNum, skill.bodyStartLine);
|
|
2016
|
+
const inCodeBlock = localIdx >= 0 && isInCodeBlock(srcLines, localIdx);
|
|
2017
|
+
if (inCodeBlock) {
|
|
2018
|
+
severity = "MEDIUM";
|
|
2019
|
+
reducedFrom = "HIGH";
|
|
2020
|
+
msgSuffix = " [reduced: in code block]";
|
|
2021
|
+
} else {
|
|
2022
|
+
const allLines = getAllLines(skill);
|
|
2023
|
+
const globalIdx = findGlobalLineIndex(allLines, source, lineNum);
|
|
2024
|
+
const isDoc = source === "SKILL.md" && globalIdx >= 0 && isInDocumentationContext(
|
|
2025
|
+
allLines.map((l) => l.line),
|
|
2026
|
+
globalIdx
|
|
2027
|
+
);
|
|
2028
|
+
if (isDoc) {
|
|
2029
|
+
severity = "LOW";
|
|
2030
|
+
reducedFrom = "HIGH";
|
|
2031
|
+
msgSuffix = " [reduced: in documentation context]";
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
1847
2035
|
results.push({
|
|
1848
2036
|
id: "SUPPLY-007",
|
|
1849
2037
|
category: "SUPPLY",
|
|
1850
|
-
severity
|
|
1851
|
-
title:
|
|
1852
|
-
message: `${source}:${lineNum}: References suspicious domain "${domain}"
|
|
2038
|
+
severity,
|
|
2039
|
+
title: `Suspicious domain${categoryLabel} detected`,
|
|
2040
|
+
message: `${source}:${lineNum}: References suspicious domain "${domain}".${msgSuffix}`,
|
|
1853
2041
|
line: lineNum,
|
|
1854
2042
|
snippet: url,
|
|
1855
|
-
source
|
|
2043
|
+
source,
|
|
2044
|
+
reducedFrom
|
|
1856
2045
|
});
|
|
1857
2046
|
break;
|
|
1858
2047
|
}
|