@secfn/core 0.0.1

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 (64) hide show
  1. package/README.md +334 -0
  2. package/dist/access-control/rbac.d.ts +48 -0
  3. package/dist/access-control/rbac.d.ts.map +1 -0
  4. package/dist/access-control/rbac.js +203 -0
  5. package/dist/access-control/rbac.js.map +1 -0
  6. package/dist/core/secfn.d.ts +28 -0
  7. package/dist/core/secfn.d.ts.map +1 -0
  8. package/dist/core/secfn.js +73 -0
  9. package/dist/core/secfn.js.map +1 -0
  10. package/dist/index.d.ts +12 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +12 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/monitoring/security-monitor.d.ts +46 -0
  15. package/dist/monitoring/security-monitor.d.ts.map +1 -0
  16. package/dist/monitoring/security-monitor.js +172 -0
  17. package/dist/monitoring/security-monitor.js.map +1 -0
  18. package/dist/rate-limiting/rate-limiter.d.ts +31 -0
  19. package/dist/rate-limiting/rate-limiter.d.ts.map +1 -0
  20. package/dist/rate-limiting/rate-limiter.js +140 -0
  21. package/dist/rate-limiting/rate-limiter.js.map +1 -0
  22. package/dist/scanning/patterns.d.ts +3 -0
  23. package/dist/scanning/patterns.d.ts.map +1 -0
  24. package/dist/scanning/patterns.js +139 -0
  25. package/dist/scanning/patterns.js.map +1 -0
  26. package/dist/scanning/scanner.d.ts +36 -0
  27. package/dist/scanning/scanner.d.ts.map +1 -0
  28. package/dist/scanning/scanner.js +152 -0
  29. package/dist/scanning/scanner.js.map +1 -0
  30. package/dist/secrets/encryption.d.ts +17 -0
  31. package/dist/secrets/encryption.d.ts.map +1 -0
  32. package/dist/secrets/encryption.js +47 -0
  33. package/dist/secrets/encryption.js.map +1 -0
  34. package/dist/secrets/vault.d.ts +43 -0
  35. package/dist/secrets/vault.d.ts.map +1 -0
  36. package/dist/secrets/vault.js +126 -0
  37. package/dist/secrets/vault.js.map +1 -0
  38. package/dist/storage/indexeddb-storage.d.ts +37 -0
  39. package/dist/storage/indexeddb-storage.d.ts.map +1 -0
  40. package/dist/storage/indexeddb-storage.js +284 -0
  41. package/dist/storage/indexeddb-storage.js.map +1 -0
  42. package/dist/types/index.d.ts +189 -0
  43. package/dist/types/index.d.ts.map +1 -0
  44. package/dist/types/index.js +2 -0
  45. package/dist/types/index.js.map +1 -0
  46. package/dist/utils/helpers.d.ts +8 -0
  47. package/dist/utils/helpers.d.ts.map +1 -0
  48. package/dist/utils/helpers.js +73 -0
  49. package/dist/utils/helpers.js.map +1 -0
  50. package/examples/basic-usage.ts +143 -0
  51. package/package.json +68 -0
  52. package/src/access-control/rbac.ts +271 -0
  53. package/src/core/secfn.ts +90 -0
  54. package/src/index.ts +27 -0
  55. package/src/monitoring/security-monitor.ts +245 -0
  56. package/src/rate-limiting/rate-limiter.ts +181 -0
  57. package/src/scanning/patterns.ts +140 -0
  58. package/src/scanning/scanner.ts +199 -0
  59. package/src/secrets/encryption.ts +74 -0
  60. package/src/secrets/vault.ts +187 -0
  61. package/src/storage/indexeddb-storage.ts +355 -0
  62. package/src/types/index.ts +213 -0
  63. package/src/utils/helpers.ts +84 -0
  64. package/tsconfig.json +26 -0
