xploitscan-shared-rules 1.13.1 → 1.14.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 CHANGED
@@ -97,6 +97,7 @@ __export(index_exports, {
97
97
  hardcodedAnthropicKey: () => hardcodedAnthropicKey,
98
98
  hardcodedCloudflareToken: () => hardcodedCloudflareToken,
99
99
  hardcodedCohereKey: () => hardcodedCohereKey,
100
+ hardcodedCreditCard: () => hardcodedCreditCard,
100
101
  hardcodedDatadogKey: () => hardcodedDatadogKey,
101
102
  hardcodedDiscordToken: () => hardcodedDiscordToken,
102
103
  hardcodedEncryptionKey: () => hardcodedEncryptionKey,
@@ -126,6 +127,7 @@ __export(index_exports, {
126
127
  hardcodedRailwayToken: () => hardcodedRailwayToken,
127
128
  hardcodedReplicateKey: () => hardcodedReplicateKey,
128
129
  hardcodedResendKey: () => hardcodedResendKey,
130
+ hardcodedSSN: () => hardcodedSSN,
129
131
  hardcodedSecrets: () => hardcodedSecrets,
130
132
  hardcodedSendGridKey: () => hardcodedSendGridKey,
131
133
  hardcodedSentryAuthToken: () => hardcodedSentryAuthToken,
@@ -521,7 +523,9 @@ var RULE_IMPACTS = {
521
523
  VC207: "Model output is attacker-influenceable via prompt injection. Feeding it into eval, new Function, a shell command, a raw SQL string, or a filesystem path turns a crafted or hallucinated response into remote code execution, command injection, SQL injection, or path traversal \u2014 your most dangerous sinks, driven by untrusted text.",
522
524
  VC208: "Interpolating a secret into a prompt ships your API key, token, or password to a third-party model provider, where it persists in their request logs and training-eligible data. A credential that leaves your infrastructure in prompt text should be considered compromised and rotated.",
523
525
  VC209: "Webhooks are delivered at-least-once. Without de-duplicating on the event id, a retried or replayed delivery re-runs the side effect \u2014 a customer is charged twice, a record is duplicated, or an entitlement is granted again. Stripe and Svix both retry on any non-2xx, so this fires in normal operation, not just under attack.",
524
- VC210: "If your auth middleware skips /api, those routes run with no gate unless each one re-checks auth itself. It is the most common way a Next.js app ends up with publicly callable API routes that everyone assumed the middleware was protecting."
526
+ VC210: "If your auth middleware skips /api, those routes run with no gate unless each one re-checks auth itself. It is the most common way a Next.js app ends up with publicly callable API routes that everyone assumed the middleware was protecting.",
527
+ VC211: "A real credit card number in source is cardholder data sitting in git history, CI logs, and every backup \u2014 a direct PCI-DSS violation. Anyone with repo access can read it, and it cannot be un-leaked once committed; the card must be treated as compromised.",
528
+ VC212: "A hardcoded Social Security Number is regulated PII permanently embedded in your git history and backups. It exposes a real person to identity theft, and its presence in source can trigger breach-notification and privacy-law obligations the moment the repo is accessed."
525
529
  };
526
530
 
527
531
  // src/exposure.ts
@@ -5062,8 +5066,13 @@ var complianceMap = {
5062
5066
  // VC209–VC210: advisory heuristics
5063
5067
  VC209: { owasp: "A04:2021", cwe: "CWE-799" },
5064
5068
  // webhook missing idempotency
5065
- VC210: { owasp: "A01:2021", cwe: "CWE-862" }
5069
+ VC210: { owasp: "A01:2021", cwe: "CWE-862" },
5066
5070
  // middleware matcher excludes /api
5071
+ // VC211–VC212: hardcoded sensitive personal data (PII) in source
5072
+ VC211: { owasp: "A02:2021", cwe: "CWE-540" },
5073
+ // hardcoded credit card number
5074
+ VC212: { owasp: "A02:2021", cwe: "CWE-540" }
5075
+ // hardcoded US SSN
5067
5076
  };
5068
5077
  var consoleLogProduction = {
5069
5078
  id: "VC097",
@@ -7888,6 +7897,113 @@ var middlewareMatcherExcludesApi = {
7888
7897
  }], content, "VC210");
7889
7898
  }
7890
7899
  };
7900
+ function passesLuhn(digits) {
7901
+ let sum = 0;
7902
+ let alt = false;
7903
+ for (let i = digits.length - 1; i >= 0; i--) {
7904
+ let d = digits.charCodeAt(i) - 48;
7905
+ if (d < 0 || d > 9) return false;
7906
+ if (alt) {
7907
+ d *= 2;
7908
+ if (d > 9) d -= 9;
7909
+ }
7910
+ sum += d;
7911
+ alt = !alt;
7912
+ }
7913
+ return sum % 10 === 0;
7914
+ }
7915
+ function looksLikeIssuerCard(d) {
7916
+ if (/^4/.test(d) && (d.length === 13 || d.length === 16 || d.length === 19)) return true;
7917
+ if (/^5[1-5]/.test(d) && d.length === 16) return true;
7918
+ if (/^2(2[2-9]|[3-6]\d|7[01]|720)/.test(d) && d.length === 16) return true;
7919
+ if (/^3[47]/.test(d) && d.length === 15) return true;
7920
+ if (/^(6011|65|64[4-9])/.test(d) && d.length === 16) return true;
7921
+ return false;
7922
+ }
7923
+ var TEST_CARD_NUMBERS = /* @__PURE__ */ new Set([
7924
+ "4242424242424242",
7925
+ "4111111111111111",
7926
+ "4012888888881881",
7927
+ "4000056655665556",
7928
+ "4000000000000002",
7929
+ "4000000000009995",
7930
+ "5555555555554444",
7931
+ "5200828282828210",
7932
+ "5105105105105100",
7933
+ "2223003122003222",
7934
+ "378282246310005",
7935
+ "371449635398431",
7936
+ "6011111111111117",
7937
+ "6011000990139424",
7938
+ "3056930009020004",
7939
+ "38520000023237"
7940
+ ]);
7941
+ var hardcodedCreditCard = {
7942
+ id: "VC211",
7943
+ title: "Hardcoded credit card number",
7944
+ severity: "high",
7945
+ category: "Information Leakage",
7946
+ description: "A literal credit card number (Luhn-valid, with a real issuer prefix, and not a known network test card) appears in source. Cardholder data does not belong in code \u2014 it lands in git history, logs, and backups, and is a PCI-DSS violation. Use your processor's published test cards for tests, and tokenize real cards.",
7947
+ check(content, filePath) {
7948
+ if (isTestFile(filePath)) return [];
7949
+ const findings = [];
7950
+ const pattern = /(?<![\d.])\d[\d -]{11,21}\d(?![\d.])/g;
7951
+ let m;
7952
+ while ((m = pattern.exec(content)) !== null) {
7953
+ const digits = m[0].replace(/[ -]/g, "");
7954
+ if (digits.length < 13 || digits.length > 19) continue;
7955
+ if (TEST_CARD_NUMBERS.has(digits)) continue;
7956
+ if (!looksLikeIssuerCard(digits)) continue;
7957
+ if (!passesLuhn(digits)) continue;
7958
+ if (isCommentLine(content, m.index)) continue;
7959
+ const lineNum = lineNumberAt(content, m.index);
7960
+ findings.push({
7961
+ rule: "VC211",
7962
+ title: hardcodedCreditCard.title,
7963
+ severity: "high",
7964
+ category: "Information Leakage",
7965
+ file: filePath,
7966
+ line: lineNum,
7967
+ snippet: getSnippet(content, lineNum),
7968
+ fix: "Never store a real card number in source. Tokenize through your payment processor (Stripe, Braintree) and keep only the returned token. For tests, use the processor's published test cards (e.g. Stripe's 4242 4242 4242 4242)."
7969
+ });
7970
+ }
7971
+ return findings;
7972
+ }
7973
+ };
7974
+ var hardcodedSSN = {
7975
+ id: "VC212",
7976
+ title: "Hardcoded US Social Security Number",
7977
+ severity: "high",
7978
+ category: "Information Leakage",
7979
+ description: "A value in SSN format (NNN-NN-NNNN) is assigned to an SSN-named field in source. Social Security Numbers are regulated PII; a literal one leaks into git history, logs, and backups. Store SSNs encrypted at rest and load test data from generated fakes, never a code literal.",
7980
+ check(content, filePath) {
7981
+ if (isTestFile(filePath)) return [];
7982
+ const findings = [];
7983
+ const pattern = /(?:ssn|social[_-]?sec(?:urity)?(?:[_-]?(?:no|num|number))?|taxpayer[_-]?id)\b["']?\s*[:=]\s*["']?(\d{3}-\d{2}-\d{4})/gi;
7984
+ let m;
7985
+ while ((m = pattern.exec(content)) !== null) {
7986
+ const ssn = m[1];
7987
+ const [area, group, serial] = ssn.split("-");
7988
+ if (area === "000" || area === "666" || Number(area) >= 900) continue;
7989
+ if (group === "00" || serial === "0000") continue;
7990
+ if (ssn === "123-45-6789") continue;
7991
+ if (isCommentLine(content, m.index)) continue;
7992
+ const lineNum = lineNumberAt(content, m.index);
7993
+ findings.push({
7994
+ rule: "VC212",
7995
+ title: hardcodedSSN.title,
7996
+ severity: "high",
7997
+ category: "Information Leakage",
7998
+ file: filePath,
7999
+ line: lineNum,
8000
+ snippet: getSnippet(content, lineNum),
8001
+ fix: "Don't hardcode SSNs. Generate fake values for tests (a faker/factory), and store real SSNs with application-level encryption or in a secrets vault \u2014 never as a code literal."
8002
+ });
8003
+ }
8004
+ return findings;
8005
+ }
8006
+ };
7891
8007
  var secretInURLParam = {
7892
8008
  id: "VC146",
7893
8009
  title: "Secret Passed in URL Query Parameter",
@@ -8616,7 +8732,10 @@ var allCustomRules = [
8616
8732
  // VC204–VC206: GraphQL server hardening
8617
8733
  graphqlNoDepthLimit,
8618
8734
  graphqlNoComplexityLimit,
8619
- graphqlCSRFDisabled
8735
+ graphqlCSRFDisabled,
8736
+ // VC211–VC212: hardcoded sensitive personal data (PII)
8737
+ hardcodedCreditCard,
8738
+ hardcodedSSN
8620
8739
  ];
8621
8740
  function runCustomRules(content, filePath, disabledRules = [], tier = "free", extraRules = []) {
8622
8741
  const findings = [];
@@ -9045,6 +9164,7 @@ function scanEntropy(files) {
9045
9164
  hardcodedAnthropicKey,
9046
9165
  hardcodedCloudflareToken,
9047
9166
  hardcodedCohereKey,
9167
+ hardcodedCreditCard,
9048
9168
  hardcodedDatadogKey,
9049
9169
  hardcodedDiscordToken,
9050
9170
  hardcodedEncryptionKey,
@@ -9074,6 +9194,7 @@ function scanEntropy(files) {
9074
9194
  hardcodedRailwayToken,
9075
9195
  hardcodedReplicateKey,
9076
9196
  hardcodedResendKey,
9197
+ hardcodedSSN,
9077
9198
  hardcodedSecrets,
9078
9199
  hardcodedSendGridKey,
9079
9200
  hardcodedSentryAuthToken,