@ship-safe/cli 1.1.3 → 1.1.5
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.js +30 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4210,7 +4210,8 @@ var NEXTJS_RULES = [
|
|
|
4210
4210
|
{ regex: "export\\s+(?:async\\s+)?function\\s+(?:GET|POST|PUT|DELETE|PATCH)\\s*\\(", type: "match" }
|
|
4211
4211
|
],
|
|
4212
4212
|
excludePatterns: [
|
|
4213
|
-
{ regex: "(?:auth|session|token|clerk|getAuth|currentUser|requireAuth|protect|webhook|public|health)", type: "context_line" }
|
|
4213
|
+
{ regex: "(?:auth|session|token|clerk|getAuth|currentUser|requireAuth|protect|webhook|public|health)", type: "context_line" },
|
|
4214
|
+
{ regex: "(?:webhook|callback|health|cron|stripe|checkout|unsubscribe|contact|export|badge)", type: "file_path" }
|
|
4214
4215
|
],
|
|
4215
4216
|
fix: { description: "Add authentication to your API route. Use middleware or check the session at the start of each handler.", suggestion: "const { userId } = auth();\nif (!userId) return new Response('Unauthorized', { status: 401 });" }
|
|
4216
4217
|
},
|
|
@@ -4893,6 +4894,10 @@ var XSS_RULES = [
|
|
|
4893
4894
|
owasp: "A03:2021",
|
|
4894
4895
|
languages: ["javascript", "typescript"],
|
|
4895
4896
|
patterns: [{ regex: "dangerouslySetInnerHTML", type: "match" }],
|
|
4897
|
+
excludePatterns: [
|
|
4898
|
+
{ regex: "(?:json-ld|jsonLd|structured-data|schema\\.org|ld\\+json|localStorage|theme|nonce|suppressHydration|DOMPurify|sanitize)", type: "context_line" },
|
|
4899
|
+
{ regex: "(?:json-ld|jsonld|structured-data)", type: "file_path" }
|
|
4900
|
+
],
|
|
4896
4901
|
fix: { description: "Avoid dangerouslySetInnerHTML. If you must use it, sanitize the HTML with DOMPurify or a similar library first." }
|
|
4897
4902
|
},
|
|
4898
4903
|
{
|
|
@@ -5574,10 +5579,12 @@ var HEADERS_RULES = [
|
|
|
5574
5579
|
owasp: "A10:2021",
|
|
5575
5580
|
languages: ["javascript", "typescript"],
|
|
5576
5581
|
patterns: [
|
|
5577
|
-
{ regex: "
|
|
5582
|
+
{ regex: "\\bfetch\\s*\\(\\s*(?:req\\.(?:body|query)|params|input|url|body|userUrl|targetUrl)", type: "match" },
|
|
5578
5583
|
{ regex: "axios\\.(?:get|post|request)\\s*\\(\\s*(?:req\\.(?:body|query)|params|input|url|body|userUrl)", type: "match" }
|
|
5579
5584
|
],
|
|
5580
|
-
excludePatterns: [
|
|
5585
|
+
excludePatterns: [
|
|
5586
|
+
{ regex: "(?:169\\.254|metadata|validateUrl|isValidUrl|blockInternal|isInternalIp|allowedDomains|whitelist|resolveAndValidate|safeFetch|pinnedUrl|ssrf)", type: "context_line" }
|
|
5587
|
+
],
|
|
5581
5588
|
fix: { description: "Block requests to cloud metadata IPs (169.254.169.254, fd00::, 10.x, 172.16-31.x, 192.168.x) before fetching user-provided URLs.", suggestion: "if (isInternalIP(url)) throw new Error('Internal URLs not allowed')" }
|
|
5582
5589
|
},
|
|
5583
5590
|
{
|
|
@@ -5590,10 +5597,10 @@ var HEADERS_RULES = [
|
|
|
5590
5597
|
owasp: "A10:2021",
|
|
5591
5598
|
languages: ["javascript", "typescript"],
|
|
5592
5599
|
patterns: [
|
|
5593
|
-
{ regex: "
|
|
5600
|
+
{ regex: "\\bfetch\\s*\\(\\s*(?:req|request|ctx)\\.(?:body|query|params)\\.\\w+\\s*\\)", type: "match" },
|
|
5594
5601
|
{ regex: "new\\s+URL\\s*\\(\\s*(?:req|request|ctx)\\.(?:body|query|params)\\.(?:url|target|link|href)", type: "match" }
|
|
5595
5602
|
],
|
|
5596
|
-
excludePatterns: [{ regex: "(?:isInternalIp|blockPrivate|127\\.0\\.0|10\\.|172\\.16|192\\.168|validateUrl|allowedHosts|dnsResolve)", type: "context_line" }],
|
|
5603
|
+
excludePatterns: [{ regex: "(?:isInternalIp|blockPrivate|127\\.0\\.0|10\\.|172\\.16|192\\.168|validateUrl|allowedHosts|dnsResolve|resolveAndValidate|safeFetch|pinnedUrl|ssrf)", type: "context_line" }],
|
|
5597
5604
|
fix: { description: "Validate user URLs by resolving DNS and checking the IP is not in a private range before making the request." }
|
|
5598
5605
|
}
|
|
5599
5606
|
];
|
|
@@ -5819,10 +5826,26 @@ function extractSnippet(content, line, contextLines = 5) {
|
|
|
5819
5826
|
return `${marker} ${lineNum} | ${l}`;
|
|
5820
5827
|
}).join("\n");
|
|
5821
5828
|
}
|
|
5829
|
+
function isCommentLine(line) {
|
|
5830
|
+
const trimmed = line.trim();
|
|
5831
|
+
return trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*") || trimmed.startsWith("* ") || trimmed.startsWith("*/") || trimmed === "*" || trimmed.startsWith("<!--");
|
|
5832
|
+
}
|
|
5833
|
+
function isInsideStringLiteral(line, pattern) {
|
|
5834
|
+
const match = pattern.exec(line);
|
|
5835
|
+
if (!match) return false;
|
|
5836
|
+
const idx = match.index;
|
|
5837
|
+
pattern.lastIndex = 0;
|
|
5838
|
+
let backtickCount = 0;
|
|
5839
|
+
for (let i = 0; i < idx; i++) {
|
|
5840
|
+
if (line[i] === "`" && (i === 0 || line[i - 1] !== "\\")) backtickCount++;
|
|
5841
|
+
}
|
|
5842
|
+
return backtickCount % 2 === 1;
|
|
5843
|
+
}
|
|
5822
5844
|
function matchRule(rule, file) {
|
|
5823
5845
|
if (!rule.languages.includes("*") && !rule.languages.includes(file.language)) {
|
|
5824
5846
|
return [];
|
|
5825
5847
|
}
|
|
5848
|
+
const isSecretRule = rule.id.startsWith("secrets/");
|
|
5826
5849
|
const findings = [];
|
|
5827
5850
|
const lines = file.content.split("\n");
|
|
5828
5851
|
for (const pattern of rule.patterns) {
|
|
@@ -5832,6 +5855,8 @@ function matchRule(rule, file) {
|
|
|
5832
5855
|
const line = lines[i];
|
|
5833
5856
|
if (!regex.test(line)) continue;
|
|
5834
5857
|
regex.lastIndex = 0;
|
|
5858
|
+
if (!isSecretRule && isCommentLine(line)) continue;
|
|
5859
|
+
if (!isSecretRule && isInsideStringLiteral(line, new RegExp(pattern.regex, "gi"))) continue;
|
|
5835
5860
|
if (rule.excludePatterns?.length) {
|
|
5836
5861
|
const excluded = rule.excludePatterns.some((ep) => {
|
|
5837
5862
|
const exRegex = new RegExp(ep.regex, "i");
|