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.
- package/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +99 -1
- package/README.md +173 -161
- package/SECURITY.md +25 -81
- package/adapters/codex/install.sh +58 -16
- package/adapters/opencode/install.sh +92 -23
- package/lib/index.js +47 -4
- package/lib/patterns/review-patterns.js +58 -11
- package/lib/patterns/slop-patterns.js +154 -147
- package/lib/platform/detect-platform.js +99 -350
- package/lib/platform/detection-configs.js +93 -0
- package/lib/platform/verify-tools.js +10 -78
- package/lib/schemas/README.md +195 -0
- package/lib/schemas/validator.js +247 -0
- package/lib/sources/custom-handler.js +199 -0
- package/lib/sources/policy-questions.js +239 -0
- package/lib/sources/source-cache.js +149 -0
- package/lib/state/workflow-state.js +363 -665
- package/lib/types/README.md +292 -0
- package/lib/types/agent-frontmatter.d.ts +134 -0
- package/lib/types/command-frontmatter.d.ts +107 -0
- package/lib/types/hook-frontmatter.d.ts +115 -0
- package/lib/types/index.d.ts +84 -0
- package/lib/types/plugin-manifest.d.ts +102 -0
- package/lib/types/skill-frontmatter.d.ts +89 -0
- package/lib/utils/cache-manager.js +154 -0
- package/lib/utils/context-optimizer.js +5 -36
- package/lib/utils/deprecation.js +37 -0
- package/lib/utils/shell-escape.js +88 -0
- package/mcp-server/index.js +513 -18
- package/package.json +6 -2
- package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop-around/lib/index.js +170 -0
- package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
- package/plugins/deslop-around/lib/patterns/slop-patterns.js +170 -129
- package/plugins/deslop-around/lib/platform/detect-platform.js +212 -123
- package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +10 -1
- package/plugins/deslop-around/lib/schemas/README.md +195 -0
- package/plugins/deslop-around/lib/schemas/validator.js +205 -0
- package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
- package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
- package/plugins/deslop-around/lib/sources/source-cache.js +149 -0
- package/plugins/deslop-around/lib/state/workflow-state.js +382 -484
- package/plugins/deslop-around/lib/types/README.md +292 -0
- package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/deslop-around/lib/types/index.d.ts +84 -0
- package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
- package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
- package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
- package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/agents/ci-monitor.md +19 -0
- package/plugins/next-task/agents/delivery-validator.md +2 -2
- package/plugins/next-task/agents/implementation-agent.md +3 -4
- package/plugins/next-task/agents/planning-agent.md +77 -19
- package/plugins/next-task/agents/review-orchestrator.md +21 -122
- package/plugins/next-task/agents/task-discoverer.md +164 -23
- package/plugins/next-task/commands/next-task.md +180 -14
- package/plugins/next-task/lib/index.js +170 -0
- package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
- package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
- package/plugins/next-task/lib/platform/detect-platform.js +212 -123
- package/plugins/next-task/lib/platform/detection-configs.js +93 -0
- package/plugins/next-task/lib/platform/verify-tools.js +10 -1
- package/plugins/next-task/lib/schemas/README.md +195 -0
- package/plugins/next-task/lib/schemas/validator.js +205 -0
- package/plugins/next-task/lib/sources/custom-handler.js +199 -0
- package/plugins/next-task/lib/sources/policy-questions.js +239 -0
- package/plugins/next-task/lib/sources/source-cache.js +149 -0
- package/plugins/next-task/lib/state/workflow-state.js +382 -484
- package/plugins/next-task/lib/types/README.md +292 -0
- package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/next-task/lib/types/index.d.ts +84 -0
- package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/next-task/lib/utils/cache-manager.js +154 -0
- package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
- package/plugins/next-task/lib/utils/deprecation.js +37 -0
- package/plugins/next-task/lib/utils/shell-escape.js +88 -0
- package/plugins/project-review/.claude-plugin/plugin.json +1 -1
- package/plugins/project-review/lib/index.js +170 -0
- package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
- package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
- package/plugins/project-review/lib/platform/detect-platform.js +212 -123
- package/plugins/project-review/lib/platform/detection-configs.js +93 -0
- package/plugins/project-review/lib/platform/verify-tools.js +10 -1
- package/plugins/project-review/lib/schemas/README.md +195 -0
- package/plugins/project-review/lib/schemas/validator.js +205 -0
- package/plugins/project-review/lib/sources/custom-handler.js +199 -0
- package/plugins/project-review/lib/sources/policy-questions.js +239 -0
- package/plugins/project-review/lib/sources/source-cache.js +149 -0
- package/plugins/project-review/lib/state/workflow-state.js +382 -484
- package/plugins/project-review/lib/types/README.md +292 -0
- package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/project-review/lib/types/index.d.ts +84 -0
- package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/project-review/lib/utils/cache-manager.js +154 -0
- package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
- package/plugins/project-review/lib/utils/deprecation.js +37 -0
- package/plugins/project-review/lib/utils/shell-escape.js +88 -0
- package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
- package/plugins/reality-check/agents/code-explorer.md +1 -1
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/commands/ship-ci-review-loop.md +19 -0
- package/plugins/ship/lib/index.js +170 -0
- package/plugins/ship/lib/patterns/review-patterns.js +58 -11
- package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
- package/plugins/ship/lib/platform/detect-platform.js +212 -123
- package/plugins/ship/lib/platform/detection-configs.js +93 -0
- package/plugins/ship/lib/platform/verify-tools.js +10 -1
- package/plugins/ship/lib/schemas/README.md +195 -0
- package/plugins/ship/lib/schemas/validator.js +205 -0
- package/plugins/ship/lib/sources/custom-handler.js +199 -0
- package/plugins/ship/lib/sources/policy-questions.js +239 -0
- package/plugins/ship/lib/sources/source-cache.js +149 -0
- package/plugins/ship/lib/state/workflow-state.js +382 -484
- package/plugins/ship/lib/types/README.md +292 -0
- package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/ship/lib/types/index.d.ts +84 -0
- package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/ship/lib/utils/cache-manager.js +154 -0
- package/plugins/ship/lib/utils/context-optimizer.js +115 -37
- package/plugins/ship/lib/utils/deprecation.js +37 -0
- package/plugins/ship/lib/utils/shell-escape.js +88 -0
- package/lib/state/workflow-state.schema.json +0 -282
- package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
- package/plugins/next-task/agents/policy-selector.md +0 -248
- package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
- package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
- package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
- package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
- 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
|
-
|
|
332
|
-
(
|
|
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
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
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 = {
|