awesome-slash 2.4.3 → 2.5.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.
Files changed (146) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +99 -1
  4. package/README.md +173 -161
  5. package/SECURITY.md +25 -81
  6. package/adapters/codex/install.sh +58 -16
  7. package/adapters/opencode/install.sh +92 -23
  8. package/lib/index.js +47 -4
  9. package/lib/patterns/review-patterns.js +58 -11
  10. package/lib/patterns/slop-patterns.js +154 -147
  11. package/lib/platform/detect-platform.js +99 -350
  12. package/lib/platform/detection-configs.js +93 -0
  13. package/lib/platform/verify-tools.js +10 -78
  14. package/lib/schemas/README.md +195 -0
  15. package/lib/schemas/validator.js +247 -0
  16. package/lib/sources/custom-handler.js +199 -0
  17. package/lib/sources/policy-questions.js +239 -0
  18. package/lib/sources/source-cache.js +149 -0
  19. package/lib/state/workflow-state.js +363 -665
  20. package/lib/types/README.md +292 -0
  21. package/lib/types/agent-frontmatter.d.ts +134 -0
  22. package/lib/types/command-frontmatter.d.ts +107 -0
  23. package/lib/types/hook-frontmatter.d.ts +115 -0
  24. package/lib/types/index.d.ts +84 -0
  25. package/lib/types/plugin-manifest.d.ts +102 -0
  26. package/lib/types/skill-frontmatter.d.ts +89 -0
  27. package/lib/utils/cache-manager.js +154 -0
  28. package/lib/utils/context-optimizer.js +5 -36
  29. package/lib/utils/deprecation.js +37 -0
  30. package/lib/utils/shell-escape.js +88 -0
  31. package/mcp-server/index.js +513 -18
  32. package/package.json +6 -2
  33. package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
  34. package/plugins/deslop-around/lib/index.js +170 -0
  35. package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
  36. package/plugins/deslop-around/lib/patterns/slop-patterns.js +170 -129
  37. package/plugins/deslop-around/lib/platform/detect-platform.js +212 -123
  38. package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
  39. package/plugins/deslop-around/lib/platform/verify-tools.js +10 -1
  40. package/plugins/deslop-around/lib/schemas/README.md +195 -0
  41. package/plugins/deslop-around/lib/schemas/validator.js +205 -0
  42. package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
  43. package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
  44. package/plugins/deslop-around/lib/sources/source-cache.js +149 -0
  45. package/plugins/deslop-around/lib/state/workflow-state.js +382 -484
  46. package/plugins/deslop-around/lib/types/README.md +292 -0
  47. package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
  48. package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
  49. package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
  50. package/plugins/deslop-around/lib/types/index.d.ts +84 -0
  51. package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
  52. package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
  53. package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
  54. package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
  55. package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
  56. package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
  57. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  58. package/plugins/next-task/agents/ci-monitor.md +19 -0
  59. package/plugins/next-task/agents/delivery-validator.md +2 -2
  60. package/plugins/next-task/agents/implementation-agent.md +3 -4
  61. package/plugins/next-task/agents/planning-agent.md +77 -19
  62. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  63. package/plugins/next-task/agents/task-discoverer.md +164 -23
  64. package/plugins/next-task/commands/next-task.md +180 -14
  65. package/plugins/next-task/lib/index.js +170 -0
  66. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  67. package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
  68. package/plugins/next-task/lib/platform/detect-platform.js +212 -123
  69. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  70. package/plugins/next-task/lib/platform/verify-tools.js +10 -1
  71. package/plugins/next-task/lib/schemas/README.md +195 -0
  72. package/plugins/next-task/lib/schemas/validator.js +205 -0
  73. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  74. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  75. package/plugins/next-task/lib/sources/source-cache.js +149 -0
  76. package/plugins/next-task/lib/state/workflow-state.js +382 -484
  77. package/plugins/next-task/lib/types/README.md +292 -0
  78. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  79. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  80. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  81. package/plugins/next-task/lib/types/index.d.ts +84 -0
  82. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  83. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  84. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  85. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  86. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  87. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  88. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  89. package/plugins/project-review/lib/index.js +170 -0
  90. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  91. package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
  92. package/plugins/project-review/lib/platform/detect-platform.js +212 -123
  93. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  94. package/plugins/project-review/lib/platform/verify-tools.js +10 -1
  95. package/plugins/project-review/lib/schemas/README.md +195 -0
  96. package/plugins/project-review/lib/schemas/validator.js +205 -0
  97. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  98. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  99. package/plugins/project-review/lib/sources/source-cache.js +149 -0
  100. package/plugins/project-review/lib/state/workflow-state.js +382 -484
  101. package/plugins/project-review/lib/types/README.md +292 -0
  102. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  103. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  104. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  105. package/plugins/project-review/lib/types/index.d.ts +84 -0
  106. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  107. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  108. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  109. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  110. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  111. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  112. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  113. package/plugins/reality-check/agents/code-explorer.md +1 -1
  114. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  115. package/plugins/ship/commands/ship-ci-review-loop.md +19 -0
  116. package/plugins/ship/lib/index.js +170 -0
  117. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  118. package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
  119. package/plugins/ship/lib/platform/detect-platform.js +212 -123
  120. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  121. package/plugins/ship/lib/platform/verify-tools.js +10 -1
  122. package/plugins/ship/lib/schemas/README.md +195 -0
  123. package/plugins/ship/lib/schemas/validator.js +205 -0
  124. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  125. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  126. package/plugins/ship/lib/sources/source-cache.js +149 -0
  127. package/plugins/ship/lib/state/workflow-state.js +382 -484
  128. package/plugins/ship/lib/types/README.md +292 -0
  129. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  130. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  131. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  132. package/plugins/ship/lib/types/index.d.ts +84 -0
  133. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  134. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  135. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  136. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  137. package/plugins/ship/lib/utils/deprecation.js +37 -0
  138. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  139. package/lib/state/workflow-state.schema.json +0 -282
  140. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  141. package/plugins/next-task/agents/policy-selector.md +0 -248
  142. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  143. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  144. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  145. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  146. package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
