Rename API_KEY_OPENAI to API_KEY_SK with expanded pattern (#61)
authorStefan Gasser <redacted>
Mon, 26 Jan 2026 08:18:03 +0000 (09:18 +0100)
committerGitHub <redacted>
Mon, 26 Jan 2026 08:18:03 +0000 (09:18 +0100)
- Rename API_KEY_OPENAI to API_KEY_SK for broader coverage
- Expand pattern from /sk-.../ to /sk[-_].../ to match both hyphen and underscore
- Reduce minimum length from 45 to 20 chars for Stripe compatibility
- Now detects: OpenAI, Anthropic, Stripe, RevenueCat, and similar sk-prefixed keys
- Add tests for Stripe (sk_test_, sk_live_), Anthropic (sk-ant-), RevenueCat (sk_)
- Update all documentation and config examples

README.md
config.example.yaml
docs/concepts/secrets-detection.mdx
docs/configuration/secrets-detection.mdx
src/config.ts
src/masking/placeholders.test.ts
src/masking/placeholders.ts
src/secrets/detect.test.ts
src/secrets/mask.test.ts
src/secrets/patterns/api-keys.ts
src/secrets/patterns/types.ts

index 7f1abebf92a59f19342aa97e6c7b49d2960a3b6c..bcfd14f8819beb28585d3100cb41993785ba4524 100644 (file)
--- a/README.md
+++ b/README.md
@@ -116,7 +116,7 @@ Works with OpenAI, Anthropic, and compatible tools:
 **Secrets**
 - OpenSSH private keys
 - PEM private keys
-- OpenAI API keys
+- API keys with sk- or sk_ prefix (OpenAI, Anthropic, Stripe, etc.)
 - AWS access keys
 - GitHub tokens
 - JWT tokens
index 319d0596e28e070f7d14b4e219ab3091c920ad27..3c20aaf3dfca1cb44078529f654b18fe04a25b76 100644 (file)
@@ -117,7 +117,7 @@ secrets_detection:
   #   - PEM_PRIVATE_KEY:     PEM formats (RSA, PRIVATE KEY, ENCRYPTED PRIVATE KEY)
   #
   # API Keys (opt-in):
-  #   - API_KEY_OPENAI:      OpenAI API keys (sk-...)
+  #   - API_KEY_SK:          Secret keys with sk- or sk_ prefix (OpenAI, Anthropic, Stripe, RevenueCat)
   #   - API_KEY_AWS:         AWS Access Keys (AKIA...)
   #   - API_KEY_GITHUB:      GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
   #
@@ -133,7 +133,7 @@ secrets_detection:
     - OPENSSH_PRIVATE_KEY
     - PEM_PRIVATE_KEY
     # Uncomment to detect API keys and tokens:
-    # - API_KEY_OPENAI
+    # - API_KEY_SK
     # - API_KEY_AWS
     # - API_KEY_GITHUB
     # - JWT_TOKEN
index f96977383ac41fd92feda0bae476d73555517077..9752c6a1d28f795f4036093fbfae56a63c2403fb 100644 (file)
@@ -20,7 +20,7 @@ PasteGuard detects secrets before PII detection and can block, mask, or route re
 
 | Type | Pattern |
 |------|---------|
-| `API_KEY_OPENAI` | `sk-...` (48+ chars) |
+| `API_KEY_SK` | `sk-...` or `sk_...` (20+ chars) - OpenAI, Anthropic, Stripe, RevenueCat |
 | `API_KEY_AWS` | `AKIA...` (20 chars) |
 | `API_KEY_GITHUB` | `ghp_...`, `gho_...`, `ghu_...`, `ghs_...`, `ghr_...` (40+ chars) |
 
@@ -82,7 +82,7 @@ When secrets are detected:
 
 ```
 X-PasteGuard-Secrets-Detected: true
-X-PasteGuard-Secrets-Types: OPENSSH_PRIVATE_KEY,API_KEY_OPENAI
+X-PasteGuard-Secrets-Types: OPENSSH_PRIVATE_KEY,API_KEY_SK
 ```
 
 If secrets were masked:
index d1172284efc21036bc113b63ed08886787d24767..2705e6bd4743e7089f745bcaa0fa609b0c460dc1 100644 (file)
@@ -72,7 +72,7 @@ secrets_detection:
 ```yaml
 secrets_detection:
   entities:
-    - API_KEY_OPENAI   # sk-... (48+ chars)
+    - API_KEY_SK       # sk-/sk_ prefix (OpenAI, Anthropic, Stripe, RevenueCat)
     - API_KEY_AWS      # AKIA... (20 chars)
     - API_KEY_GITHUB   # ghp_, gho_, ghu_, ghs_, ghr_ (40+ chars)
 ```
index 324b5c084dd42e175123bcd7bd6e883f68183ce3..00e24ab89155a20718ce6cbfe16e337c49d848bb 100644 (file)
@@ -95,7 +95,7 @@ const DashboardSchema = z.object({
 const SecretEntityTypes = [
   "OPENSSH_PRIVATE_KEY",
   "PEM_PRIVATE_KEY",
-  "API_KEY_OPENAI",
+  "API_KEY_SK",
   "API_KEY_AWS",
   "API_KEY_GITHUB",
   "JWT_TOKEN",
index d46dee2cf49a2b63ff50042fb28b532775c3e9e4..d60a0847289a817647622e80db8cae3c259d5a22 100644 (file)
@@ -41,8 +41,8 @@ describe("generatePlaceholder", () => {
 
 describe("generateSecretPlaceholder", () => {
   test("generates secret placeholder", () => {
-    const result = generateSecretPlaceholder("API_KEY_OPENAI", 1);
-    expect(result).toBe("[[API_KEY_OPENAI_1]]");
+    const result = generateSecretPlaceholder("API_KEY_SK", 1);
+    expect(result).toBe("[[API_KEY_SK_1]]");
   });
 
   test("generates secret placeholder with different type and count", () => {
index 66f38b233e24b5fb44b08968f5c948fe3726d63f..5669436328c7336c5096a7bc0b98a96c93c8f932 100644 (file)
@@ -10,7 +10,7 @@ export const PLACEHOLDER_DELIMITERS = {
 /** PII placeholder format: [[TYPE_N]] e.g. [[PERSON_1]], [[EMAIL_ADDRESS_2]] */
 export const PII_PLACEHOLDER_FORMAT = "[[{TYPE}_{N}]]";
 
-/** Secrets placeholder format: [[TYPE_N]] e.g. [[API_KEY_OPENAI_1]] */
+/** Secrets placeholder format: [[TYPE_N]] e.g. [[API_KEY_SK_1]] */
 export const SECRET_PLACEHOLDER_FORMAT = "[[{N}]]";
 
 /**
@@ -22,7 +22,7 @@ export function generatePlaceholder(format: string, type: string, count: number)
 
 /**
  * Generates a secret placeholder string
- * {N} is replaced with TYPE_COUNT e.g. API_KEY_OPENAI_1
+ * {N} is replaced with TYPE_COUNT e.g. API_KEY_SK_1
  */
 export function generateSecretPlaceholder(type: string, count: number): string {
   return SECRET_PLACEHOLDER_FORMAT.replace("{N}", `${type}_${count}`);
index 0337d83853759036e7f734a88780b417305d4aef..bfb68a7730b3a8ffbb60fec75ef43e30a9a3f83c 100644 (file)
@@ -180,8 +180,13 @@ describe("detectSecrets", () => {
   });
 });
 
-// Test data for new secret types
+// Test data for secret types
 const openaiApiKey = "sk-proj-abc123def456ghi789jkl012mno345pqr678stu901vwx";
+const anthropicApiKey =
+  "sk-ant-api03-abc123def456ghi789jkl012mno345pqr678stu901vwxyz0123456789abcdefghijklmnopqrstuvwxyz";
+const stripeTestKey = "sk_test_abc123def456ghi789jkl012";
+const stripeLiveKey = "sk_live_xyz789abc123def456ghi789";
+const revenueCatKey = "sk_abcdefghijklmnopqrstuvwx";
 const awsAccessKey = "AKIAIOSFODNN7EXAMPLE";
 const githubToken = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1234";
 const githubOAuthToken = "gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5678";
@@ -192,18 +197,50 @@ const bearerToken = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9abcdefghijk";
 describe("detectSecrets - API Keys", () => {
   const apiKeyConfig: SecretsDetectionConfig = {
     ...defaultConfig,
-    entities: ["API_KEY_OPENAI", "API_KEY_AWS", "API_KEY_GITHUB"],
+    entities: ["API_KEY_SK", "API_KEY_AWS", "API_KEY_GITHUB"],
   };
 
-  test("detects OpenAI API key", () => {
+  test("detects OpenAI API key (sk-proj-...)", () => {
     const text = `My API key is ${openaiApiKey}`;
     const result = detectSecrets(text, apiKeyConfig);
     expect(result.detected).toBe(true);
     expect(result.matches).toHaveLength(1);
-    expect(result.matches[0].type).toBe("API_KEY_OPENAI");
+    expect(result.matches[0].type).toBe("API_KEY_SK");
     expect(result.matches[0].count).toBe(1);
     expect(result.locations).toBeDefined();
-    expect(result.locations?.[0].type).toBe("API_KEY_OPENAI");
+    expect(result.locations?.[0].type).toBe("API_KEY_SK");
+  });
+
+  test("detects Anthropic API key (sk-ant-...)", () => {
+    const text = `Anthropic key: ${anthropicApiKey}`;
+    const result = detectSecrets(text, apiKeyConfig);
+    expect(result.detected).toBe(true);
+    expect(result.matches).toHaveLength(1);
+    expect(result.matches[0].type).toBe("API_KEY_SK");
+  });
+
+  test("detects Stripe test key (sk_test_...)", () => {
+    const text = `STRIPE_SECRET_KEY=${stripeTestKey}`;
+    const result = detectSecrets(text, apiKeyConfig);
+    expect(result.detected).toBe(true);
+    expect(result.matches).toHaveLength(1);
+    expect(result.matches[0].type).toBe("API_KEY_SK");
+  });
+
+  test("detects Stripe live key (sk_live_...)", () => {
+    const text = `export STRIPE_KEY="${stripeLiveKey}"`;
+    const result = detectSecrets(text, apiKeyConfig);
+    expect(result.detected).toBe(true);
+    expect(result.matches).toHaveLength(1);
+    expect(result.matches[0].type).toBe("API_KEY_SK");
+  });
+
+  test("detects RevenueCat key (sk_...)", () => {
+    const text = `revenuecat_api_key: ${revenueCatKey}`;
+    const result = detectSecrets(text, apiKeyConfig);
+    expect(result.detected).toBe(true);
+    expect(result.matches).toHaveLength(1);
+    expect(result.matches[0].type).toBe("API_KEY_SK");
   });
 
   test("detects AWS access key", () => {
@@ -236,7 +273,7 @@ describe("detectSecrets - API Keys", () => {
     const result = detectSecrets(text, apiKeyConfig);
     expect(result.detected).toBe(true);
     expect(result.matches).toHaveLength(3);
-    expect(result.matches.find((m) => m.type === "API_KEY_OPENAI")).toBeDefined();
+    expect(result.matches.find((m) => m.type === "API_KEY_SK")).toBeDefined();
     expect(result.matches.find((m) => m.type === "API_KEY_AWS")).toBeDefined();
     expect(result.matches.find((m) => m.type === "API_KEY_GITHUB")).toBeDefined();
   });
@@ -247,6 +284,12 @@ describe("detectSecrets - API Keys", () => {
     expect(result.detected).toBe(false);
   });
 
+  test("avoids false positive - sk_ prefix but too short", () => {
+    const text = "This sk_short is not valid";
+    const result = detectSecrets(text, apiKeyConfig);
+    expect(result.detected).toBe(false);
+  });
+
   test("avoids false positive - AKIA prefix but wrong length", () => {
     const text = "AKIA12345 is not valid";
     const result = detectSecrets(text, apiKeyConfig);
@@ -611,7 +654,7 @@ describe("detectSecrets - Mixed secret types", () => {
     entities: [
       "OPENSSH_PRIVATE_KEY",
       "PEM_PRIVATE_KEY",
-      "API_KEY_OPENAI",
+      "API_KEY_SK",
       "API_KEY_AWS",
       "API_KEY_GITHUB",
       "JWT_TOKEN",
index 3bd85646627e484ad027001771683ef4b35da330..dcfce6219f2c5088d0dc84d07ee8dd7c06d71427 100644 (file)
@@ -14,6 +14,7 @@ import {
 } from "./mask";
 
 const sampleSecret = "sk-proj-abc123def456ghi789jkl012mno345pqr678stu901vwx";
+const stripeSecret = "sk_live_abc123def456ghi789jkl012";
 
 /** Helper to create a minimal request from messages */
 function createRequest(messages: OpenAIMessage[]): OpenAIRequest {
@@ -24,35 +25,35 @@ describe("secrets placeholder format", () => {
   test("uses [[TYPE_N]] format", () => {
     const text = `My API key is ${sampleSecret}`;
     const locations: SecretLocation[] = [
-      { start: 14, end: 14 + sampleSecret.length, type: "API_KEY_OPENAI" },
+      { start: 14, end: 14 + sampleSecret.length, type: "API_KEY_SK" },
     ];
     const result = maskSecrets(text, locations);
 
-    expect(result.masked).toBe("My API key is [[API_KEY_OPENAI_1]]");
+    expect(result.masked).toBe("My API key is [[API_KEY_SK_1]]");
   });
 
   test("increments counter per secret type", () => {
     const anotherSecret = "sk-proj-xyz789abc123def456ghi789jkl012mno345pqr678";
     const text = `Key1: ${sampleSecret} Key2: ${anotherSecret}`;
     const locations: SecretLocation[] = [
-      { start: 6, end: 6 + sampleSecret.length, type: "API_KEY_OPENAI" },
+      { start: 6, end: 6 + sampleSecret.length, type: "API_KEY_SK" },
       {
         start: 6 + sampleSecret.length + 7,
         end: 6 + sampleSecret.length + 7 + anotherSecret.length,
-        type: "API_KEY_OPENAI",
+        type: "API_KEY_SK",
       },
     ];
     const result = maskSecrets(text, locations);
 
-    expect(result.masked).toContain("[[API_KEY_OPENAI_1]]");
-    expect(result.masked).toContain("[[API_KEY_OPENAI_2]]");
+    expect(result.masked).toContain("[[API_KEY_SK_1]]");
+    expect(result.masked).toContain("[[API_KEY_SK_2]]");
   });
 
   test("tracks different secret types separately", () => {
     const awsKey = "AKIAIOSFODNN7EXAMPLE";
     const text = `OpenAI: ${sampleSecret} AWS: ${awsKey}`;
     const locations: SecretLocation[] = [
-      { start: 8, end: 8 + sampleSecret.length, type: "API_KEY_OPENAI" },
+      { start: 8, end: 8 + sampleSecret.length, type: "API_KEY_SK" },
       {
         start: 8 + sampleSecret.length + 6,
         end: 8 + sampleSecret.length + 6 + awsKey.length,
@@ -61,9 +62,20 @@ describe("secrets placeholder format", () => {
     ];
     const result = maskSecrets(text, locations);
 
-    expect(result.masked).toContain("[[API_KEY_OPENAI_1]]");
+    expect(result.masked).toContain("[[API_KEY_SK_1]]");
     expect(result.masked).toContain("[[API_KEY_AWS_1]]");
   });
+
+  test("masks sk_ prefix keys (Stripe)", () => {
+    const text = `Stripe key: ${stripeSecret}`;
+    const locations: SecretLocation[] = [
+      { start: 12, end: 12 + stripeSecret.length, type: "API_KEY_SK" },
+    ];
+    const result = maskSecrets(text, locations);
+
+    expect(result.masked).toBe("Stripe key: [[API_KEY_SK_1]]");
+    expect(result.context.mapping["[[API_KEY_SK_1]]"]).toBe(stripeSecret);
+  });
 });
 
 describe("maskRequest with MessageSecretsResult", () => {
@@ -74,13 +86,13 @@ describe("maskRequest with MessageSecretsResult", () => {
     ]);
     // spanLocations[0] = first message (user), spanLocations[1] = second message (assistant)
     const detection = createSecretsResultFromSpans([
-      [{ start: 10, end: 10 + sampleSecret.length, type: "API_KEY_OPENAI" }],
+      [{ start: 10, end: 10 + sampleSecret.length, type: "API_KEY_SK" }],
       [],
     ]);
 
     const { masked, context } = maskRequest(request, detection, openaiExtractor);
 
-    expect(masked.messages[0].content).toContain("[[API_KEY_OPENAI_1]]");
+    expect(masked.messages[0].content).toContain("[[API_KEY_SK_1]]");
     expect(masked.messages[0].content).not.toContain(sampleSecret);
     expect(masked.messages[1].content).toBe("I'll help you with that.");
     expect(Object.keys(context.mapping)).toHaveLength(1);
@@ -92,14 +104,14 @@ describe("maskRequest with MessageSecretsResult", () => {
       { role: "user", content: `Key2: ${sampleSecret}` },
     ]);
     const detection = createSecretsResultFromSpans([
-      [{ start: 6, end: 6 + sampleSecret.length, type: "API_KEY_OPENAI" }],
-      [{ start: 6, end: 6 + sampleSecret.length, type: "API_KEY_OPENAI" }],
+      [{ start: 6, end: 6 + sampleSecret.length, type: "API_KEY_SK" }],
+      [{ start: 6, end: 6 + sampleSecret.length, type: "API_KEY_SK" }],
     ]);
 
     const { masked, context } = maskRequest(request, detection, openaiExtractor);
 
-    expect(masked.messages[0].content).toBe("Key1: [[API_KEY_OPENAI_1]]");
-    expect(masked.messages[1].content).toBe("Key2: [[API_KEY_OPENAI_1]]");
+    expect(masked.messages[0].content).toBe("Key1: [[API_KEY_SK_1]]");
+    expect(masked.messages[1].content).toBe("Key2: [[API_KEY_SK_1]]");
     expect(Object.keys(context.mapping)).toHaveLength(1);
   });
 
@@ -115,13 +127,13 @@ describe("maskRequest with MessageSecretsResult", () => {
     ]);
     // Two spans: text content at index 0, image is skipped
     const detection = createSecretsResultFromSpans([
-      [{ start: 5, end: 5 + sampleSecret.length, type: "API_KEY_OPENAI" }],
+      [{ start: 5, end: 5 + sampleSecret.length, type: "API_KEY_SK" }],
     ]);
 
     const { masked } = maskRequest(request, detection, openaiExtractor);
 
     const content = masked.messages[0].content as Array<{ type: string; text?: string }>;
-    expect(content[0].text).toBe("Key: [[API_KEY_OPENAI_1]]");
+    expect(content[0].text).toBe("Key: [[API_KEY_SK_1]]");
     expect(content[1].type).toBe("image_url");
   });
 });
@@ -129,7 +141,7 @@ describe("maskRequest with MessageSecretsResult", () => {
 describe("streaming with secrets placeholders", () => {
   test("buffers partial [[ placeholder", () => {
     const context = createSecretsMaskingContext();
-    context.mapping["[[API_KEY_OPENAI_1]]"] = sampleSecret;
+    context.mapping["[[API_KEY_SK_1]]"] = sampleSecret;
 
     const { output, remainingBuffer } = unmaskSecretsStreamChunk("", "Key: [[API_KEY", context);
 
@@ -139,11 +151,11 @@ describe("streaming with secrets placeholders", () => {
 
   test("completes buffered placeholder across chunks", () => {
     const context = createSecretsMaskingContext();
-    context.mapping["[[API_KEY_OPENAI_1]]"] = sampleSecret;
+    context.mapping["[[API_KEY_SK_1]]"] = sampleSecret;
 
     const { output, remainingBuffer } = unmaskSecretsStreamChunk(
       "[[API_KEY",
-      "_OPENAI_1]] done",
+      "_SK_1]] done",
       context,
     );
 
@@ -169,14 +181,14 @@ Please store them securely.
       {
         start: originalText.indexOf(sampleSecret),
         end: originalText.indexOf(sampleSecret) + sampleSecret.length,
-        type: "API_KEY_OPENAI",
+        type: "API_KEY_SK",
       },
     ];
 
     const { masked, context } = maskSecrets(originalText, locations);
 
     expect(masked).not.toContain(sampleSecret);
-    expect(masked).toContain("[[API_KEY_OPENAI_1]]");
+    expect(masked).toContain("[[API_KEY_SK_1]]");
 
     const restored = unmaskSecrets(masked, context);
     expect(restored).toBe(originalText);
@@ -186,7 +198,7 @@ Please store them securely.
 describe("unmaskSecretsResponse", () => {
   test("unmasks all choices in response", () => {
     const context = createSecretsMaskingContext();
-    context.mapping["[[API_KEY_OPENAI_1]]"] = sampleSecret;
+    context.mapping["[[API_KEY_SK_1]]"] = sampleSecret;
 
     const response: OpenAIResponse = {
       id: "test",
@@ -198,7 +210,7 @@ describe("unmaskSecretsResponse", () => {
           index: 0,
           message: {
             role: "assistant",
-            content: "Your key is [[API_KEY_OPENAI_1]]",
+            content: "Your key is [[API_KEY_SK_1]]",
           },
           finish_reason: "stop",
         },
@@ -244,16 +256,16 @@ describe("edge cases", () => {
   test("reuses placeholder for duplicate secret values", () => {
     const text = `Key1: ${sampleSecret} Key2: ${sampleSecret}`;
     const locations: SecretLocation[] = [
-      { start: 6, end: 6 + sampleSecret.length, type: "API_KEY_OPENAI" },
+      { start: 6, end: 6 + sampleSecret.length, type: "API_KEY_SK" },
       {
         start: 6 + sampleSecret.length + 7,
         end: 6 + sampleSecret.length * 2 + 7,
-        type: "API_KEY_OPENAI",
+        type: "API_KEY_SK",
       },
     ];
     const result = maskSecrets(text, locations);
 
-    expect(result.masked).toBe("Key1: [[API_KEY_OPENAI_1]] Key2: [[API_KEY_OPENAI_1]]");
+    expect(result.masked).toBe("Key1: [[API_KEY_SK_1]] Key2: [[API_KEY_SK_1]]");
     expect(Object.keys(result.context.mapping)).toHaveLength(1);
   });
 
@@ -262,18 +274,18 @@ describe("edge cases", () => {
 
     maskSecrets(
       `Key: ${sampleSecret}`,
-      [{ start: 5, end: 5 + sampleSecret.length, type: "API_KEY_OPENAI" }],
+      [{ start: 5, end: 5 + sampleSecret.length, type: "API_KEY_SK" }],
       context,
     );
 
     const anotherSecret = "sk-proj-xyz789abc123def456ghi789jkl012mno345pqr678";
     const result2 = maskSecrets(
       `Another: ${anotherSecret}`,
-      [{ start: 9, end: 9 + anotherSecret.length, type: "API_KEY_OPENAI" }],
+      [{ start: 9, end: 9 + anotherSecret.length, type: "API_KEY_SK" }],
       context,
     );
 
-    expect(result2.masked).toBe("Another: [[API_KEY_OPENAI_2]]");
+    expect(result2.masked).toBe("Another: [[API_KEY_SK_2]]");
     expect(Object.keys(context.mapping)).toHaveLength(2);
   });
 });
index 57e33e81d15d5ab682de9b56ee74e0fa5e875de2..b7fda2930b03e34d91ac2f19a6e4af76c75d8352 100644 (file)
@@ -5,22 +5,25 @@ import { detectPattern } from "./utils";
  * API keys detector
  *
  * Detects:
- * - API_KEY_OPENAI: OpenAI API keys (sk-...)
+ * - API_KEY_SK: Secret keys with sk- or sk_ prefix (OpenAI, Anthropic, Stripe, RevenueCat)
  * - API_KEY_AWS: AWS Access Keys (AKIA...)
  * - API_KEY_GITHUB: GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
  */
 export const apiKeysDetector: PatternDetector = {
-  patterns: ["API_KEY_OPENAI", "API_KEY_AWS", "API_KEY_GITHUB"],
+  patterns: ["API_KEY_SK", "API_KEY_AWS", "API_KEY_GITHUB"],
 
   detect(text: string, enabledTypes: Set<string>) {
     const matches: SecretsMatch[] = [];
     const locations: SecretLocation[] = [];
 
-    // OpenAI API keys: sk-... followed by alphanumeric chars
-    // Modern format: sk-proj-... or sk-... with 48+ total chars
-    if (enabledTypes.has("API_KEY_OPENAI")) {
-      const openaiPattern = /sk-[a-zA-Z0-9_-]{45,}/g;
-      detectPattern(text, openaiPattern, "API_KEY_OPENAI", matches, locations);
+    // Secret keys with sk- or sk_ prefix:
+    // - OpenAI: sk-proj-... (48+ chars)
+    // - Anthropic: sk-ant-api03-... (~100 chars)
+    // - Stripe: sk_test_..., sk_live_... (24-32 chars after prefix)
+    // - RevenueCat, Moyasar: sk_... (various lengths)
+    if (enabledTypes.has("API_KEY_SK")) {
+      const skPattern = /sk[-_][a-zA-Z0-9_-]{20,}/g;
+      detectPattern(text, skPattern, "API_KEY_SK", matches, locations);
     }
 
     // AWS access keys: AKIA followed by 16 uppercase alphanumeric chars
index 500016aa98feb1f822d484a72adc8d850630bee6..6a1563e07f575aac51fd578b65de03f1564325dd 100644 (file)
@@ -4,7 +4,7 @@
 export type SecretEntityType =
   | "OPENSSH_PRIVATE_KEY"
   | "PEM_PRIVATE_KEY"
-  | "API_KEY_OPENAI"
+  | "API_KEY_SK"
   | "API_KEY_AWS"
   | "API_KEY_GITHUB"
   | "JWT_TOKEN"
git clone https://git.99rst.org/PROJECT