xploitscan-shared-rules 1.6.2 → 1.7.0
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/index.cjs +32 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +32 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -887,6 +887,17 @@ function isInsideFixMessage(content, matchIndex) {
|
|
|
887
887
|
const lineText = content.substring(lineStart, content.indexOf("\n", matchIndex));
|
|
888
888
|
return /(?:fix|description|message|suggestion|hint|help|example|doc|comment)\s*[:=(]/i.test(lineText) || /return\s*["'`].*\b(?:Use|Replace|Add|Move|Set|Enable|Disable|Never|Don't|Do not|Instead)\b/i.test(lineText);
|
|
889
889
|
}
|
|
890
|
+
function isInlineSilenced(content, matchIndex, ruleId) {
|
|
891
|
+
const lines = content.split("\n");
|
|
892
|
+
const matchLineNum = content.substring(0, matchIndex).split("\n").length - 1;
|
|
893
|
+
const matchLine = lines[matchLineNum] ?? "";
|
|
894
|
+
const prevLine = matchLineNum > 0 ? lines[matchLineNum - 1] ?? "" : "";
|
|
895
|
+
const marker = new RegExp(
|
|
896
|
+
`(?://|/\\*|#)\\s*(?:${ruleId}|scanner)-OK\\b`,
|
|
897
|
+
"i"
|
|
898
|
+
);
|
|
899
|
+
return marker.test(matchLine) || marker.test(prevLine);
|
|
900
|
+
}
|
|
890
901
|
function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
891
902
|
const matches = [];
|
|
892
903
|
const lines = content.split("\n");
|
|
@@ -1015,6 +1026,16 @@ var hardcodedSecrets = {
|
|
|
1015
1026
|
const secretMatch = lineText.match(/[:=]\s*["'`]([^"'`]*)["'`]/);
|
|
1016
1027
|
if (secretMatch && secretMatch[1].length < floor) continue;
|
|
1017
1028
|
}
|
|
1029
|
+
if (pi === 6) {
|
|
1030
|
+
const valMatch = lineText.match(/[:=]\s*["'`]([^"'`]+)["'`]/);
|
|
1031
|
+
const nameMatch = lineText.match(/\b([A-Z][A-Z0-9_]*_(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD))\b/);
|
|
1032
|
+
if (valMatch && nameMatch) {
|
|
1033
|
+
const value = valMatch[1];
|
|
1034
|
+
const isKeySuffix = nameMatch[1].endsWith("_KEY");
|
|
1035
|
+
const isKebabIdentifier = value.length < 40 && /^[a-z0-9]+(-[a-z0-9]+)+$/.test(value);
|
|
1036
|
+
if (isKeySuffix && isKebabIdentifier) continue;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1018
1039
|
matches.push(rm);
|
|
1019
1040
|
}
|
|
1020
1041
|
}
|
|
@@ -1208,7 +1229,7 @@ var sqlInjection = {
|
|
|
1208
1229
|
/(?:SELECT|INSERT|UPDATE|DELETE|WHERE)\s+.*\$\{(?!.*parameterized)/gi
|
|
1209
1230
|
];
|
|
1210
1231
|
const matches = [];
|
|
1211
|
-
const usesParams = /\?\s
|
|
1232
|
+
const usesParams = /\?\s*,|\?[^?,;]{1,20}\?|\$\d+|:[\w]+|\bprepare\b|\bplaceholders?\b/i.test(content);
|
|
1212
1233
|
if (usesParams) return [];
|
|
1213
1234
|
for (const pattern of patterns) {
|
|
1214
1235
|
const raw = findMatches(
|
|
@@ -3299,6 +3320,7 @@ var dangerousInnerHTML = {
|
|
|
3299
3320
|
let m;
|
|
3300
3321
|
while ((m = re.exec(content)) !== null) {
|
|
3301
3322
|
if (isCommentLine(content, m.index)) continue;
|
|
3323
|
+
if (isInlineSilenced(content, m.index, "VC063")) continue;
|
|
3302
3324
|
const value = m[1].trim();
|
|
3303
3325
|
if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
|
|
3304
3326
|
if (/^["'`][^$]*["'`]$/.test(value)) continue;
|
|
@@ -3312,7 +3334,7 @@ var dangerousInnerHTML = {
|
|
|
3312
3334
|
file: filePath,
|
|
3313
3335
|
line: lineNum,
|
|
3314
3336
|
snippet: getSnippet(content, lineNum),
|
|
3315
|
-
fix: "Sanitize HTML before using dangerouslySetInnerHTML: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Install: npm install dompurify"
|
|
3337
|
+
fix: "Sanitize HTML before using dangerouslySetInnerHTML: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Install: npm install dompurify. If this site is intentional (server-built template, nonce-attribute boot script, etc.), add an inline `// VC063-OK: <reason>` comment above the line to silence."
|
|
3316
3338
|
});
|
|
3317
3339
|
}
|
|
3318
3340
|
return findings;
|
|
@@ -7314,6 +7336,7 @@ var secretInURLParam = {
|
|
|
7314
7336
|
while ((m = re.exec(content)) !== null) {
|
|
7315
7337
|
if (isCommentLine(content, m.index)) continue;
|
|
7316
7338
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7339
|
+
if (isInlineSilenced(content, m.index, "VC146")) continue;
|
|
7317
7340
|
const lineNum = content.substring(0, m.index).split("\n").length;
|
|
7318
7341
|
findings.push({
|
|
7319
7342
|
rule: "VC146",
|
|
@@ -7323,7 +7346,7 @@ var secretInURLParam = {
|
|
|
7323
7346
|
file: filePath,
|
|
7324
7347
|
line: lineNum,
|
|
7325
7348
|
snippet: getSnippet(content, lineNum),
|
|
7326
|
-
fix: "Pass secrets in the Authorization header (Bearer token) or request body, never in URL parameters. URL parameters are logged in server access logs, browser history, and referrer headers."
|
|
7349
|
+
fix: "Pass secrets in the Authorization header (Bearer token) or request body, never in URL parameters. URL parameters are logged in server access logs, browser history, and referrer headers. If this finding is legitimate (magic-link / claim-token flow), add an inline `// VC146-OK: <reason>` comment above the line to silence it."
|
|
7327
7350
|
});
|
|
7328
7351
|
}
|
|
7329
7352
|
}
|
|
@@ -7514,10 +7537,14 @@ var webhookSignatureVerification = {
|
|
|
7514
7537
|
check(content, filePath) {
|
|
7515
7538
|
if (!filePath.match(/\.(js|ts|jsx|tsx)$/)) return [];
|
|
7516
7539
|
if (isTestFile(filePath)) return [];
|
|
7517
|
-
if (
|
|
7540
|
+
if (!/webhook/i.test(filePath)) return [];
|
|
7518
7541
|
if (!/export\s+(?:async\s+)?function\s+POST/i.test(content)) return [];
|
|
7519
7542
|
const services = [
|
|
7520
|
-
|
|
7543
|
+
// Tightened Clerk events: require the specific subevent suffix
|
|
7544
|
+
// rather than bare `session.`. The bare-prefix version matched
|
|
7545
|
+
// Stripe Checkout `session.url`, which has nothing to do with
|
|
7546
|
+
// Clerk webhooks.
|
|
7547
|
+
{ name: "Clerk", content: /clerk/i, events: /user\.(created|updated|deleted)|session\.(created|removed|ended|revoked)/i, verify: /svix|Webhook\(\)\.verify|webhook-id|wh_secret/i },
|
|
7521
7548
|
{ name: "GitHub", content: /github/i, events: /push|pull_request|issues|installation/i, verify: /createHmac|x-hub-signature|verify.*signature|GITHUB_WEBHOOK_SECRET/i },
|
|
7522
7549
|
{ name: "Resend", content: /resend/i, events: /email\.sent|email\.delivered|email\.bounced/i, verify: /svix|webhook.*verify|x-webhook-signature|RESEND_WEBHOOK_SECRET/i },
|
|
7523
7550
|
{ name: "SendGrid", content: /sendgrid/i, events: /inbound.*parse|event.*webhook/i, verify: /EventWebhook|x-twilio-email|verifySignature|SENDGRID_WEBHOOK_VERIFICATION/i },
|