@@ -328,8 +328,24 @@ const _patternCountByFramework = new Map();
328
328
  */
329
329
  const _flattenedPatterns = new Map();
330
330
 
331
- // Build indexes at module load time
332
- (function buildIndexes() {
331
+ /**
332
+ * Track if indexes have been built (lazy initialization)
333
+ */
334
+ let _indexesBuilt = false;
335
+
336
+ /**
337
+ * Cached arrays for getAvailableFrameworks/Categories (computed once)
338
+ */
339
+ let _cachedFrameworks = null;
340
+ let _cachedCategories = null;
341
+
342
+ /**
343
+ * Build indexes on first access (lazy initialization)
344
+ * This improves module load time by deferring work until needed
345
+ */
346
+ function ensureIndexesBuilt() {
347
+ if (_indexesBuilt) return;
348
+
333
349
  for (const [framework, categories] of Object.entries(reviewPatterns)) {
334
350
  let totalPatterns = 0;
335
351
  const flattened = [];
@@ -355,11 +371,13 @@ const _flattenedPatterns = new Map();
355
371
  _patternCountByFramework.set(framework, totalPatterns);
356
372
  _flattenedPatterns.set(framework, flattened);
357
373
  }
358
- })();
359
374
 
360
- // Freeze the index Sets
361
- Object.freeze(_frameworksSet);
362
- Object.freeze(_categoriesSet);
375
+ // Cache the arrays (avoid repeated Array.from calls)
376
+ _cachedFrameworks = Array.from(_frameworksSet);
377
+ _cachedCategories = Array.from(_categoriesSet);
378
+
379
+ _indexesBuilt = true;
380
+ }
363
381
 