@@ -0,0 +1,245 @@
1
+ import type { SecurityEvent, EventQuery, SecurityMetrics, TimeRange, SecurityEventType, Severity, SecurityAnomaly } from '../types/index.js';
2
+ import type { IndexedDBStorage } from '../storage/indexeddb-storage.js';
3
+ import { generateId } from '../utils/helpers.js';
4
+
5
+ export interface MonitoringConfig {
6
+ storage: IndexedDBStorage;
7
+ retention?: number;
8
+ anomalyDetection?: {
9
+ enabled: boolean;
10
+ sensitivity: number;
11
+ };
12
+ }
13
+
14
+ export interface LogEventOptions {
15
+ type: SecurityEventType;
16
+ severity: Severity;
17
+ userId?: string;
18
+ ip: string;
19
+ userAgent?: string;
20
+ resource?: string;
21
+ action?: string;
22
+ metadata?: Record<string, unknown>;
23
+ }
24
+
25
+ export interface ResolveEventOptions {
26
+ resolvedBy: string;
27
+ notes?: string;
28
+ }
29
+
30
+ export class SecurityMonitor {
31
+ private storage: IndexedDBStorage;
32
+ private retention: number;
33
+ private anomalyDetection: { enabled: boolean; sensitivity: number };
34
+
35
+ constructor(config: MonitoringConfig) {
36
+ this.storage = config.storage;
37
+ this.retention = config.retention || 90 * 24 * 60 * 60 * 1000;
38
+ this.anomalyDetection = config.anomalyDetection || {
39
+ enabled: false,
40
+ sensitivity: 0.8,
41
+ };
42
+ }
43
+
44
+ async logEvent(options: LogEventOptions): Promise<string> {
45
+ const event: SecurityEvent = {
46
+ id: generateId('event'),
47
+ timestamp: Date.now(),
48
+ type: options.type,
49
+ severity: options.severity,
50
+ userId: options.userId,
51
+ ip: options.ip,
52
+ userAgent: options.userAgent,
53
+ resource: options.resource,
54
+ action: options.action,
55
+ metadata: options.metadata || {},
56
+ resolved: false,
57
+ };
58
+
59
+ await this.storage.logSecurityEvent(event);
60
+
61
+ return event.id;
62
+ }
63
+
64
+ async queryEvents(query: EventQuery): Promise<SecurityEvent[]> {
65
+ return await this.storage.queryEvents(query);
66
+ }
67
+
68
+ async getEvent(id: string): Promise<SecurityEvent | null> {
69
+ const events = await this.storage.queryEvents({ limit: 1 });
70
+ return events.find(e => e.id === id) || null;
71
+ }
72
+
73
+ async resolveEvent(id: string, options: ResolveEventOptions): Promise<void> {
74
+ const events = await this.storage.queryEvents({});
75
+ const event = events.find(e => e.id === id);
76
+
77
+ if (!event) {
78
+ throw new Error(`Event ${id} not found`);
79
+ }
80
+
81
+ event.resolved = true;
82
+ event.resolvedAt = Date.now();
83
+ event.resolvedBy = options.resolvedBy;
84
+ event.notes = options.notes;
85
+
86
+ await this.storage.logSecurityEvent(event);
87
+ }
88
+
89
+ async getMetrics(range: TimeRange): Promise<SecurityMetrics> {
90
+ const metrics = await this.storage.getMetrics(range);
91
+
92
+ if (this.anomalyDetection.enabled) {
93
+ const events = await this.storage.queryEvents({
94
+ startDate: range.start,
95
+ endDate: range.end,
96
+ });
97
+
98
+ const anomalies = await this.detectAnomalies(events);
99
+ metrics.anomalies = anomalies;
100
+ }
101
+
102
+ return metrics;
103
+ }
104
+
105
+ async getRecentEvents(limit = 100): Promise<SecurityEvent[]> {
106
+ return await this.storage.queryEvents({ limit });
107
+ }
108
+
109
+ async getUnresolvedEvents(): Promise<SecurityEvent[]> {
110
+ return await this.storage.queryEvents({ resolved: false });
111
+ }
112
+
113
+ async getCriticalEvents(hours = 24): Promise<SecurityEvent[]> {
114
+ const startDate = Date.now() - hours * 60 * 60 * 1000;
115
+ return await this.storage.queryEvents({
116
+ severity: 'critical',
117
+ startDate,
118
+ });
119
+ }
120
+
121
+ async getEventsByUser(userId: string, limit = 100): Promise<SecurityEvent[]> {
122
+ return await this.storage.queryEvents({
123
+ userId,
124
+ limit,
125
+ });
126
+ }
127
+
128
+ async getEventsByIP(ip: string, limit = 100): Promise<SecurityEvent[]> {
129
+ return await this.storage.queryEvents({
130
+ ip,
131
+ limit,
132
+ });
133
+ }
134
+
135
+ async cleanup(): Promise<void> {
136
+ await this.storage.cleanup(this.retention);
137
+ }
138
+
139
+ private async detectAnomalies(events: SecurityEvent[]): Promise<SecurityAnomaly[]> {
140
+ const anomalies: SecurityAnomaly[] = [];
141
+
142
+ const authFailures = events.filter(e => e.type === 'auth_failure');
143
+ if (authFailures.length > 0) {
144
+ const failureRate = this.calculateFailureRate(events, authFailures);
145
+ if (failureRate > this.anomalyDetection.sensitivity) {
146
+ anomalies.push({
147
+ id: generateId('anomaly'),
148
+ timestamp: Date.now(),
149
+ type: 'spike_in_failures',
150
+ score: failureRate,
151
+ description: `Unusual spike in authentication failures: ${(failureRate * 100).toFixed(1)}%`,
152
+ relatedEvents: authFailures.slice(0, 10).map(e => e.id),
153
+ });
154
+ }
155
+ }
156
+
157
+ const userActivityPatterns = this.analyzeUserActivity(events);
158
+ for (const pattern of userActivityPatterns) {
159
+ if (pattern.score > this.anomalyDetection.sensitivity) {
160
+ anomalies.push({
161
+ id: generateId('anomaly'),
162
+ timestamp: Date.now(),
163
+ type: 'unusual_access_pattern',
164
+ score: pattern.score,
165
+ description: pattern.description,
166
+ relatedEvents: pattern.eventIds,
167
+ });
168
+ }
169
+ }
170
+
171
+ return anomalies;
172
+ }
173
+
174
+ private calculateFailureRate(totalEvents: SecurityEvent[], failures: SecurityEvent[]): number {
175
+ if (totalEvents.length === 0) return 0;
176
+
177
+ const authRelatedEvents = totalEvents.filter(e =>
178
+ e.type === 'auth_failure' ||
179
+ e.action?.includes('auth') ||
180
+ e.action?.includes('login')
181
+ );
182
+
183
+ if (authRelatedEvents.length === 0) return 0;
184
+
185
+ return failures.length / authRelatedEvents.length;
186
+ }
187
+
188
+ private analyzeUserActivity(events: SecurityEvent[]): Array<{
189
+ score: number;
190
+ description: string;
191
+ eventIds: string[];
192
+ }> {
193
+ const patterns: Array<{
194
+ score: number;
195
+ description: string;
196
+ eventIds: string[];
197
+ }> = [];
198
+
199
+ const userGroups = new Map<string, SecurityEvent[]>();
200
+ for (const event of events) {
201
+ if (!event.userId) continue;
202
+
203
+ const userEvents = userGroups.get(event.userId) || [];
204
+ userEvents.push(event);
205
+ userGroups.set(event.userId, userEvents);
206
+ }
207
+
208
+ for (const [userId, userEvents] of userGroups) {
209
+ const uniqueIPs = new Set(userEvents.map(e => e.ip));
210
+
211
+ if (uniqueIPs.size > 5) {
212
+ patterns.push({
213
+ score: Math.min(uniqueIPs.size / 10, 1.0),
214
+ description: `User ${userId} accessed from ${uniqueIPs.size} different IP addresses`,
215
+ eventIds: userEvents.slice(0, 10).map(e => e.id),
216
+ });
217
+ }
218
+
219
+ const timeGaps = this.detectTimeAnomalies(userEvents);
220
+ if (timeGaps.length > 0) {
221
+ patterns.push({
222
+ score: 0.7,
223
+ description: `Unusual access times detected for user ${userId}`,
224
+ eventIds: timeGaps.map(e => e.id),
225
+ });
226
+ }
227
+ }
228
+
229
+ return patterns;
230
+ }
231
+
232
+ private detectTimeAnomalies(events: SecurityEvent[]): SecurityEvent[] {
233
+ const anomalous: SecurityEvent[] = [];
234
+
235
+ for (const event of events) {
236
+ const hour = new Date(event.timestamp).getHours();
237
+
238
+ if (hour >= 2 && hour <= 5) {
239
+ anomalous.push(event);
240
+ }
241
+ }
242
+
243
+ return anomalous;
244
+ }
245
+ }
@@ -0,0 +1,181 @@
1
+ import type { RateLimitRule, RateLimitEntry, RateLimitResult, RateLimitViolation } from '../types/index.js';
2
+ import type { IndexedDBStorage } from '../storage/indexeddb-storage.js';
3
+ import { generateId } from '../utils/helpers.js';
4
+
5
+ export interface RateLimiterConfig {
6
+ storage: IndexedDBStorage;
7
+ rules: {
8
+ global?: RateLimitRule;
9
+ perUser?: RateLimitRule;
10
+ perIP?: RateLimitRule;
11
+ perEndpoint?: Record<string, RateLimitRule>;
12
+ };
13
+ onLimitExceeded?: (violation: RateLimitViolation) => void | Promise<void>;
14
+ }
15
+
16
+ export interface RateLimitCheckRequest {
17
+ userId?: string;
18
+ ip: string;
19
+ endpoint?: string;
20
+ }
21
+
22
+ export class RateLimiter {
23
+ private storage: IndexedDBStorage;
24
+ private rules: RateLimiterConfig['rules'];
25
+ private onLimitExceeded?: (violation: RateLimitViolation) => void | Promise<void>;
26
+
27
+ constructor(config: RateLimiterConfig) {
28
+ this.storage = config.storage;
29
+ this.rules = config.rules;
30
+ this.onLimitExceeded = config.onLimitExceeded;
31
+ }
32
+
33
+ async check(request: RateLimitCheckRequest): Promise<RateLimitResult> {
34
+ const checks: Array<{ key: string; rule: RateLimitRule }> = [];
35
+
36
+ if (this.rules.global) {
37
+ checks.push({
38
+ key: 'global',
39
+ rule: this.rules.global,
40
+ });
41
+ }
42
+
43
+ if (this.rules.perUser && request.userId) {
44
+ checks.push({
45
+ key: `user:${request.userId}`,
46
+ rule: this.rules.perUser,
47
+ });
48
+ }
49
+
50
+ if (this.rules.perIP) {
51
+ checks.push({
52
+ key: `ip:${request.ip}`,
53
+ rule: this.rules.perIP,
54
+ });
55
+ }
56
+
57
+ if (this.rules.perEndpoint && request.endpoint) {
58
+ const endpointRule = this.rules.perEndpoint[request.endpoint];
59
+ if (endpointRule) {
60
+ checks.push({
61
+ key: `endpoint:${request.endpoint}:${request.ip}`,
62
+ rule: endpointRule,
63
+ });
64
+ }
65
+ }
66
+
67
+ for (const check of checks) {
68
+ const result = await this.checkLimit(check.key, check.rule);
69
+
70
+ if (!result.allowed) {
71
+ await this.handleViolation({
72
+ id: generateId('violation'),
73
+ timestamp: Date.now(),
74
+ key: check.key,
75
+ ip: request.ip,
76
+ userId: request.userId,
77
+ endpoint: request.endpoint || 'unknown',
78
+ requestCount: result.limit || 0,
79
+ limit: check.rule.requests,
80
+ window: check.rule.window,
81
+ blocked: true,
82
+ });
83
+
84
+ return result;
85
+ }
86
+ }
87
+
88
+ return {
89
+ allowed: true,
90
+ remaining: checks.length > 0 ? checks[0].rule.requests : 0,
91
+ resetAt: Date.now() + (checks.length > 0 ? checks[0].rule.window : 0),
92
+ };
93
+ }
94
+
95
+ private async checkLimit(key: string, rule: RateLimitRule): Promise<RateLimitResult> {
96
+ const now = Date.now();
97
+ const entry = await this.storage.getRateLimitEntry(key);
98
+
99
+ if (!entry || now >= entry.resetAt) {
100
+ const newEntry: RateLimitEntry = {
101
+ key,
102
+ count: 1,
103
+ resetAt: now + rule.window,
104
+ blocked: false,
105
+ };
106
+ await this.storage.setRateLimitEntry(newEntry);
107
+
108
+ return {
109
+ allowed: true,
110
+ remaining: rule.requests - 1,
111
+ resetAt: newEntry.resetAt,
112
+ limit: rule.requests,
113
+ };
114
+ }
115
+
116
+ if (entry.blocked && entry.blockedUntil && now < entry.blockedUntil) {
117
+ return {
118
+ allowed: false,
119
+ remaining: 0,
120
+ resetAt: entry.resetAt,
121
+ retryAfter: entry.blockedUntil - now,
122
+ limit: rule.requests,
123
+ };
124
+ }
125
+
126
+ if (entry.count >= rule.requests) {
127
+ entry.blocked = true;
128
+ entry.blockedUntil = now + (rule.blockDuration || rule.window);
129
+ await this.storage.setRateLimitEntry(entry);
130
+
131
+ return {
132
+ allowed: false,
133
+ remaining: 0,
134
+ resetAt: entry.resetAt,
135
+ retryAfter: entry.blockedUntil - now,
136
+ limit: rule.requests,
137
+ };
138
+ }
139
+
140
+ entry.count++;
141
+ await this.storage.setRateLimitEntry(entry);
142
+
143
+ return {
144
+ allowed: true,
145
+ remaining: rule.requests - entry.count,
146
+ resetAt: entry.resetAt,
147
+ limit: rule.requests,
148
+ };
149
+ }
150
+
151
+ private async handleViolation(violation: RateLimitViolation): Promise<void> {
152
+ if (this.onLimitExceeded) {
153
+ try {
154
+ await this.onLimitExceeded(violation);
155
+ } catch (error) {
156
+ console.error('[SecFn] Error in rate limit violation handler:', error);
157
+ }
158
+ }
159
+ }
160
+
161
+ async reset(key: string): Promise<void> {
162
+ await this.storage.deleteRateLimitEntry(key);
163
+ }
164
+
165
+ async resetUser(userId: string): Promise<void> {
166
+ await this.reset(`user:${userId}`);
167
+ }
168
+
169
+ async resetIP(ip: string): Promise<void> {
170
+ await this.reset(`ip:${ip}`);
171
+ }
172
+
173
+ async unblock(key: string): Promise<void> {
174
+ const entry = await this.storage.getRateLimitEntry(key);
175
+ if (entry) {
176
+ entry.blocked = false;
177
+ entry.blockedUntil = undefined;
178
+ await this.storage.setRateLimitEntry(entry);
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,140 @@
1
+ import type { SecretPattern } from '../types/index.js';
2
+
3
+ export const DEFAULT_PATTERNS: SecretPattern[] = [
4
+ {
5
+ name: 'AWS Access Key',
6
+ pattern: /AKIA[0-9A-Z]{16}/g,
7
+ entropy: 4.5,
8
+ description: 'AWS Access Key ID',
9
+ severity: 'critical',
10
+ },
11
+ {
12
+ name: 'AWS Secret Key',
13
+ pattern: /aws[_-]?secret[_-]?access[_-]?key[=:\s]+['"]?([a-zA-Z0-9/+=]{40})['"]?/gi,
14
+ entropy: 5.0,
15
+ description: 'AWS Secret Access Key',
16
+ severity: 'critical',
17
+ },
18
+ {
19
+ name: 'Generic API Key',
20
+ pattern: /api[_-]?key[_-]?[=:]\s*['"]?([a-zA-Z0-9]{32,})['"]?/gi,
21
+ entropy: 4.0,
22
+ description: 'Generic API key pattern',
23
+ severity: 'high',
24
+ },
25
+ {
26
+ name: 'Private Key',
27
+ pattern: /-----BEGIN (RSA|OPENSSH|DSA|EC|PGP) PRIVATE KEY-----/g,
28
+ description: 'Private cryptographic key',
29
+ severity: 'critical',
30
+ },
31
+ {
32
+ name: 'JWT Token',
33
+ pattern: /eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
34
+ description: 'JSON Web Token',
35
+ severity: 'high',
36
+ },
37
+ {
38
+ name: 'GitHub Token',
39
+ pattern: /ghp_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/g,
40
+ entropy: 4.5,
41
+ description: 'GitHub Personal Access Token',
42
+ severity: 'critical',
43
+ },
44
+ {
45
+ name: 'Stripe API Key',
46
+ pattern: /sk_live_[a-zA-Z0-9]{24,}|rk_live_[a-zA-Z0-9]{24,}/g,
47
+ entropy: 4.5,
48
+ description: 'Stripe API Key',
49
+ severity: 'critical',
50
+ },
51
+ {
52
+ name: 'Google API Key',
53
+ pattern: /AIza[0-9A-Za-z_-]{35}/g,
54
+ entropy: 4.5,
55
+ description: 'Google API Key',
56
+ severity: 'high',
57
+ },
58
+ {
59
+ name: 'Slack Token',
60
+ pattern: /xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24,}/g,
61
+ entropy: 4.5,
62
+ description: 'Slack API Token',
63
+ severity: 'high',
64
+ },
65
+ {
66
+ name: 'OpenAI API Key',
67
+ pattern: /sk-[a-zA-Z0-9]{48}/g,
68
+ entropy: 4.5,
69
+ description: 'OpenAI API Key',
70
+ severity: 'critical',
71
+ },
72
+ {
73
+ name: 'Anthropic API Key',
74
+ pattern: /sk-ant-[a-zA-Z0-9_-]{95,}/g,
75
+ entropy: 4.5,
76
+ description: 'Anthropic API Key',
77
+ severity: 'critical',
78
+ },
79
+ {
80
+ name: 'Database Connection String',
81
+ pattern: /(postgres|mysql|mongodb):\/\/[^\s'"<>]+:[^\s'"<>]+@[^\s'"<>]+/gi,
82
+ description: 'Database connection string with credentials',
83
+ severity: 'critical',
84
+ },
85
+ {
86
+ name: 'Password in Code',
87
+ pattern: /(password|passwd|pwd)[=:\s]+['"]([^'"]{8,})['"]/gi,
88
+ entropy: 3.5,
89
+ description: 'Hardcoded password',
90
+ severity: 'high',
91
+ },
92
+ {
93
+ name: 'Bearer Token',
94
+ pattern: /Bearer\s+[a-zA-Z0-9_\-\.]{20,}/gi,
95
+ entropy: 4.0,
96
+ description: 'Bearer authentication token',
97
+ severity: 'high',
98
+ },
99
+ {
100
+ name: 'NPM Token',
101
+ pattern: /npm_[a-zA-Z0-9]{36}/g,
102
+ entropy: 4.5,
103
+ description: 'NPM Access Token',
104
+ severity: 'high',
105
+ },
106
+ {
107
+ name: 'Twilio API Key',
108
+ pattern: /SK[a-f0-9]{32}/g,
109
+ entropy: 4.5,
110
+ description: 'Twilio API Key',
111
+ severity: 'high',
112
+ },
113
+ {
114
+ name: 'SendGrid API Key',
115
+ pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
116
+ entropy: 4.5,
117
+ description: 'SendGrid API Key',
118
+ severity: 'high',
119
+ },
120
+ {
121
+ name: 'Mailgun API Key',
122
+ pattern: /key-[a-f0-9]{32}/g,
123
+ entropy: 4.0,
124
+ description: 'Mailgun API Key',
125
+ severity: 'high',
126
+ },
127
+ {
128
+ name: 'Azure Storage Key',
129
+ pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[^;]+/gi,
130
+ description: 'Azure Storage Connection String',
131
+ severity: 'critical',
132
+ },
133
+ {
134
+ name: 'Generic Secret',
135
+ pattern: /(secret|token|api[_-]?key|auth[_-]?token)[=:\s]+['"]([a-zA-Z0-9_\-\/.+=]{20,})['"]/gi,
136
+ entropy: 3.5,
137
+ description: 'Generic secret pattern',
138
+ severity: 'medium',
139
+ },
140
+ ];