xploitscan-shared-rules 1.0.0 → 1.1.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.d.cts CHANGED
@@ -246,4 +246,47 @@ interface AIFilterResult {
246
246
  */
247
247
  declare function filterFalsePositives(findings: Finding[], fileContents: Map<string, string>): Promise<AIFilterResult>;
248
248
 
249
- export { type AIFilterResult, type Confidence, type CustomRule, type DetectedFramework, type FilteredFinding, type Finding, type GradeResult, type RuleMatch, type SecurityGrade, type Severity, allCustomRules, allRules, androidDebuggable, blockingMainThread, calculateGrade, callbackHell, clickjacking, clientComponentSecret, clientSideAuth, commandInjection, complianceMap, consoleLogProduction, corsLocalhost, corsServerless, corsWildcard, dangerousInnerHTML, deprecatedTLS, detectFramework, disabledTLSVerification, djangoDebug, dockerCopySensitive, dockerLatestTag, dockerRunAsRoot, dockerTooManyPorts, ecbModeEncryption, electronNavigationUnrestricted, emptyCatchBlock, envNotGitignored, evalUsage, eventListenerLeak, exposedAdminRoutes, exposedAuthSecret, exposedDBCredentials, exposedDatabaseStudio, exposedDebugMode, exposedDockerPorts, exposedEnvFile, exposedGitDir, exposedServerActions, exposedSourceMaps, exposedStackTraces, filterFalsePositives, firebaseClientConfig, flaskSecretKey, freeRules, getSnippet, githubActionsInjection, graphqlIntrospection, hardcodedAnthropicKey, hardcodedDatadogKey, hardcodedEncryptionKey, hardcodedGCPServiceAccount, hardcodedGitHubPAT, hardcodedGitLabToken, hardcodedIPAllowlist, hardcodedJWTSecret, hardcodedMailgunKey, hardcodedOAuthSecret, hardcodedPineconeKey, hardcodedSecrets, hardcodedSendGridKey, hardcodedShopifyToken, hardcodedSlackToken, hardcodedSupabaseServiceRole, hardcodedTwilioKey, hardcodedVaultToken, hardcodedVercelToken, hostHeaderRedirect, httpRequestSmuggling, insecureCookies, insecureDeepLink, insecureDeserialization, insecureDirectObjectReference, insecureElectronWindow, insecureFileUpload, insecureGRPC, insecureHTTPMethods, insecurePasswordReset, insecureRandomness, insecureWebSocket, ipcPathTraversal, javaDeserialization, jwtAlgConfusion, k8sNoResourceLimits, k8sPrivileged, k8sSecretNotEncrypted, lambdaWithoutVPC, largeBundleImport, logInjection, magicNumbers, massAssignment, missingAIRateLimit, missingAuthMiddleware, missingAuthRateLimit, missingBruteForce, missingCSP, missingCSRF, missingCertPinning, missingCloudTrail, missingContentDisposition, missingDBEncryption, missingErrorBoundary, missingFileSizeLimits, missingHSTS, missingHTTPS, missingLockFile, missingOAuthState, missingPagination, missingRequestSizeLimit, missingRequestValidation, missingSRI, missingSecurityMeta, nPlusOneQuery, nextPublicSecret, noRateLimiting, nosqlInjection, openRedirectParams, overlyPermissiveIAM, pathTraversal, pickleDeserialization, piiInLogs, prototypePollution, raceCondition, rdsPubliclyAccessible, reflectedCORSOrigin, regexDos, runCustomRules, s3BucketNoEncryption, secretInBundleConfig, secretInCLIArgument, secretInErrorResponse, secretInHTMLAttribute, secretInURLParam, secretLoggedToConsole, secretsInCI, securityGroupAllInbound, sensitiveAsyncStorage, sensitiveLocalStorage, sensitiveURLParams, sessionFixation, sqlInjection, ssrfVulnerability, ssti, stripeWebhookUnprotected, supabaseAnonAdmin, supabaseNoRLS, syncFileOps, terraformStateExposed, timingAttack, todoLeftInCode, unencryptedPII, unpinnedGitHubAction, unprotectedAPIRoutes, unprotectedDownload, unsafeObjectAssign, unsanitizedFilenames, unsanitizedHTMLExport, unvalidatedAPIParams, unvalidatedEventData, unvalidatedRedirect, vulnerableDependencies, weakHashing, weakPasswordRequirements, weakRSAKeySize, webhookSignatureVerification, xssVulnerability, xxeVulnerability };
249
+ /**
250
+ * Shannon-entropy based secret detection.
251
+ *
252
+ * Catches high-entropy string literals that look like API keys / tokens /
253
+ * credentials even when they don't match any of the service-specific
254
+ * hardcoded-secret rules (VC001, VC132, etc). Fires a single `ENTROPY`
255
+ * finding per suspicious literal.
256
+ *
257
+ * The trick to keeping this useful is keeping false positives down. Real
258
+ * codebases are full of high-entropy strings that aren't secrets — SHA-256
259
+ * hashes, UUIDs, base64 integrity hashes, Tailwind-generated class names,
260
+ * SVG path data, Next.js content-addressed filenames, publishable
261
+ * (intentionally-public) keys from Clerk/Stripe/etc. This scanner has three
262
+ * layers of suppression before emitting a finding:
263
+ *
264
+ * 1. File-level: skip lockfiles, CSS/SVG/image/map files, node_modules,
265
+ * minified/bundled output.
266
+ * 2. Shape-level: if the string matches a known-safe shape (UUID, Git SHA,
267
+ * integrity hash, publishable key prefix, Tailwind fingerprint, SVG
268
+ * path data, Next.js content hash), skip.
269
+ * 3. Context-level: inspect the assignment context — if the variable name
270
+ * implies "this holds a hash/digest/fingerprint" (not a secret), skip.
271
+ * If the variable name implies a secret (key/token/etc), lower the
272
+ * entropy bar. Otherwise require high entropy AND no safe signals.
273
+ *
274
+ * When in doubt we bias toward suppressing — one FP every 100 scans is much
275
+ * more costly to the product than one missed secret, because the expanded
276
+ * VC132-VC151 service-specific rules already cover the most common key
277
+ * formats.
278
+ */
279
+
280
+ /**
281
+ * Scan a batch of files for high-entropy string literals.
282
+ *
283
+ * Each element in `files` must be `{ path, content }`. Returns a list of
284
+ * `Finding` objects for suspicious string literals. See the file header for
285
+ * the suppression layers.
286
+ */
287
+ declare function scanEntropy(files: {
288
+ path: string;
289
+ content: string;
290
+ }[]): Finding[];
291
+
292
+ export { type AIFilterResult, type Confidence, type CustomRule, type DetectedFramework, type FilteredFinding, type Finding, type GradeResult, type RuleMatch, type SecurityGrade, type Severity, allCustomRules, allRules, androidDebuggable, blockingMainThread, calculateGrade, callbackHell, clickjacking, clientComponentSecret, clientSideAuth, commandInjection, complianceMap, consoleLogProduction, corsLocalhost, corsServerless, corsWildcard, dangerousInnerHTML, deprecatedTLS, detectFramework, disabledTLSVerification, djangoDebug, dockerCopySensitive, dockerLatestTag, dockerRunAsRoot, dockerTooManyPorts, ecbModeEncryption, electronNavigationUnrestricted, emptyCatchBlock, envNotGitignored, evalUsage, eventListenerLeak, exposedAdminRoutes, exposedAuthSecret, exposedDBCredentials, exposedDatabaseStudio, exposedDebugMode, exposedDockerPorts, exposedEnvFile, exposedGitDir, exposedServerActions, exposedSourceMaps, exposedStackTraces, filterFalsePositives, firebaseClientConfig, flaskSecretKey, freeRules, getSnippet, githubActionsInjection, graphqlIntrospection, hardcodedAnthropicKey, hardcodedDatadogKey, hardcodedEncryptionKey, hardcodedGCPServiceAccount, hardcodedGitHubPAT, hardcodedGitLabToken, hardcodedIPAllowlist, hardcodedJWTSecret, hardcodedMailgunKey, hardcodedOAuthSecret, hardcodedPineconeKey, hardcodedSecrets, hardcodedSendGridKey, hardcodedShopifyToken, hardcodedSlackToken, hardcodedSupabaseServiceRole, hardcodedTwilioKey, hardcodedVaultToken, hardcodedVercelToken, hostHeaderRedirect, httpRequestSmuggling, insecureCookies, insecureDeepLink, insecureDeserialization, insecureDirectObjectReference, insecureElectronWindow, insecureFileUpload, insecureGRPC, insecureHTTPMethods, insecurePasswordReset, insecureRandomness, insecureWebSocket, ipcPathTraversal, javaDeserialization, jwtAlgConfusion, k8sNoResourceLimits, k8sPrivileged, k8sSecretNotEncrypted, lambdaWithoutVPC, largeBundleImport, logInjection, magicNumbers, massAssignment, missingAIRateLimit, missingAuthMiddleware, missingAuthRateLimit, missingBruteForce, missingCSP, missingCSRF, missingCertPinning, missingCloudTrail, missingContentDisposition, missingDBEncryption, missingErrorBoundary, missingFileSizeLimits, missingHSTS, missingHTTPS, missingLockFile, missingOAuthState, missingPagination, missingRequestSizeLimit, missingRequestValidation, missingSRI, missingSecurityMeta, nPlusOneQuery, nextPublicSecret, noRateLimiting, nosqlInjection, openRedirectParams, overlyPermissiveIAM, pathTraversal, pickleDeserialization, piiInLogs, prototypePollution, raceCondition, rdsPubliclyAccessible, reflectedCORSOrigin, regexDos, runCustomRules, s3BucketNoEncryption, scanEntropy, secretInBundleConfig, secretInCLIArgument, secretInErrorResponse, secretInHTMLAttribute, secretInURLParam, secretLoggedToConsole, secretsInCI, securityGroupAllInbound, sensitiveAsyncStorage, sensitiveLocalStorage, sensitiveURLParams, sessionFixation, sqlInjection, ssrfVulnerability, ssti, stripeWebhookUnprotected, supabaseAnonAdmin, supabaseNoRLS, syncFileOps, terraformStateExposed, timingAttack, todoLeftInCode, unencryptedPII, unpinnedGitHubAction, unprotectedAPIRoutes, unprotectedDownload, unsafeObjectAssign, unsanitizedFilenames, unsanitizedHTMLExport, unvalidatedAPIParams, unvalidatedEventData, unvalidatedRedirect, vulnerableDependencies, weakHashing, weakPasswordRequirements, weakRSAKeySize, webhookSignatureVerification, xssVulnerability, xxeVulnerability };
package/dist/index.d.ts CHANGED
@@ -246,4 +246,47 @@ interface AIFilterResult {
246
246
  */
247
247
  declare function filterFalsePositives(findings: Finding[], fileContents: Map<string, string>): Promise<AIFilterResult>;
248
248
 
249
- export { type AIFilterResult, type Confidence, type CustomRule, type DetectedFramework, type FilteredFinding, type Finding, type GradeResult, type RuleMatch, type SecurityGrade, type Severity, allCustomRules, allRules, androidDebuggable, blockingMainThread, calculateGrade, callbackHell, clickjacking, clientComponentSecret, clientSideAuth, commandInjection, complianceMap, consoleLogProduction, corsLocalhost, corsServerless, corsWildcard, dangerousInnerHTML, deprecatedTLS, detectFramework, disabledTLSVerification, djangoDebug, dockerCopySensitive, dockerLatestTag, dockerRunAsRoot, dockerTooManyPorts, ecbModeEncryption, electronNavigationUnrestricted, emptyCatchBlock, envNotGitignored, evalUsage, eventListenerLeak, exposedAdminRoutes, exposedAuthSecret, exposedDBCredentials, exposedDatabaseStudio, exposedDebugMode, exposedDockerPorts, exposedEnvFile, exposedGitDir, exposedServerActions, exposedSourceMaps, exposedStackTraces, filterFalsePositives, firebaseClientConfig, flaskSecretKey, freeRules, getSnippet, githubActionsInjection, graphqlIntrospection, hardcodedAnthropicKey, hardcodedDatadogKey, hardcodedEncryptionKey, hardcodedGCPServiceAccount, hardcodedGitHubPAT, hardcodedGitLabToken, hardcodedIPAllowlist, hardcodedJWTSecret, hardcodedMailgunKey, hardcodedOAuthSecret, hardcodedPineconeKey, hardcodedSecrets, hardcodedSendGridKey, hardcodedShopifyToken, hardcodedSlackToken, hardcodedSupabaseServiceRole, hardcodedTwilioKey, hardcodedVaultToken, hardcodedVercelToken, hostHeaderRedirect, httpRequestSmuggling, insecureCookies, insecureDeepLink, insecureDeserialization, insecureDirectObjectReference, insecureElectronWindow, insecureFileUpload, insecureGRPC, insecureHTTPMethods, insecurePasswordReset, insecureRandomness, insecureWebSocket, ipcPathTraversal, javaDeserialization, jwtAlgConfusion, k8sNoResourceLimits, k8sPrivileged, k8sSecretNotEncrypted, lambdaWithoutVPC, largeBundleImport, logInjection, magicNumbers, massAssignment, missingAIRateLimit, missingAuthMiddleware, missingAuthRateLimit, missingBruteForce, missingCSP, missingCSRF, missingCertPinning, missingCloudTrail, missingContentDisposition, missingDBEncryption, missingErrorBoundary, missingFileSizeLimits, missingHSTS, missingHTTPS, missingLockFile, missingOAuthState, missingPagination, missingRequestSizeLimit, missingRequestValidation, missingSRI, missingSecurityMeta, nPlusOneQuery, nextPublicSecret, noRateLimiting, nosqlInjection, openRedirectParams, overlyPermissiveIAM, pathTraversal, pickleDeserialization, piiInLogs, prototypePollution, raceCondition, rdsPubliclyAccessible, reflectedCORSOrigin, regexDos, runCustomRules, s3BucketNoEncryption, secretInBundleConfig, secretInCLIArgument, secretInErrorResponse, secretInHTMLAttribute, secretInURLParam, secretLoggedToConsole, secretsInCI, securityGroupAllInbound, sensitiveAsyncStorage, sensitiveLocalStorage, sensitiveURLParams, sessionFixation, sqlInjection, ssrfVulnerability, ssti, stripeWebhookUnprotected, supabaseAnonAdmin, supabaseNoRLS, syncFileOps, terraformStateExposed, timingAttack, todoLeftInCode, unencryptedPII, unpinnedGitHubAction, unprotectedAPIRoutes, unprotectedDownload, unsafeObjectAssign, unsanitizedFilenames, unsanitizedHTMLExport, unvalidatedAPIParams, unvalidatedEventData, unvalidatedRedirect, vulnerableDependencies, weakHashing, weakPasswordRequirements, weakRSAKeySize, webhookSignatureVerification, xssVulnerability, xxeVulnerability };
249
+ /**
250
+ * Shannon-entropy based secret detection.
251
+ *
252
+ * Catches high-entropy string literals that look like API keys / tokens /
253
+ * credentials even when they don't match any of the service-specific
254
+ * hardcoded-secret rules (VC001, VC132, etc). Fires a single `ENTROPY`
255
+ * finding per suspicious literal.
256
+ *
257
+ * The trick to keeping this useful is keeping false positives down. Real
258
+ * codebases are full of high-entropy strings that aren't secrets — SHA-256
259
+ * hashes, UUIDs, base64 integrity hashes, Tailwind-generated class names,
260
+ * SVG path data, Next.js content-addressed filenames, publishable
261
+ * (intentionally-public) keys from Clerk/Stripe/etc. This scanner has three
262
+ * layers of suppression before emitting a finding:
263
+ *
264
+ * 1. File-level: skip lockfiles, CSS/SVG/image/map files, node_modules,
265
+ * minified/bundled output.
266
+ * 2. Shape-level: if the string matches a known-safe shape (UUID, Git SHA,
267
+ * integrity hash, publishable key prefix, Tailwind fingerprint, SVG
268
+ * path data, Next.js content hash), skip.
269
+ * 3. Context-level: inspect the assignment context — if the variable name
270
+ * implies "this holds a hash/digest/fingerprint" (not a secret), skip.
271
+ * If the variable name implies a secret (key/token/etc), lower the
272
+ * entropy bar. Otherwise require high entropy AND no safe signals.
273
+ *
274
+ * When in doubt we bias toward suppressing — one FP every 100 scans is much
275
+ * more costly to the product than one missed secret, because the expanded
276
+ * VC132-VC151 service-specific rules already cover the most common key
277
+ * formats.
278
+ */
279
+
280
+ /**
281
+ * Scan a batch of files for high-entropy string literals.
282
+ *
283
+ * Each element in `files` must be `{ path, content }`. Returns a list of
284
+ * `Finding` objects for suspicious string literals. See the file header for
285
+ * the suppression layers.
286
+ */
287
+ declare function scanEntropy(files: {
288
+ path: string;
289
+ content: string;
290
+ }[]): Finding[];
291
+
292
+ export { type AIFilterResult, type Confidence, type CustomRule, type DetectedFramework, type FilteredFinding, type Finding, type GradeResult, type RuleMatch, type SecurityGrade, type Severity, allCustomRules, allRules, androidDebuggable, blockingMainThread, calculateGrade, callbackHell, clickjacking, clientComponentSecret, clientSideAuth, commandInjection, complianceMap, consoleLogProduction, corsLocalhost, corsServerless, corsWildcard, dangerousInnerHTML, deprecatedTLS, detectFramework, disabledTLSVerification, djangoDebug, dockerCopySensitive, dockerLatestTag, dockerRunAsRoot, dockerTooManyPorts, ecbModeEncryption, electronNavigationUnrestricted, emptyCatchBlock, envNotGitignored, evalUsage, eventListenerLeak, exposedAdminRoutes, exposedAuthSecret, exposedDBCredentials, exposedDatabaseStudio, exposedDebugMode, exposedDockerPorts, exposedEnvFile, exposedGitDir, exposedServerActions, exposedSourceMaps, exposedStackTraces, filterFalsePositives, firebaseClientConfig, flaskSecretKey, freeRules, getSnippet, githubActionsInjection, graphqlIntrospection, hardcodedAnthropicKey, hardcodedDatadogKey, hardcodedEncryptionKey, hardcodedGCPServiceAccount, hardcodedGitHubPAT, hardcodedGitLabToken, hardcodedIPAllowlist, hardcodedJWTSecret, hardcodedMailgunKey, hardcodedOAuthSecret, hardcodedPineconeKey, hardcodedSecrets, hardcodedSendGridKey, hardcodedShopifyToken, hardcodedSlackToken, hardcodedSupabaseServiceRole, hardcodedTwilioKey, hardcodedVaultToken, hardcodedVercelToken, hostHeaderRedirect, httpRequestSmuggling, insecureCookies, insecureDeepLink, insecureDeserialization, insecureDirectObjectReference, insecureElectronWindow, insecureFileUpload, insecureGRPC, insecureHTTPMethods, insecurePasswordReset, insecureRandomness, insecureWebSocket, ipcPathTraversal, javaDeserialization, jwtAlgConfusion, k8sNoResourceLimits, k8sPrivileged, k8sSecretNotEncrypted, lambdaWithoutVPC, largeBundleImport, logInjection, magicNumbers, massAssignment, missingAIRateLimit, missingAuthMiddleware, missingAuthRateLimit, missingBruteForce, missingCSP, missingCSRF, missingCertPinning, missingCloudTrail, missingContentDisposition, missingDBEncryption, missingErrorBoundary, missingFileSizeLimits, missingHSTS, missingHTTPS, missingLockFile, missingOAuthState, missingPagination, missingRequestSizeLimit, missingRequestValidation, missingSRI, missingSecurityMeta, nPlusOneQuery, nextPublicSecret, noRateLimiting, nosqlInjection, openRedirectParams, overlyPermissiveIAM, pathTraversal, pickleDeserialization, piiInLogs, prototypePollution, raceCondition, rdsPubliclyAccessible, reflectedCORSOrigin, regexDos, runCustomRules, s3BucketNoEncryption, scanEntropy, secretInBundleConfig, secretInCLIArgument, secretInErrorResponse, secretInHTMLAttribute, secretInURLParam, secretLoggedToConsole, secretsInCI, securityGroupAllInbound, sensitiveAsyncStorage, sensitiveLocalStorage, sensitiveURLParams, sessionFixation, sqlInjection, ssrfVulnerability, ssti, stripeWebhookUnprotected, supabaseAnonAdmin, supabaseNoRLS, syncFileOps, terraformStateExposed, timingAttack, todoLeftInCode, unencryptedPII, unpinnedGitHubAction, unprotectedAPIRoutes, unprotectedDownload, unsafeObjectAssign, unsanitizedFilenames, unsanitizedHTMLExport, unvalidatedAPIParams, unvalidatedEventData, unvalidatedRedirect, vulnerableDependencies, weakHashing, weakPasswordRequirements, weakRSAKeySize, webhookSignatureVerification, xssVulnerability, xxeVulnerability };
package/dist/index.js CHANGED
@@ -5222,6 +5222,163 @@ async function filterFalsePositives(findings, fileContents) {
5222
5222
  totalBefore
5223
5223
  };
5224
5224
  }
5225
+
5226
+ // src/entropy-scanner.ts
5227
+ function shannonEntropy(str) {
5228
+ const freq = {};
5229
+ for (const ch of str) {
5230
+ freq[ch] = (freq[ch] || 0) + 1;
5231
+ }
5232
+ const len = str.length;
5233
+ let entropy = 0;
5234
+ for (const count of Object.values(freq)) {
5235
+ const p = count / len;
5236
+ entropy -= p * Math.log2(p);
5237
+ }
5238
+ return entropy;
5239
+ }
5240
+ var SAFE_PATTERNS = [
5241
+ // UUIDs (v1-v5)
5242
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
5243
+ // Git commit hashes (long and short)
5244
+ /^[0-9a-f]{40}$/i,
5245
+ /^[0-9a-f]{7,8}$/i,
5246
+ // Hex colors
5247
+ /^#?[0-9a-fA-F]{3,8}$/,
5248
+ // Small base64 (< 20 chars can't be a real key anyway)
5249
+ /^[A-Za-z0-9+/]{1,19}={0,2}$/,
5250
+ // Package versions
5251
+ /^\d+\.\d+\.\d+/,
5252
+ // Integrity-hash prefix (sha256-, sha512-, ...)
5253
+ /^sha\d+-/i,
5254
+ // URLs without credentials in them
5255
+ /^https?:\/\/[^:@]*$/,
5256
+ /^https?:\/\/registry\.npmjs\.org\//,
5257
+ /^https?:\/\/registry\.yarnpkg\.com\//,
5258
+ // Full package integrity hash (sha512-[base64]=)
5259
+ /^sha\d+-[A-Za-z0-9+/=]+$/,
5260
+ // Package tarball URLs
5261
+ /\.tgz$/,
5262
+ /registry.*\/-\/.*\.tgz$/,
5263
+ // ISO dates / times
5264
+ /^\d{4}-\d{2}-\d{2}/,
5265
+ // Locale tags
5266
+ /^[a-z]{2}-[A-Z]{2}$/,
5267
+ // Text encodings
5268
+ /^utf-?8|ascii|latin|iso-8859/i,
5269
+ // MIME types
5270
+ /^(?:application|text|image|audio|video)\//,
5271
+ // CSS keyword values
5272
+ /^(?:inherit|none|auto|block|flex|grid|absolute|relative|fixed|px|em|rem|%)/,
5273
+ // Developer-placeholder tokens
5274
+ /^(?:test|example|sample|demo|placeholder|temp|tmp|foo|bar|baz|lorem|ipsum)/i,
5275
+ // XML / DTD markers
5276
+ /DTD|DOCTYPE|w3\.org|apple\.com\/DTDs/i,
5277
+ /xmlns|schema|xsd|xsi/i,
5278
+ // NEW — added in Wave 3.2 for context-aware FP reduction
5279
+ // ──────────────────────────────────────────────────────
5280
+ // Tailwind JIT / CSS-in-JS class-name fingerprints, e.g. "css-2kx3yr8",
5281
+ // "tw-abc12def", "jss-1a2b3c4d". Typically prefix + 6-12 hex-ish chars.
5282
+ /^(?:css|tw|jss|emotion|styled|mui|chakra)-[a-z0-9]{4,14}$/i,
5283
+ // SVG path data — starts with a path command letter followed by coords.
5284
+ // These can get very long and high-entropy but are never secrets.
5285
+ /^[MmLlHhVvCcSsQqTtAaZz][\d.,\s\-MmLlHhVvCcSsQqTtAaZz]{10,}$/,
5286
+ // Next.js / Vite / webpack content-addressed asset filenames, e.g.
5287
+ // "main.4e5f6a78.js", "chunk-2a3b.js", "_next/static/chunks/pages-xyz".
5288
+ /\.[0-9a-f]{6,16}\.(?:js|css|mjs|woff2?|ttf|png|jpg|svg)(?:\?.*)?$/i,
5289
+ /^_next\//,
5290
+ // Publishable / client-side keys from common vendors. Flagged by their
5291
+ // own service-specific rules if they look wrong, but entropy should NOT
5292
+ // double-flag these. They are designed to ship to the browser.
5293
+ /^pk_(?:live|test|[a-z0-9]+)_/,
5294
+ // Stripe / Clerk publishable
5295
+ /^NEXT_PUBLIC_|^VITE_|^REACT_APP_/,
5296
+ // Build-time public env vars
5297
+ /^pub_/,
5298
+ // Segment etc.
5299
+ /^ey[A-Za-z0-9_-]+\.ey[A-Za-z0-9_-]+\./,
5300
+ // JWT header.payload prefix — don't flag solely on entropy
5301
+ // Content-Security-Policy hashes / nonces in HTML/JSON
5302
+ /^'sha\d+-/,
5303
+ /^nonce-/i,
5304
+ // Embedded data URIs
5305
+ /^data:[a-z]+\//i
5306
+ ];
5307
+ var SKIP_FILES = /\.(css|scss|less|svg|md|txt|html?|xml|yml|yaml|toml|lock|map|woff2?|ttf|eot|ico|png|jpg|gif|webp)$/i;
5308
+ var SKIP_FILENAMES = /(?:package-lock\.json|pnpm-lock\.yaml|yarn\.lock|composer\.lock|Gemfile\.lock|Cargo\.lock|poetry\.lock|Pipfile\.lock|shrinkwrap\.json)$/i;
5309
+ var SAFE_VAR_NAMES = /(?:description|message|text|label|title|content|template|html|svg|css|style|class(?:Name)?|query|mutation|schema|regex|pattern|format|placeholder|comment|url|path|route|endpoint|href|src|alt|name|type|version|encoding|charset|locale|translation|copy|prose|markdown|slug|handle)/i;
5310
+ var HASH_LIKE_VAR_NAMES = /(?:^|[^a-z])(?:hash|digest|checksum|fingerprint|etag|crc|md5|sha1|sha256|sha512|contenthash|buildid|revision|commit|sri|integrity|cacheKey|fileHash|assetId|versionId)(?:$|[^a-z])/i;
5311
+ var HASH_PREFIX_RE = /^(?:sha\d+|md5|crc32|base64|bcrypt|argon2|pbkdf2|blake2b?)[:_]/i;
5312
+ var SECRET_VAR_NAMES = /(?:^|[^a-z])(?:key|secret|token|password|passwd|pwd|api[_-]?key|auth|credential|private|signing|bearer|access[_-]?token|refresh[_-]?token|session[_-]?id)(?:$|[^a-z])/i;
5313
+ function getSnippet2(content, line) {
5314
+ const lines = content.split("\n");
5315
+ const start = Math.max(0, line - 2);
5316
+ const end = Math.min(lines.length, line + 2);
5317
+ return lines.slice(start, end).map((l, i) => {
5318
+ const lineNum = start + i + 1;
5319
+ const prefix = lineNum === line ? ">" : " ";
5320
+ return `${prefix} ${String(lineNum).padStart(5)} | ${l}`;
5321
+ }).join("\n");
5322
+ }
5323
+ function scanEntropy(files) {
5324
+ const findings = [];
5325
+ for (const { path: filePath, content } of files) {
5326
+ if (SKIP_FILES.test(filePath)) continue;
5327
+ if (SKIP_FILENAMES.test(filePath)) continue;
5328
+ const basename = filePath.split("/").pop() || "";
5329
+ if (SKIP_FILENAMES.test(basename)) continue;
5330
+ if (filePath.includes("node_modules")) continue;
5331
+ if (filePath.includes(".min.")) continue;
5332
+ if (/pro-rules-bundle|\.bundle\.|\.chunk\./i.test(filePath)) continue;
5333
+ if (/(?:\.test\.|\.spec\.|__tests__|__mocks__|fixtures?\/)/i.test(filePath)) continue;
5334
+ const lines = content.split("\n");
5335
+ for (let i = 0; i < lines.length; i++) {
5336
+ const line = lines[i];
5337
+ const trimmed = line.trimStart();
5338
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
5339
+ const stringPattern = /(?:[:=]\s*)(["'`])([^"'`\n]{20,120})\1/g;
5340
+ let match;
5341
+ while ((match = stringPattern.exec(line)) !== null) {
5342
+ const value = match[2];
5343
+ if (SAFE_PATTERNS.some((p) => p.test(value))) continue;
5344
+ if (HASH_PREFIX_RE.test(value)) continue;
5345
+ const beforeAssign = line.substring(0, match.index);
5346
+ if (SAFE_VAR_NAMES.test(beforeAssign)) continue;
5347
+ if (/^https?:\/\/[^:@]*$/.test(value)) continue;
5348
+ if ((value.match(/\s/g) || []).length > 2) continue;
5349
+ const isHex = /^[0-9a-fA-F]+$/.test(value);
5350
+ const isBase64 = /^[A-Za-z0-9+/]+=*$/.test(value);
5351
+ let threshold = 4;
5352
+ if (isHex) threshold = 3;
5353
+ else if (isBase64) threshold = 4.5;
5354
+ if (value.length < 20) continue;
5355
+ const entropy = shannonEntropy(value);
5356
+ if (entropy < threshold) continue;
5357
+ const varName = beforeAssign.match(/(\w+)\s*[:=]\s*$/)?.[1] || "";
5358
+ if (HASH_LIKE_VAR_NAMES.test(varName) && (isHex || isBase64)) continue;
5359
+ const isLikelySecret = SECRET_VAR_NAMES.test(varName);
5360
+ if (entropy < 4.5 && !isLikelySecret) continue;
5361
+ const masked = value.substring(0, 6) + "..." + value.substring(value.length - 4);
5362
+ findings.push({
5363
+ id: `ENTROPY-${filePath}:${i + 1}`,
5364
+ rule: "ENTROPY",
5365
+ severity: isLikelySecret ? "critical" : "high",
5366
+ title: "High-Entropy String Detected (Possible Secret)",
5367
+ description: `Found a high-entropy string (${entropy.toFixed(1)} bits) that may be a hardcoded secret or API key: "${masked}"`,
5368
+ file: filePath,
5369
+ line: i + 1,
5370
+ snippet: getSnippet2(content, i + 1),
5371
+ fix: "If this is a secret, move it to an environment variable. If it's not a secret (e.g., hash, encoded data), add it to .xploitscanignore.",
5372
+ category: "Secrets",
5373
+ source: "custom",
5374
+ owasp: "A02:2021",
5375
+ cwe: "CWE-798"
5376
+ });
5377
+ }
5378
+ }
5379
+ }
5380
+ return findings;
5381
+ }
5225
5382
  export {
5226
5383
  allCustomRules,
5227
5384
  allRules,
@@ -5351,6 +5508,7 @@ export {
5351
5508
  regexDos,
5352
5509
  runCustomRules,
5353
5510
  s3BucketNoEncryption,
5511
+ scanEntropy,
5354
5512
  secretInBundleConfig,
5355
5513
  secretInCLIArgument,
5356
5514
  secretInErrorResponse,