364
382
  /**
365
383
  * Get review patterns for a detected framework (O(1) lookup)
@@ -377,6 +395,7 @@ function getPatternsForFramework(framework) {
377
395
  * @returns {Map<string, Array>} Map of framework -> patterns for that category
378
396
  */
379
397
  function getPatternsByCategory(category) {
398
+ ensureIndexesBuilt();
380
399
  return _patternsByCategory.get(category) || new Map();
381
400
  }
382
401
 
@@ -393,19 +412,23 @@ function getPatternsForFrameworkCategory(framework, category) {
393
412
  }
394
413
 
395
414
  /**
396
- * Get all available frameworks (O(1) via pre-computed Set)
415
+ * Get all available frameworks (O(1) via cached array)
416
+ * Optimized: returns cached array instead of Array.from() on every call
397
417
  * @returns {Array<string>} List of framework names
398
418
  */
399
419
  function getAvailableFrameworks() {
400
- return Array.from(_frameworksSet);
420
+ ensureIndexesBuilt();
421
+ return _cachedFrameworks;
401
422
  }
402
423
 
403
424
  /**
404
- * Get all available categories across all frameworks
425
+ * Get all available categories across all frameworks (O(1) via cached array)
426
+ * Optimized: returns cached array instead of Array.from() on every call
405
427
  * @returns {Array<string>} List of category names
406
428
  */
407
429
  function getAvailableCategories() {
408
- return Array.from(_categoriesSet);
430
+ ensureIndexesBuilt();
431
+ return _cachedCategories;
409
432
  }
410
433
 
411
434
  /**
@@ -435,6 +458,7 @@ function hasPatternsFor(framework) {
435
458
  * @returns {boolean} True if category exists
436
459
  */
437
460
  function hasCategory(category) {
461
+ ensureIndexesBuilt();
438
462
  return _categoriesSet.has(category);
439
463
  }
440
464
 
