@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.
Files changed (2) hide show
  1. package/dist/index.js +30 -5
  2. 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: "fetch\\s*\\(\\s*(?:req\\.(?:body|query)|params|input|url|body|userUrl|targetUrl)", type: "match" },
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: [{ regex: "(?:169\\.254|metadata|validateUrl|isValidUrl|blockInternal|isInternalIp|allowedDomains|whitelist)", type: "context_line" }],
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: "fetch\\s*\\(\\s*(?:req|request|ctx)\\.(?:body|query|params)\\.\\w+\\s*\\)", type: "match" },
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ship-safe/cli",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Security scanner for AI-generated code — find vulnerabilities before you ship",
5
5
  "type": "module",
6
6
  "license": "MIT",