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/index.d.ts
CHANGED
|
@@ -141,16 +141,19 @@ declare function generateHookResponse(report: ScanReport, config?: SkillCheckerC
|
|
|
141
141
|
*/
|
|
142
142
|
declare function loadConfig(startDir?: string, configPath?: string): SkillCheckerConfig;
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
interface CategorizedDomains {
|
|
145
|
+
exfiltration: string[];
|
|
146
|
+
tunnel: string[];
|
|
147
|
+
oast: string[];
|
|
148
|
+
paste: string[];
|
|
149
|
+
c2: string[];
|
|
150
|
+
}
|
|
148
151
|
interface IOCDatabase {
|
|
149
152
|
version: string;
|
|
150
153
|
updated: string;
|
|
151
154
|
c2_ips: string[];
|
|
152
155
|
malicious_hashes: Record<string, string>;
|
|
153
|
-
malicious_domains:
|
|
156
|
+
malicious_domains: CategorizedDomains;
|
|
154
157
|
typosquat: {
|
|
155
158
|
known_patterns: string[];
|
|
156
159
|
protected_names: string[];
|
package/dist/index.js
CHANGED
|
@@ -336,6 +336,70 @@ var structuralChecks = {
|
|
|
336
336
|
}
|
|
337
337
|
};
|
|
338
338
|
|
|
339
|
+
// src/types.ts
|
|
340
|
+
var SEVERITY_SCORES = {
|
|
341
|
+
CRITICAL: 25,
|
|
342
|
+
HIGH: 10,
|
|
343
|
+
MEDIUM: 3,
|
|
344
|
+
LOW: 1
|
|
345
|
+
};
|
|
346
|
+
function computeGrade(score) {
|
|
347
|
+
if (score >= 90) return "A";
|
|
348
|
+
if (score >= 75) return "B";
|
|
349
|
+
if (score >= 60) return "C";
|
|
350
|
+
if (score >= 40) return "D";
|
|
351
|
+
return "F";
|
|
352
|
+
}
|
|
353
|
+
var REDUCE_MAP = {
|
|
354
|
+
CRITICAL: "HIGH",
|
|
355
|
+
HIGH: "MEDIUM",
|
|
356
|
+
MEDIUM: "LOW",
|
|
357
|
+
LOW: "LOW"
|
|
358
|
+
};
|
|
359
|
+
function reduceSeverity(original, reason) {
|
|
360
|
+
let reduced = REDUCE_MAP[original];
|
|
361
|
+
if (original === "CRITICAL" && reduced === "LOW") {
|
|
362
|
+
reduced = "MEDIUM";
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
severity: reduced,
|
|
366
|
+
reducedFrom: original,
|
|
367
|
+
annotation: `[reduced: ${reason}]`
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
var DEFAULT_CONFIG = {
|
|
371
|
+
policy: "balanced",
|
|
372
|
+
overrides: {},
|
|
373
|
+
ignore: []
|
|
374
|
+
};
|
|
375
|
+
function getHookAction(policy, severity) {
|
|
376
|
+
const matrix = {
|
|
377
|
+
strict: {
|
|
378
|
+
CRITICAL: "deny",
|
|
379
|
+
HIGH: "deny",
|
|
380
|
+
MEDIUM: "ask",
|
|
381
|
+
LOW: "report"
|
|
382
|
+
},
|
|
383
|
+
balanced: {
|
|
384
|
+
CRITICAL: "deny",
|
|
385
|
+
HIGH: "ask",
|
|
386
|
+
MEDIUM: "report",
|
|
387
|
+
LOW: "report"
|
|
388
|
+
},
|
|
389
|
+
permissive: {
|
|
390
|
+
CRITICAL: "ask",
|
|
391
|
+
HIGH: "report",
|
|
392
|
+
MEDIUM: "report",
|
|
393
|
+
LOW: "report"
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
const row = matrix[policy];
|
|
397
|
+
if (!row) {
|
|
398
|
+
return matrix.balanced[severity];
|
|
399
|
+
}
|
|
400
|
+
return row[severity];
|
|
401
|
+
}
|
|
402
|
+
|
|
339
403
|
// src/utils/context.ts
|
|
340
404
|
function isInCodeBlock(lines, lineIndex) {
|
|
341
405
|
let inBlock = false;
|
|
@@ -399,6 +463,49 @@ function isLicenseFile(filePath) {
|
|
|
399
463
|
function isLocalhostURL(url) {
|
|
400
464
|
return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])/i.test(url);
|
|
401
465
|
}
|
|
466
|
+
function isInEducationalContext(lines, lineIndex) {
|
|
467
|
+
const line = lines[lineIndex];
|
|
468
|
+
if (/^#{1,6}\s+/.test(line)) return true;
|
|
469
|
+
if (/^\s*[-*]?\s*(\*\*[^*]+\*\*\s*:|[A-Z][^:]{0,40}:)\s/.test(line))
|
|
470
|
+
return true;
|
|
471
|
+
for (let i = lineIndex; i >= Math.max(0, lineIndex - 15); i--) {
|
|
472
|
+
if (/^#{1,4}\s+.*(strateg|guide|framework|structure|model|overview|comparison|concept|principle|example|tutorial|reference|approach|method)/i.test(
|
|
473
|
+
lines[i]
|
|
474
|
+
)) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
var PROMOTIONAL_INTENT_PATTERNS = [
|
|
481
|
+
/\d+%\s*off\b/i,
|
|
482
|
+
/\blimited\s+time\b/i,
|
|
483
|
+
/\bact\s+now\b/i,
|
|
484
|
+
/\bhurry\b/i,
|
|
485
|
+
/\btoday\s+only\b/i,
|
|
486
|
+
/\bdon'?t\s+miss\b/i,
|
|
487
|
+
/\bsave\s+\d+%/i,
|
|
488
|
+
/\bexclusive\s+(offer|deal)\b/i,
|
|
489
|
+
/\bsign\s+up\s+(now|today)\b/i,
|
|
490
|
+
/\bget\s+started\b/i,
|
|
491
|
+
/\bclaim\s+(now|yours?)\b/i,
|
|
492
|
+
/\boffer\s+ends?\b/i,
|
|
493
|
+
/\blast\s+chance\b/i,
|
|
494
|
+
/\bonly\s+\d+\s+left\b/i,
|
|
495
|
+
/\bends?\s+in\s+\d+/i,
|
|
496
|
+
/\bstart\s+(your\s+)?free\s+trial\b/i
|
|
497
|
+
];
|
|
498
|
+
function hasPromotionalIntent(line) {
|
|
499
|
+
return PROMOTIONAL_INTENT_PATTERNS.some((p) => p.test(line));
|
|
500
|
+
}
|
|
501
|
+
function hasPromotionalIntentNearby(lines, lineIndex, window = 3) {
|
|
502
|
+
const start = Math.max(0, lineIndex - window);
|
|
503
|
+
const end = Math.min(lines.length - 1, lineIndex + window);
|
|
504
|
+
for (let i = start; i <= end; i++) {
|
|
505
|
+
if (hasPromotionalIntent(lines[i])) return true;
|
|
506
|
+
}
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
402
509
|
function parseURLPath(url) {
|
|
403
510
|
try {
|
|
404
511
|
const u = new URL(url);
|
|
@@ -430,16 +537,18 @@ var LOREM_PATTERNS = [
|
|
|
430
537
|
/dolor\s+sit\s+amet/i,
|
|
431
538
|
/consectetur\s+adipiscing/i
|
|
432
539
|
];
|
|
433
|
-
var
|
|
540
|
+
var STRONG_AD_PATTERNS = [
|
|
434
541
|
/\bbuy\s+now\b/i,
|
|
435
|
-
/\
|
|
542
|
+
/\bclick\s+here\s+to\s+(buy|subscribe|download)/i,
|
|
543
|
+
/\buse\s+code\b.*\b\d+%?\s*off\b/i
|
|
544
|
+
];
|
|
545
|
+
var SOFT_AD_PATTERNS = [
|
|
436
546
|
/\bdiscount\b/i,
|
|
547
|
+
/\bfree\s+trial\b/i,
|
|
437
548
|
/\bpromo\s*code\b/i,
|
|
438
549
|
/\bsubscribe\s+(to|now)\b/i,
|
|
439
550
|
/\bsponsored\s+by\b/i,
|
|
440
551
|
/\baffiliate\s+link\b/i,
|
|
441
|
-
/\bclick\s+here\s+to\s+(buy|subscribe|download)/i,
|
|
442
|
-
/\buse\s+code\b.*\b\d+%?\s*off\b/i,
|
|
443
552
|
/\bcheck\s+out\s+my\b/i
|
|
444
553
|
];
|
|
445
554
|
var contentChecks = {
|
|
@@ -504,7 +613,8 @@ var contentChecks = {
|
|
|
504
613
|
checkDescriptionMismatch(results, skill);
|
|
505
614
|
for (let i = 0; i < skill.bodyLines.length; i++) {
|
|
506
615
|
const line = skill.bodyLines[i];
|
|
507
|
-
|
|
616
|
+
let matched = false;
|
|
617
|
+
for (const pattern of STRONG_AD_PATTERNS) {
|
|
508
618
|
if (pattern.test(line)) {
|
|
509
619
|
results.push({
|
|
510
620
|
id: "CONT-005",
|
|
@@ -513,8 +623,43 @@ var contentChecks = {
|
|
|
513
623
|
title: "Promotional/advertising content",
|
|
514
624
|
message: `Line ${skill.bodyStartLine + i}: Contains ad-like content.`,
|
|
515
625
|
line: skill.bodyStartLine + i,
|
|
516
|
-
snippet: line.trim().slice(0, 120)
|
|
626
|
+
snippet: line.trim().slice(0, 120),
|
|
627
|
+
source: "SKILL.md"
|
|
517
628
|
});
|
|
629
|
+
matched = true;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (matched) continue;
|
|
634
|
+
for (const pattern of SOFT_AD_PATTERNS) {
|
|
635
|
+
if (pattern.test(line)) {
|
|
636
|
+
const inCode = isInCodeBlock(skill.bodyLines, i);
|
|
637
|
+
const inEducational = isInEducationalContext(skill.bodyLines, i);
|
|
638
|
+
if ((inCode || inEducational) && !hasPromotionalIntentNearby(skill.bodyLines, i)) {
|
|
639
|
+
const reduction = reduceSeverity("HIGH", "educational/descriptive context");
|
|
640
|
+
results.push({
|
|
641
|
+
id: "CONT-005",
|
|
642
|
+
category: "CONT",
|
|
643
|
+
severity: reduction.severity,
|
|
644
|
+
title: "Promotional/advertising content",
|
|
645
|
+
message: `Line ${skill.bodyStartLine + i}: Contains ad-like content. ${reduction.annotation}`,
|
|
646
|
+
line: skill.bodyStartLine + i,
|
|
647
|
+
snippet: line.trim().slice(0, 120),
|
|
648
|
+
reducedFrom: reduction.reducedFrom,
|
|
649
|
+
source: "SKILL.md"
|
|
650
|
+
});
|
|
651
|
+
} else {
|
|
652
|
+
results.push({
|
|
653
|
+
id: "CONT-005",
|
|
654
|
+
category: "CONT",
|
|
655
|
+
severity: "HIGH",
|
|
656
|
+
title: "Promotional/advertising content",
|
|
657
|
+
message: `Line ${skill.bodyStartLine + i}: Contains ad-like content.`,
|
|
658
|
+
line: skill.bodyStartLine + i,
|
|
659
|
+
snippet: line.trim().slice(0, 120),
|
|
660
|
+
source: "SKILL.md"
|
|
661
|
+
});
|
|
662
|
+
}
|
|
518
663
|
break;
|
|
519
664
|
}
|
|
520
665
|
}
|
|
@@ -993,70 +1138,6 @@ function dedup(results) {
|
|
|
993
1138
|
});
|
|
994
1139
|
}
|
|
995
1140
|
|
|
996
|
-
// src/types.ts
|
|
997
|
-
var SEVERITY_SCORES = {
|
|
998
|
-
CRITICAL: 25,
|
|
999
|
-
HIGH: 10,
|
|
1000
|
-
MEDIUM: 3,
|
|
1001
|
-
LOW: 1
|
|
1002
|
-
};
|
|
1003
|
-
function computeGrade(score) {
|
|
1004
|
-
if (score >= 90) return "A";
|
|
1005
|
-
if (score >= 75) return "B";
|
|
1006
|
-
if (score >= 60) return "C";
|
|
1007
|
-
if (score >= 40) return "D";
|
|
1008
|
-
return "F";
|
|
1009
|
-
}
|
|
1010
|
-
var REDUCE_MAP = {
|
|
1011
|
-
CRITICAL: "HIGH",
|
|
1012
|
-
HIGH: "MEDIUM",
|
|
1013
|
-
MEDIUM: "LOW",
|
|
1014
|
-
LOW: "LOW"
|
|
1015
|
-
};
|
|
1016
|
-
function reduceSeverity(original, reason) {
|
|
1017
|
-
let reduced = REDUCE_MAP[original];
|
|
1018
|
-
if (original === "CRITICAL" && reduced === "LOW") {
|
|
1019
|
-
reduced = "MEDIUM";
|
|
1020
|
-
}
|
|
1021
|
-
return {
|
|
1022
|
-
severity: reduced,
|
|
1023
|
-
reducedFrom: original,
|
|
1024
|
-
annotation: `[reduced: ${reason}]`
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
var DEFAULT_CONFIG = {
|
|
1028
|
-
policy: "balanced",
|
|
1029
|
-
overrides: {},
|
|
1030
|
-
ignore: []
|
|
1031
|
-
};
|
|
1032
|
-
function getHookAction(policy, severity) {
|
|
1033
|
-
const matrix = {
|
|
1034
|
-
strict: {
|
|
1035
|
-
CRITICAL: "deny",
|
|
1036
|
-
HIGH: "deny",
|
|
1037
|
-
MEDIUM: "ask",
|
|
1038
|
-
LOW: "report"
|
|
1039
|
-
},
|
|
1040
|
-
balanced: {
|
|
1041
|
-
CRITICAL: "deny",
|
|
1042
|
-
HIGH: "ask",
|
|
1043
|
-
MEDIUM: "report",
|
|
1044
|
-
LOW: "report"
|
|
1045
|
-
},
|
|
1046
|
-
permissive: {
|
|
1047
|
-
CRITICAL: "ask",
|
|
1048
|
-
HIGH: "report",
|
|
1049
|
-
MEDIUM: "report",
|
|
1050
|
-
LOW: "report"
|
|
1051
|
-
}
|
|
1052
|
-
};
|
|
1053
|
-
const row = matrix[policy];
|
|
1054
|
-
if (!row) {
|
|
1055
|
-
return matrix.balanced[severity];
|
|
1056
|
-
}
|
|
1057
|
-
return row[severity];
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
1141
|
// src/checks/code-safety.ts
|
|
1061
1142
|
var EVAL_PATTERNS = [
|
|
1062
1143
|
/\beval\s*\(/,
|
|
@@ -1533,8 +1614,8 @@ import { homedir } from "os";
|
|
|
1533
1614
|
|
|
1534
1615
|
// src/ioc/indicators.ts
|
|
1535
1616
|
var DEFAULT_IOC = {
|
|
1536
|
-
version: "2026.03.
|
|
1537
|
-
updated: "2026-03-
|
|
1617
|
+
version: "2026.03.16",
|
|
1618
|
+
updated: "2026-03-16",
|
|
1538
1619
|
c2_ips: [
|
|
1539
1620
|
"91.92.242.30",
|
|
1540
1621
|
"91.92.242.39",
|
|
@@ -1543,36 +1624,65 @@ var DEFAULT_IOC = {
|
|
|
1543
1624
|
"45.155.205.233"
|
|
1544
1625
|
],
|
|
1545
1626
|
malicious_hashes: {
|
|
1546
|
-
// NOTE: Never add the
|
|
1627
|
+
// NOTE: Never add the SHA256 of an empty file (e3b0c44298fc...b855)
|
|
1547
1628
|
// as it causes false positives on any empty file.
|
|
1548
1629
|
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2": "clawhavoc-exfiltrator"
|
|
1549
1630
|
},
|
|
1550
|
-
malicious_domains:
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1631
|
+
malicious_domains: {
|
|
1632
|
+
exfiltration: [
|
|
1633
|
+
"webhook.site",
|
|
1634
|
+
"requestbin.com",
|
|
1635
|
+
"requestcatcher.com",
|
|
1636
|
+
"pipedream.com",
|
|
1637
|
+
"pipedream.net",
|
|
1638
|
+
"hookbin.com",
|
|
1639
|
+
"beeceptor.com",
|
|
1640
|
+
"postb.in",
|
|
1641
|
+
"webhook.lol",
|
|
1642
|
+
"requestinspector.com",
|
|
1643
|
+
"mockbin.org"
|
|
1644
|
+
],
|
|
1645
|
+
tunnel: [
|
|
1646
|
+
"ngrok.io",
|
|
1647
|
+
"ngrok-free.app",
|
|
1648
|
+
"serveo.net",
|
|
1649
|
+
"localtunnel.me",
|
|
1650
|
+
"bore.pub",
|
|
1651
|
+
"localhost.run",
|
|
1652
|
+
"loca.lt",
|
|
1653
|
+
"telebit.cloud",
|
|
1654
|
+
"playit.gg",
|
|
1655
|
+
"portmap.io",
|
|
1656
|
+
"pagekite.me"
|
|
1657
|
+
],
|
|
1658
|
+
oast: [
|
|
1659
|
+
"interact.sh",
|
|
1660
|
+
"oast.fun",
|
|
1661
|
+
"oastify.com",
|
|
1662
|
+
"dnslog.cn",
|
|
1663
|
+
"ceye.io",
|
|
1664
|
+
"burpcollaborator.net",
|
|
1665
|
+
"canarytokens.com",
|
|
1666
|
+
"requestrepo.com"
|
|
1667
|
+
],
|
|
1668
|
+
paste: [
|
|
1669
|
+
"pastebin.com",
|
|
1670
|
+
"paste.ee",
|
|
1671
|
+
"hastebin.com",
|
|
1672
|
+
"ghostbin.com",
|
|
1673
|
+
"dpaste.org",
|
|
1674
|
+
"rentry.co",
|
|
1675
|
+
"0bin.net",
|
|
1676
|
+
"privatebin.net",
|
|
1677
|
+
"paste.mozilla.org"
|
|
1678
|
+
],
|
|
1679
|
+
c2: [
|
|
1680
|
+
"evil.com",
|
|
1681
|
+
"malware.com",
|
|
1682
|
+
"exploit.in",
|
|
1683
|
+
"darkweb.onion"
|
|
1684
|
+
]
|
|
1685
|
+
},
|
|
1576
1686
|
typosquat: {
|
|
1577
1687
|
known_patterns: [
|
|
1578
1688
|
"clawhub1",
|
|
@@ -1632,10 +1742,21 @@ function mergeIOC(base, ext) {
|
|
|
1632
1742
|
Object.assign(base.malicious_hashes, ext.malicious_hashes);
|
|
1633
1743
|
}
|
|
1634
1744
|
if (ext.malicious_domains) {
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1745
|
+
const categories = [
|
|
1746
|
+
"exfiltration",
|
|
1747
|
+
"tunnel",
|
|
1748
|
+
"oast",
|
|
1749
|
+
"paste",
|
|
1750
|
+
"c2"
|
|
1751
|
+
];
|
|
1752
|
+
for (const cat of categories) {
|
|
1753
|
+
if (ext.malicious_domains[cat]) {
|
|
1754
|
+
base.malicious_domains[cat] = dedupe([
|
|
1755
|
+
...base.malicious_domains[cat],
|
|
1756
|
+
...ext.malicious_domains[cat]
|
|
1757
|
+
]);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1639
1760
|
}
|
|
1640
1761
|
if (ext.typosquat) {
|
|
1641
1762
|
if (ext.typosquat.known_patterns) {
|
|
@@ -1660,6 +1781,22 @@ function mergeIOC(base, ext) {
|
|
|
1660
1781
|
if (ext.version) base.version = ext.version;
|
|
1661
1782
|
if (ext.updated) base.updated = ext.updated;
|
|
1662
1783
|
}
|
|
1784
|
+
function getAllDomains(ioc) {
|
|
1785
|
+
const { exfiltration, tunnel, oast, paste, c2 } = ioc.malicious_domains;
|
|
1786
|
+
return [...exfiltration, ...tunnel, ...oast, ...paste, ...c2];
|
|
1787
|
+
}
|
|
1788
|
+
function getDomainCategory(ioc, domain) {
|
|
1789
|
+
const d = domain.toLowerCase();
|
|
1790
|
+
const categories = ["exfiltration", "tunnel", "oast", "paste", "c2"];
|
|
1791
|
+
for (const cat of categories) {
|
|
1792
|
+
if (ioc.malicious_domains[cat].some(
|
|
1793
|
+
(entry) => d === entry || d.endsWith("." + entry)
|
|
1794
|
+
)) {
|
|
1795
|
+
return cat;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return void 0;
|
|
1799
|
+
}
|
|
1663
1800
|
function dedupe(arr) {
|
|
1664
1801
|
return [...new Set(arr)];
|
|
1665
1802
|
}
|
|
@@ -1668,6 +1805,24 @@ function dedupe(arr) {
|
|
|
1668
1805
|
var FALLBACK_SUSPICIOUS_DOMAINS = [
|
|
1669
1806
|
"darkweb.onion"
|
|
1670
1807
|
];
|
|
1808
|
+
function isSensitiveDomainCombo(line) {
|
|
1809
|
+
if (/curl\b[^\n]*(?:-d|--data|--data-binary|--data-raw|--data-urlencode)\s+@/i.test(line)) {
|
|
1810
|
+
return true;
|
|
1811
|
+
}
|
|
1812
|
+
if (/curl\b[^\n]*(?:-F|--form)\s+[^\s=]+=@/i.test(line)) {
|
|
1813
|
+
return true;
|
|
1814
|
+
}
|
|
1815
|
+
if (/wget\b[^\n]*--post-file/i.test(line)) {
|
|
1816
|
+
return true;
|
|
1817
|
+
}
|
|
1818
|
+
if (/\|\s*(?:sh|bash|zsh|python|node)\b/i.test(line)) {
|
|
1819
|
+
return true;
|
|
1820
|
+
}
|
|
1821
|
+
if (/(?:\.env|\.ssh|id_rsa|\.aws|credentials|\.netrc|\.git-credentials)/i.test(line)) {
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1671
1826
|
var MCP_SERVER_PATTERN = /\bmcp[-_]?server\b/i;
|
|
1672
1827
|
var NPX_Y_PATTERN = /\bnpx\s+-y\s+/;
|
|
1673
1828
|
var NPM_INSTALL_PATTERN = /\bnpm\s+install\b/;
|
|
@@ -1692,7 +1847,10 @@ var supplyChainChecks = {
|
|
|
1692
1847
|
const results = [];
|
|
1693
1848
|
const allText = getAllText(skill);
|
|
1694
1849
|
const ioc = loadIOC();
|
|
1695
|
-
const suspiciousDomains = ioc
|
|
1850
|
+
const suspiciousDomains = getAllDomains(ioc);
|
|
1851
|
+
if (suspiciousDomains.length === 0) {
|
|
1852
|
+
suspiciousDomains.push(...FALLBACK_SUSPICIOUS_DOMAINS);
|
|
1853
|
+
}
|
|
1696
1854
|
for (let i = 0; i < allText.length; i++) {
|
|
1697
1855
|
const { line, lineNum, source } = allText[i];
|
|
1698
1856
|
if (MCP_SERVER_PATTERN.test(line)) {
|
|
@@ -1855,15 +2013,46 @@ var supplyChainChecks = {
|
|
|
1855
2013
|
const hostname = extractHostname(url);
|
|
1856
2014
|
for (const domain of suspiciousDomains) {
|
|
1857
2015
|
if (hostnameMatchesDomain(hostname, domain)) {
|
|
2016
|
+
const category = getDomainCategory(ioc, domain);
|
|
2017
|
+
const categoryLabel = category ? ` (${category})` : "";
|
|
2018
|
+
let severity = "HIGH";
|
|
2019
|
+
let reducedFrom;
|
|
2020
|
+
let msgSuffix = "";
|
|
2021
|
+
if (isSensitiveDomainCombo(line)) {
|
|
2022
|
+
severity = "CRITICAL";
|
|
2023
|
+
msgSuffix = " [escalated: combined with sensitive operation]";
|
|
2024
|
+
} else {
|
|
2025
|
+
const srcLines = getLinesForSource(skill, source);
|
|
2026
|
+
const localIdx = getLocalIndex(source, lineNum, skill.bodyStartLine);
|
|
2027
|
+
const inCodeBlock = localIdx >= 0 && isInCodeBlock(srcLines, localIdx);
|
|
2028
|
+
if (inCodeBlock) {
|
|
2029
|
+
severity = "MEDIUM";
|
|
2030
|
+
reducedFrom = "HIGH";
|
|
2031
|
+
msgSuffix = " [reduced: in code block]";
|
|
2032
|
+
} else {
|
|
2033
|
+
const allLines = getAllLines(skill);
|
|
2034
|
+
const globalIdx = findGlobalLineIndex(allLines, source, lineNum);
|
|
2035
|
+
const isDoc = source === "SKILL.md" && globalIdx >= 0 && isInDocumentationContext(
|
|
2036
|
+
allLines.map((l) => l.line),
|
|
2037
|
+
globalIdx
|
|
2038
|
+
);
|
|
2039
|
+
if (isDoc) {
|
|
2040
|
+
severity = "LOW";
|
|
2041
|
+
reducedFrom = "HIGH";
|
|
2042
|
+
msgSuffix = " [reduced: in documentation context]";
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
1858
2046
|
results.push({
|
|
1859
2047
|
id: "SUPPLY-007",
|
|
1860
2048
|
category: "SUPPLY",
|
|
1861
|
-
severity
|
|
1862
|
-
title:
|
|
1863
|
-
message: `${source}:${lineNum}: References suspicious domain "${domain}"
|
|
2049
|
+
severity,
|
|
2050
|
+
title: `Suspicious domain${categoryLabel} detected`,
|
|
2051
|
+
message: `${source}:${lineNum}: References suspicious domain "${domain}".${msgSuffix}`,
|
|
1864
2052
|
line: lineNum,
|
|
1865
2053
|
snippet: url,
|
|
1866
|
-
source
|
|
2054
|
+
source,
|
|
2055
|
+
reducedFrom
|
|
1867
2056
|
});
|
|
1868
2057
|
break;
|
|
1869
2058
|
}
|