@@ -444,6 +468,7 @@ function hasCategory(category) {
444
468
  * @returns {number} Number of patterns
445
469
  */
446
470
  function getPatternCount(framework) {
471
+ ensureIndexesBuilt();
447
472
  return _patternCountByFramework.get(framework.toLowerCase()) || 0;
448
473
  }
449
474
 
@@ -452,6 +477,7 @@ function getPatternCount(framework) {
452
477
  * @returns {number} Total pattern count
453
478
  */
454
479
  function getTotalPatternCount() {
480
+ ensureIndexesBuilt();
455
481
  let total = 0;
456
482
  for (const count of _patternCountByFramework.values()) {
457
483
  total += count;
@@ -461,17 +487,37 @@ function getTotalPatternCount() {
461
487
 
462
488
  /**
463
489
  * Search patterns by keyword across all frameworks
490
+ * Optimized: supports pagination to avoid returning thousands of results
464
491
  * @param {string} keyword - Search term (case-insensitive)
492
+ * @param {Object} [options] - Search options
493
+ * @param {number} [options.limit] - Maximum number of results (default: unlimited)
494
+ * @param {number} [options.offset=0] - Number of results to skip
465
495
  * @returns {Array<{framework: string, category: string, pattern: string}>} Matching patterns
466
496
  */
467
- function searchPatterns(keyword) {
497
+ function searchPatterns(keyword, options = {}) {
498
+ ensureIndexesBuilt();
468
499
  const lowerKeyword = keyword.toLowerCase();
500
+ const { limit, offset = 0 } = options;
469
501
  const results = [];
502
+ let skipped = 0;
503
+ let collected = 0;
470
504
 
471
505
  for (const [framework, patterns] of _flattenedPatterns) {
472
506
  for (const { category, pattern } of patterns) {
473
507
  if (pattern.toLowerCase().includes(lowerKeyword)) {
508
+ // Skip results until we reach offset
509
+ if (skipped < offset) {
510
+ skipped++;
511
+ continue;
512
+ }
513
+
474
514
  results.push({ framework, category, pattern });
515
+ collected++;
516
+
517
+ // Stop if we've collected enough results
518
+ if (limit && collected >= limit) {
519
+ return results;
520
+ }
475
521
  }
476
522
  }
477
523
  }
@@ -485,6 +531,7 @@ function searchPatterns(keyword) {
485
531
  * @returns {Array<string>} List of frameworks with this category
486
532
  */
487
533
  function getFrameworksWithCategory(category) {
534
+ ensureIndexesBuilt();
488
535
  const categoryMap = _patternsByCategory.get(category);
489
536
  if (!categoryMap) return [];
490
537
  return Array.from(categoryMap.keys());
@@ -8,34 +8,102 @@
8
8
 
9
9
  /**
10
10
  * Deep freeze an object for V8 optimization and immutability
11
+ * Optimized: uses for-of instead of forEach to avoid function call overhead
11
12
  * @param {Object} obj - Object to freeze
12
13
  * @returns {Object} Frozen object
13
14
  */
14
15
  function deepFreeze(obj) {
15
- Object.keys(obj).forEach(key => {
16
- if (typeof obj[key] === 'object' && obj[key] !== null && !(obj[key] instanceof RegExp)) {
17
- deepFreeze(obj[key]);
16
+ // Freeze the object first (fast path)
17
+ Object.freeze(obj);
18
+
19
+ // Then recursively freeze nested objects (only if needed)
20
+ // Use Object.keys() for cleaner iteration over own properties
21
+ for (const key of Object.keys(obj)) {
22
+ const value = obj[key];
23
+ if (value && typeof value === 'object' && !(value instanceof RegExp)) {
24
+ deepFreeze(value);
18
25
  }
19
- });
20
- return Object.freeze(obj);
26
+ }
27
+
28
+ return obj;
21
29
  }
22
30
 
23
- // Pre-compiled regex cache for performance
31
+ // Pre-compiled regex cache for performance (limited to prevent memory growth)
32
+ const MAX_PATTERN_CACHE_SIZE = 50;
24
33
  const _compiledExcludePatterns = new Map();
25
34
 
35
+ // Exclude result cache for directory-level caching (limited to prevent memory growth)
36
+ const MAX_EXCLUDE_RESULT_CACHE_SIZE = 200;
37
+ const _excludeResultCache = new Map();
38
+
39
+ /**
40
+ * Maximum allowed wildcards in a glob pattern to prevent ReDoS
41
+ */
42
+ const MAX_GLOB_WILDCARDS = 10;
43
+
26
44
  /**
27
45
  * Get a compiled regex for an exclude pattern (cached)
46
+ * Uses safe regex construction to prevent catastrophic backtracking
47
+ * Optimized: uses Map.get() instead of has() + get() (eliminates redundant lookup)
28
48
  * @param {string} pattern - Glob pattern to compile
29
49
  * @returns {RegExp} Compiled regex
30
50
  */
31
51
  function getCompiledPattern(pattern) {
32
- if (!_compiledExcludePatterns.has(pattern)) {
33
- // Escape all regex metacharacters except *, then replace * with .*
34
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
35
- const regexStr = '^' + escaped.replace(/\*/g, '.*') + '$';
36
- _compiledExcludePatterns.set(pattern, new RegExp(regexStr));
52
+ // Try to get cached pattern (O(1) lookup)
53
+ let cached = _compiledExcludePatterns.get(pattern);
54
+ if (cached) {
55
+ return cached;
56
+ }
57
+
58
+ // Enforce cache size limit using FIFO eviction
59
+ if (_compiledExcludePatterns.size >= MAX_PATTERN_CACHE_SIZE) {
60
+ const firstKey = _compiledExcludePatterns.keys().next().value;
61
+ _compiledExcludePatterns.delete(firstKey);
62
+ }
63
+
64
+ // Count wildcards to prevent overly complex patterns
65
+ const wildcardCount = (pattern.match(/\*/g) || []).length;
66
+ if (wildcardCount > MAX_GLOB_WILDCARDS) {
67
+ // Too many wildcards - use a safe fallback that matches nothing dangerous
68
+ const safeRegex = /^$/;
69
+ _compiledExcludePatterns.set(pattern, safeRegex);
70
+ return safeRegex;
37
71
  }
38
- return _compiledExcludePatterns.get(pattern);
72
+
73
+ // Escape all regex metacharacters except *
74
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
75
+
76
+ // Convert glob patterns to regex:
77
+ // - Both * and ** use .* for backward compatibility (patterns match anywhere in path)
78
+ // - ReDoS protection is provided by MAX_GLOB_WILDCARDS limit above
79
+ let regexStr = escaped
80
+ .replace(/\*\*/g, '\0GLOBSTAR\0') // Temporarily mark globstar
81
+ .replace(/\*/g, '.*') // Single star: match anything (backward compatible)
82
+ .replace(/\0GLOBSTAR\0/g, '.*'); // Globstar: match anything
83
+
84
+ regexStr = '^' + regexStr + '$';
85
+ const compiledRegex = new RegExp(regexStr);
86
+ _compiledExcludePatterns.set(pattern, compiledRegex);
87
+ return compiledRegex;
88
+ }
89
+
90
+ /**
91
+ * Helper to create secret detection pattern with common metadata
92
+ * Reduces duplication across similar secret patterns (all have same severity/autoFix/language)
93
+ * @param {RegExp} pattern - Detection regex pattern
94
+ * @param {string} description - Human-readable description
95
+ * @param {Array<string>} [additionalExcludes=[]] - Extra files to exclude beyond standard test files
96
+ * @returns {Object} Complete pattern object
97
+ */
98
+ function createSecretPattern(pattern, description, additionalExcludes = []) {
99
+ return {
100
+ pattern,
101
+ exclude: ['*.test.*', '*.spec.*', '*.example.*', ...additionalExcludes],
102
+ severity: 'critical',
103
+ autoFix: 'flag',
104
+ language: null,
105
+ description
106
+ };
39
107
  }
40
108
 
41
109
  /**
@@ -234,9 +302,12 @@ const slopPatterns = {
234
302
 
235
303
  /**
236
304
  * Hardcoded credentials patterns (expanded for comprehensive detection)
305
+ * Excludes common false positives:
306
+ * - Template placeholders: ${VAR}, {{VAR}}, <VAR>
307
+ * - Masked/example values: xxxxxxxx, ********
237
308
  */
238
309
  hardcoded_secrets: {
239
- pattern: /(password|secret|api[_-]?key|token|credential|auth)[_-]?(key|token|secret|pass)?\s*[:=]\s*["'`][^"'`\s]{8,}["'`]/i,
310
+ pattern: /(password|secret|api[_-]?key|token|credential|auth)[_-]?(key|token|secret|pass)?\s*[:=]\s*["'`](?!\$\{)(?!\{\{)(?!<[A-Z_])(?![x*#]{8,})(?![X*#]{8,})[^"'`\s]{8,}["'`]/i,
240
311
  exclude: ['*.test.*', '*.spec.*', '*.example.*', '*.sample.*', 'README.*', '*.md'],
241
312
  severity: 'critical',
242
313
  autoFix: 'flag',
@@ -247,146 +318,99 @@ const slopPatterns = {
247
318
  /**
248
319
  * JWT tokens (eyJ prefix indicates base64 JSON header)
249
320
  */
250
- jwt_tokens: {
251
- pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
252
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
253
- severity: 'critical',
254
- autoFix: 'flag',
255
- language: null,
256
- description: 'Hardcoded JWT token'
257
- },
321
+ jwt_tokens: createSecretPattern(
322
+ /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
323
+ 'Hardcoded JWT token'
324
+ ),
258
325
 
259
326
  /**
260
327
  * OpenAI API keys (sk-... format)
261
328
  */
262
- openai_api_key: {
263
- pattern: /sk-[a-zA-Z0-9]{32,}/,
264
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
265
- severity: 'critical',
266
- autoFix: 'flag',
267
- language: null,
268
- description: 'Hardcoded OpenAI API key'
269
- },
329
+ openai_api_key: createSecretPattern(
330
+ /sk-[a-zA-Z0-9]{32,}/,
331
+ 'Hardcoded OpenAI API key'
332
+ ),
270
333
 
271
334
  /**
272
335
  * GitHub tokens (personal access tokens, fine-grained tokens, OAuth)
273
336
  */
274
- github_token: {
275
- pattern: /(ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|ghu_[a-zA-Z0-9]{36}|ghs_[a-zA-Z0-9]{36}|ghr_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})/,
276
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
277
- severity: 'critical',
278
- autoFix: 'flag',
279
- language: null,
280
- description: 'Hardcoded GitHub token'
281
- },
337
+ github_token: createSecretPattern(
338
+ /(ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|ghu_[a-zA-Z0-9]{36}|ghs_[a-zA-Z0-9]{36}|ghr_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})/,
339
+ 'Hardcoded GitHub token'
340
+ ),
282
341
 
283
342
  /**
284
343
  * AWS credentials (access key IDs and secret keys)
285
344
  */
286
- aws_credentials: {
287
- pattern: /(AKIA[0-9A-Z]{16}|aws_secret_access_key\s*[:=]\s*["'`][A-Za-z0-9/+=]{40}["'`])/i,
288
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
289
- severity: 'critical',
290
- autoFix: 'flag',
291
- language: null,
292
- description: 'Hardcoded AWS credentials'
293
- },
345
+ aws_credentials: createSecretPattern(
346
+ /(AKIA[0-9A-Z]{16}|aws_secret_access_key\s*[:=]\s*["'`][A-Za-z0-9/+=]{40}["'`])/i,
347
+ 'Hardcoded AWS credentials'
348
+ ),
294
349
 
295
350
  /**
296
351
  * Google Cloud / Firebase API keys and service accounts
297
352
  */
298
- google_api_key: {
299
- pattern: /(AIza[0-9A-Za-z_-]{35}|[0-9]+-[a-z0-9_]{32}\.apps\.googleusercontent\.com)/,
300
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
301
- severity: 'critical',
302
- autoFix: 'flag',
303
- language: null,
304
- description: 'Hardcoded Google/Firebase API key'
305
- },
353
+ google_api_key: createSecretPattern(
354
+ /(AIza[0-9A-Za-z_-]{35}|[0-9]+-[a-z0-9_]{32}\.apps\.googleusercontent\.com)/,
355
+ 'Hardcoded Google/Firebase API key'
356
+ ),
306
357
 
307
358
  /**
308
359
  * Stripe API keys (live and test)
309
360
  */
310
- stripe_api_key: {
311
- pattern: /(sk_live_[a-zA-Z0-9]{24,}|sk_test_[a-zA-Z0-9]{24,}|rk_live_[a-zA-Z0-9]{24,}|rk_test_[a-zA-Z0-9]{24,})/,
312
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
313
- severity: 'critical',
314
- autoFix: 'flag',
315
- language: null,
316
- description: 'Hardcoded Stripe API key'
317
- },
361
+ stripe_api_key: createSecretPattern(
362
+ /(sk_live_[a-zA-Z0-9]{24,}|sk_test_[a-zA-Z0-9]{24,}|rk_live_[a-zA-Z0-9]{24,}|rk_test_[a-zA-Z0-9]{24,})/,
363
+ 'Hardcoded Stripe API key'
364
+ ),
318
365
 
319
366
  /**
320
367
  * Slack tokens (bot, user, webhook)
321
368
  */
322
- slack_token: {
323
- pattern: /(xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxa-[0-9]{10,}-[a-zA-Z0-9]{24}|https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]{8}\/B[A-Z0-9]{8,}\/[a-zA-Z0-9]{24})/,
324
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
325
- severity: 'critical',
326
- autoFix: 'flag',
327
- language: null,
328
- description: 'Hardcoded Slack token or webhook URL'
329
- },
369
+ slack_token: createSecretPattern(
370
+ /(xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}|xoxa-[0-9]{10,}-[a-zA-Z0-9]{24}|https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]{8}\/B[A-Z0-9]{8,}\/[a-zA-Z0-9]{24})/,
371
+ 'Hardcoded Slack token or webhook URL'
372
+ ),
330
373
 
331
374
  /**
332
375
  * Discord tokens and webhook URLs
333
376
  */
334
- discord_token: {
335
- pattern: /(discord.*["'`][A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}["'`]|https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+)/i,
336
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
337
- severity: 'critical',
338
- autoFix: 'flag',
339
- language: null,
340
- description: 'Hardcoded Discord token or webhook'
341
- },
377
+ discord_token: createSecretPattern(
378
+ /(discord.*["'`][A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}["'`]|https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+)/i,
379
+ 'Hardcoded Discord token or webhook'
380
+ ),
342
381
 
343
382
  /**
344
383
  * SendGrid API key
345
384
  */
346
- sendgrid_api_key: {
347
- pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/,
348
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
349
- severity: 'critical',
350
- autoFix: 'flag',
351
- language: null,
352
- description: 'Hardcoded SendGrid API key'
353
- },
385
+ sendgrid_api_key: createSecretPattern(
386
+ /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/,
387
+ 'Hardcoded SendGrid API key'
388
+ ),
354
389
 
355
390
  /**
356
391
  * Twilio credentials
357
392
  */
358
- twilio_credentials: {
359
- pattern: /(AC[a-f0-9]{32}|SK[a-f0-9]{32})/,
360
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
361
- severity: 'critical',
362
- autoFix: 'flag',
363
- language: null,
364
- description: 'Hardcoded Twilio credentials'
365
- },
393
+ twilio_credentials: createSecretPattern(
394
+ /(AC[a-f0-9]{32}|SK[a-f0-9]{32})/,
395
+ 'Hardcoded Twilio credentials'
396
+ ),
366
397
 
367
398
  /**
368
399
  * NPM tokens
369
400
  */
370
- npm_token: {
371
- pattern: /npm_[a-zA-Z0-9]{36}/,
372
- exclude: ['*.test.*', '*.spec.*', '*.example.*'],
373
- severity: 'critical',
374
- autoFix: 'flag',
375
- language: null,
376
- description: 'Hardcoded NPM token'
377
- },
401
+ npm_token: createSecretPattern(
402
+ /npm_[a-zA-Z0-9]{36}/,
403
+ 'Hardcoded NPM token'
404
+ ),
378
405
 
379
406
  /**
380
407
  * Private keys (RSA, DSA, EC, PGP)
381
408
  */
382
- private_key: {
383
- pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/,
384
- exclude: ['*.test.*', '*.spec.*', '*.example.*', '*.pem.example'],
385
- severity: 'critical',
386
- autoFix: 'flag',
387
- language: null,
388
- description: 'Private key in source code'
389
- },
409
+ private_key: createSecretPattern(
410
+ /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/,
411
+ 'Private key in source code',
412
+ ['*.pem.example'] // Additional excludes beyond standard test files
413
+ ),
390
414
 
391
415
  /**
392
416
  * Generic high-entropy strings (potential secrets)
@@ -549,6 +573,7 @@ function getPatternsByAutoFix(autoFix) {
549
573
 
550
574
  /**
551
575
  * Get patterns matching multiple criteria (language AND severity)
576
+ * Optimized: single-pass filtering instead of chained Object.entries
552
577
  * @param {Object} criteria - Filter criteria
553
578
  * @param {string} [criteria.language] - Language filter
554
579
  * @param {string} [criteria.severity] - Severity filter
@@ -556,27 +581,25 @@ function getPatternsByAutoFix(autoFix) {
556
581
  * @returns {Object} Patterns matching all criteria
557
582
  */
558
583
  function getPatternsByCriteria(criteria = {}) {
559
- let result = { ...slopPatterns };
560
-
561
- if (criteria.language) {
562
- const langPatterns = getPatternsForLanguage(criteria.language);
563
- result = Object.fromEntries(
564
- Object.entries(result).filter(([name]) => name in langPatterns)
565
- );
584
+ // Fast path: no criteria means return all patterns
585
+ if (!criteria.language && !criteria.severity && !criteria.autoFix) {
586
+ return { ...slopPatterns };
566
587
  }
567
588
 
568
- if (criteria.severity) {
569
- const severityPatterns = getPatternsBySeverity(criteria.severity);
570
- result = Object.fromEntries(
571
- Object.entries(result).filter(([name]) => name in severityPatterns)
572
- );
573
- }
589
+ // Pre-fetch filter sets (O(1) Map lookups)
590
+ const langPatterns = criteria.language ? getPatternsForLanguage(criteria.language) : null;
591
+ const severityPatterns = criteria.severity ? getPatternsBySeverity(criteria.severity) : null;
592
+ const autoFixPatterns = criteria.autoFix ? getPatternsByAutoFix(criteria.autoFix) : null;
574
593
 
575
- if (criteria.autoFix) {
576
- const autoFixPatterns = getPatternsByAutoFix(criteria.autoFix);
577
- result = Object.fromEntries(
578
- Object.entries(result).filter(([name]) => name in autoFixPatterns)
579
- );
594
+ // Single-pass filter: check all criteria at once
595
+ const result = {};
596
+ for (const [name, pattern] of Object.entries(slopPatterns)) {
597
+ // Check all criteria in one pass (short-circuit on first failure)
598
+ if (langPatterns && !(name in langPatterns)) continue;
599
+ if (severityPatterns && !(name in severityPatterns)) continue;
600
+ if (autoFixPatterns && !(name in autoFixPatterns)) continue;
601
+
602
+ result[name] = pattern;
580
603
  }
581
604
 
582
605
  return result;
@@ -609,7 +632,7 @@ function hasLanguage(language) {
609
632
 
610
633
  /**
611
634
  * Check if a file should be excluded based on pattern rules
612
- * Uses pre-compiled regex cache for performance
635
+ * Uses pre-compiled regex cache and result cache for performance
613
636
  * @param {string} filePath - File path to check
614
637
  * @param {Array<string>} excludePatterns - Exclude patterns
615
638
  * @returns {boolean} True if file should be excluded
@@ -617,10 +640,28 @@ function hasLanguage(language) {
617
640
  function isFileExcluded(filePath, excludePatterns) {
618
641
  if (!excludePatterns || excludePatterns.length === 0) return false;
619
642
 
620
- return excludePatterns.some(pattern => {
643
+ // Create cache key using JSON.stringify for collision-resistant format
644
+ const cacheKey = JSON.stringify([filePath, excludePatterns]);
645
+
646
+ // Check cache first (O(1) lookup)
647
+ if (_excludeResultCache.has(cacheKey)) {
648
+ return _excludeResultCache.get(cacheKey);
649
+ }
650
+
651
+ // Compute result
652
+ const result = excludePatterns.some(pattern => {
621
653
  const regex = getCompiledPattern(pattern);
622
654
  return regex.test(filePath);
623
655
  });
656
+
657
+ // Store in cache with FIFO eviction
658
+ if (_excludeResultCache.size >= MAX_EXCLUDE_RESULT_CACHE_SIZE) {
659
+ const firstKey = _excludeResultCache.keys().next().value;
660
+ _excludeResultCache.delete(firstKey);
661
+ }
662
+ _excludeResultCache.set(cacheKey, result);
663
+
664
+ return result;
624
665
  }
625
666
 
626
667
  module.exports = {