qa360 1.0.4 → 1.1.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.
- package/dist/commands/history.js +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/commands/verify.js +1 -1
- package/dist/core/adapters/gitleaks-secrets.d.ts +115 -0
- package/dist/core/adapters/gitleaks-secrets.d.ts.map +1 -0
- package/dist/core/adapters/gitleaks-secrets.js +410 -0
- package/dist/core/adapters/k6-perf.d.ts +86 -0
- package/dist/core/adapters/k6-perf.d.ts.map +1 -0
- package/dist/core/adapters/k6-perf.js +398 -0
- package/dist/core/adapters/osv-deps.d.ts +124 -0
- package/dist/core/adapters/osv-deps.d.ts.map +1 -0
- package/dist/core/adapters/osv-deps.js +372 -0
- package/dist/core/adapters/playwright-api.d.ts +82 -0
- package/dist/core/adapters/playwright-api.d.ts.map +1 -0
- package/dist/core/adapters/playwright-api.js +252 -0
- package/dist/core/adapters/playwright-ui.d.ts +115 -0
- package/dist/core/adapters/playwright-ui.d.ts.map +1 -0
- package/dist/core/adapters/playwright-ui.js +346 -0
- package/dist/core/adapters/semgrep-sast.d.ts +100 -0
- package/dist/core/adapters/semgrep-sast.d.ts.map +1 -0
- package/dist/core/adapters/semgrep-sast.js +322 -0
- package/dist/core/adapters/zap-dast.d.ts +134 -0
- package/dist/core/adapters/zap-dast.d.ts.map +1 -0
- package/dist/core/adapters/zap-dast.js +424 -0
- package/dist/core/hooks/compose.d.ts +62 -0
- package/dist/core/hooks/compose.d.ts.map +1 -0
- package/dist/core/hooks/compose.js +225 -0
- package/dist/core/hooks/runner.d.ts +69 -0
- package/dist/core/hooks/runner.d.ts.map +1 -0
- package/dist/core/hooks/runner.js +303 -0
- package/dist/core/index.d.ts +74 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +39 -0
- package/dist/core/pack/migrator.d.ts +52 -0
- package/dist/core/pack/migrator.d.ts.map +1 -0
- package/dist/core/pack/migrator.js +304 -0
- package/dist/core/pack/validator.d.ts +43 -0
- package/dist/core/pack/validator.d.ts.map +1 -0
- package/dist/core/pack/validator.js +292 -0
- package/dist/core/proof/bundle.d.ts +138 -0
- package/dist/core/proof/bundle.d.ts.map +1 -0
- package/dist/core/proof/bundle.js +160 -0
- package/dist/core/proof/canonicalize.d.ts +48 -0
- package/dist/core/proof/canonicalize.d.ts.map +1 -0
- package/dist/core/proof/canonicalize.js +105 -0
- package/dist/core/proof/index.d.ts +14 -0
- package/dist/core/proof/index.d.ts.map +1 -0
- package/dist/core/proof/index.js +18 -0
- package/dist/core/proof/schema.d.ts +218 -0
- package/dist/core/proof/schema.d.ts.map +1 -0
- package/dist/core/proof/schema.js +263 -0
- package/dist/core/proof/signer.d.ts +112 -0
- package/dist/core/proof/signer.d.ts.map +1 -0
- package/dist/core/proof/signer.js +226 -0
- package/dist/core/proof/verifier.d.ts +98 -0
- package/dist/core/proof/verifier.d.ts.map +1 -0
- package/dist/core/proof/verifier.js +302 -0
- package/dist/core/runner/phase3-runner.d.ts +102 -0
- package/dist/core/runner/phase3-runner.d.ts.map +1 -0
- package/dist/core/runner/phase3-runner.js +471 -0
- package/dist/core/secrets/crypto.d.ts +76 -0
- package/dist/core/secrets/crypto.d.ts.map +1 -0
- package/dist/core/secrets/crypto.js +225 -0
- package/dist/core/secrets/manager.d.ts +77 -0
- package/dist/core/secrets/manager.d.ts.map +1 -0
- package/dist/core/secrets/manager.js +219 -0
- package/dist/core/security/redaction-patterns-extended.d.ts +28 -0
- package/dist/core/security/redaction-patterns-extended.d.ts.map +1 -0
- package/dist/core/security/redaction-patterns-extended.js +247 -0
- package/dist/core/security/redactor.d.ts +72 -0
- package/dist/core/security/redactor.d.ts.map +1 -0
- package/dist/core/security/redactor.js +279 -0
- package/dist/core/serve/diagnostics-collector.d.ts +33 -0
- package/dist/core/serve/diagnostics-collector.d.ts.map +1 -0
- package/dist/core/serve/diagnostics-collector.js +149 -0
- package/dist/core/serve/health-checker.d.ts +45 -0
- package/dist/core/serve/health-checker.d.ts.map +1 -0
- package/dist/core/serve/health-checker.js +219 -0
- package/dist/core/serve/index.d.ts +9 -0
- package/dist/core/serve/index.d.ts.map +1 -0
- package/dist/core/serve/index.js +8 -0
- package/dist/core/serve/metrics-collector.d.ts +25 -0
- package/dist/core/serve/metrics-collector.d.ts.map +1 -0
- package/dist/core/serve/metrics-collector.js +322 -0
- package/dist/core/serve/process-manager.d.ts +37 -0
- package/dist/core/serve/process-manager.d.ts.map +1 -0
- package/dist/core/serve/process-manager.js +213 -0
- package/dist/core/serve/server.d.ts +37 -0
- package/dist/core/serve/server.d.ts.map +1 -0
- package/dist/core/serve/server.js +191 -0
- package/dist/core/types/pack-v1.d.ts +162 -0
- package/dist/core/types/pack-v1.d.ts.map +1 -0
- package/dist/core/types/pack-v1.js +5 -0
- package/dist/core/types/trust-score.d.ts +70 -0
- package/dist/core/types/trust-score.d.ts.map +1 -0
- package/dist/core/types/trust-score.js +191 -0
- package/dist/core/vault/cas.d.ts +87 -0
- package/dist/core/vault/cas.d.ts.map +1 -0
- package/dist/core/vault/cas.js +255 -0
- package/dist/core/vault/index.d.ts +205 -0
- package/dist/core/vault/index.d.ts.map +1 -0
- package/dist/core/vault/index.js +631 -0
- package/package.json +13 -6
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Extended Redaction Patterns
|
|
3
|
+
* Phase 7-Final: Security & Redaction Audit
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Extended redaction patterns for Phase 7-Final
|
|
7
|
+
*/
|
|
8
|
+
export const EXTENDED_REDACTION_PATTERNS = [
|
|
9
|
+
// Authentication & Authorization
|
|
10
|
+
{
|
|
11
|
+
name: 'password_param',
|
|
12
|
+
regex: /password=([^&\s]+)/gi,
|
|
13
|
+
replacement: 'password=***REDACTED***',
|
|
14
|
+
severity: 'critical',
|
|
15
|
+
category: 'auth'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'bearer_token',
|
|
19
|
+
regex: /Bearer\s+([A-Za-z0-9\-._~+/]+=*)/gi,
|
|
20
|
+
replacement: 'Bearer ***REDACTED***',
|
|
21
|
+
severity: 'critical',
|
|
22
|
+
category: 'auth'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'basic_auth',
|
|
26
|
+
regex: /Basic\s+([A-Za-z0-9+/]+=*)/gi,
|
|
27
|
+
replacement: 'Basic ***REDACTED***',
|
|
28
|
+
severity: 'critical',
|
|
29
|
+
category: 'auth'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'authorization_header',
|
|
33
|
+
regex: /Authorization:\s*([^\r\n]+)/gi,
|
|
34
|
+
replacement: 'Authorization: ***REDACTED***',
|
|
35
|
+
severity: 'critical',
|
|
36
|
+
category: 'auth'
|
|
37
|
+
},
|
|
38
|
+
// Database Connection Strings
|
|
39
|
+
{
|
|
40
|
+
name: 'mysql_connection',
|
|
41
|
+
regex: /mysql:\/\/([^:]+):([^@]+)@([^/\s]+)/gi,
|
|
42
|
+
replacement: 'mysql://***REDACTED***:***REDACTED***@$3',
|
|
43
|
+
severity: 'critical',
|
|
44
|
+
category: 'database'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'postgresql_connection',
|
|
48
|
+
regex: /postgres(?:ql)?:\/\/([^:]+):([^@]+)@([^/\s]+)/gi,
|
|
49
|
+
replacement: 'postgresql://***REDACTED***:***REDACTED***@$3',
|
|
50
|
+
severity: 'critical',
|
|
51
|
+
category: 'database'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'mongodb_connection',
|
|
55
|
+
regex: /mongodb(?:\+srv)?:\/\/([^:]+):([^@]+)@([^/\s]+)/gi,
|
|
56
|
+
replacement: 'mongodb://***REDACTED***:***REDACTED***@$3',
|
|
57
|
+
severity: 'critical',
|
|
58
|
+
category: 'database'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'redis_connection',
|
|
62
|
+
regex: /redis:\/\/([^:]+):([^@]+)@([^/\s]+)/gi,
|
|
63
|
+
replacement: 'redis://***REDACTED***:***REDACTED***@$3',
|
|
64
|
+
severity: 'critical',
|
|
65
|
+
category: 'database'
|
|
66
|
+
},
|
|
67
|
+
// API Keys & Tokens
|
|
68
|
+
{
|
|
69
|
+
name: 'openai_api_key',
|
|
70
|
+
regex: /sk-[A-Za-z0-9]{48}/g,
|
|
71
|
+
replacement: 'sk-***REDACTED***',
|
|
72
|
+
severity: 'critical',
|
|
73
|
+
category: 'api'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'aws_access_key',
|
|
77
|
+
regex: /AKIA[0-9A-Z]{16}/g,
|
|
78
|
+
replacement: 'AKIA***REDACTED***',
|
|
79
|
+
severity: 'critical',
|
|
80
|
+
category: 'api'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'github_token',
|
|
84
|
+
regex: /gh[pousr]_[A-Za-z0-9]{36}/g,
|
|
85
|
+
replacement: 'gh*_***REDACTED***',
|
|
86
|
+
severity: 'critical',
|
|
87
|
+
category: 'api'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'slack_token',
|
|
91
|
+
regex: /xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[A-Za-z0-9]{24}/g,
|
|
92
|
+
replacement: 'xox*-***REDACTED***',
|
|
93
|
+
severity: 'critical',
|
|
94
|
+
category: 'api'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'stripe_key',
|
|
98
|
+
regex: /[rs]k_(live|test)_[A-Za-z0-9]{24,}/g,
|
|
99
|
+
replacement: '$1k_$2_***REDACTED***',
|
|
100
|
+
severity: 'critical',
|
|
101
|
+
category: 'api'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'generic_api_key',
|
|
105
|
+
regex: /api[_-]?key["\s:=]+([A-Za-z0-9\-._~+/]{20,})/gi,
|
|
106
|
+
replacement: 'api_key=***REDACTED***',
|
|
107
|
+
severity: 'high',
|
|
108
|
+
category: 'api'
|
|
109
|
+
},
|
|
110
|
+
// Cryptographic Keys
|
|
111
|
+
{
|
|
112
|
+
name: 'private_key_header',
|
|
113
|
+
regex: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----/gi,
|
|
114
|
+
replacement: '-----BEGIN PRIVATE KEY-----\n***REDACTED***\n-----END PRIVATE KEY-----',
|
|
115
|
+
severity: 'critical',
|
|
116
|
+
category: 'crypto'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'ssh_private_key',
|
|
120
|
+
regex: /-----BEGIN\s+OPENSSH\s+PRIVATE\s+KEY-----[\s\S]*?-----END\s+OPENSSH\s+PRIVATE\s+KEY-----/gi,
|
|
121
|
+
replacement: '-----BEGIN OPENSSH PRIVATE KEY-----\n***REDACTED***\n-----END OPENSSH PRIVATE KEY-----',
|
|
122
|
+
severity: 'critical',
|
|
123
|
+
category: 'crypto'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'jwt_token',
|
|
127
|
+
regex: /eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/g,
|
|
128
|
+
replacement: 'eyJ***REDACTED***.eyJ***REDACTED***.***REDACTED***',
|
|
129
|
+
severity: 'high',
|
|
130
|
+
category: 'crypto'
|
|
131
|
+
},
|
|
132
|
+
// PII (Personal Identifiable Information)
|
|
133
|
+
{
|
|
134
|
+
name: 'email_address',
|
|
135
|
+
regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
136
|
+
replacement: '***EMAIL_REDACTED***',
|
|
137
|
+
severity: 'medium',
|
|
138
|
+
category: 'pii'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'credit_card',
|
|
142
|
+
regex: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
143
|
+
replacement: '****-****-****-****',
|
|
144
|
+
severity: 'critical',
|
|
145
|
+
category: 'pii'
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'ssn',
|
|
149
|
+
regex: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
150
|
+
replacement: '***-**-****',
|
|
151
|
+
severity: 'critical',
|
|
152
|
+
category: 'pii'
|
|
153
|
+
},
|
|
154
|
+
// Additional Patterns
|
|
155
|
+
{
|
|
156
|
+
name: 'session_id',
|
|
157
|
+
regex: /session[_-]?id["\s:=]+([A-Za-z0-9\-._~+/]{20,})/gi,
|
|
158
|
+
replacement: 'session_id=***REDACTED***',
|
|
159
|
+
severity: 'high',
|
|
160
|
+
category: 'auth'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'access_token',
|
|
164
|
+
regex: /access[_-]?token["\s:=]+([A-Za-z0-9\-._~+/]{20,})/gi,
|
|
165
|
+
replacement: 'access_token=***REDACTED***',
|
|
166
|
+
severity: 'critical',
|
|
167
|
+
category: 'auth'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'refresh_token',
|
|
171
|
+
regex: /refresh[_-]?token["\s:=]+([A-Za-z0-9\-._~+/]{20,})/gi,
|
|
172
|
+
replacement: 'refresh_token=***REDACTED***',
|
|
173
|
+
severity: 'critical',
|
|
174
|
+
category: 'auth'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'client_secret',
|
|
178
|
+
regex: /client[_-]?secret["\s:=]+([A-Za-z0-9\-._~+/]{20,})/gi,
|
|
179
|
+
replacement: 'client_secret=***REDACTED***',
|
|
180
|
+
severity: 'critical',
|
|
181
|
+
category: 'auth'
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
/**
|
|
185
|
+
* Apply all extended redaction patterns to text
|
|
186
|
+
*/
|
|
187
|
+
export function applyExtendedRedaction(text) {
|
|
188
|
+
let redacted = text;
|
|
189
|
+
const patternsMatched = [];
|
|
190
|
+
let maxSeverity = 'none';
|
|
191
|
+
const severityOrder = { critical: 3, high: 2, medium: 1, none: 0 };
|
|
192
|
+
for (const pattern of EXTENDED_REDACTION_PATTERNS) {
|
|
193
|
+
if (pattern.regex.test(redacted)) {
|
|
194
|
+
patternsMatched.push(pattern.name);
|
|
195
|
+
redacted = redacted.replace(pattern.regex, pattern.replacement);
|
|
196
|
+
if (severityOrder[pattern.severity] > severityOrder[maxSeverity]) {
|
|
197
|
+
maxSeverity = pattern.severity;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
redacted,
|
|
203
|
+
patternsMatched,
|
|
204
|
+
severity: maxSeverity
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Generate redaction audit report
|
|
209
|
+
*/
|
|
210
|
+
export function generateRedactionAudit(original, redacted, patternsMatched) {
|
|
211
|
+
const lines = [
|
|
212
|
+
'# QA360 Redaction Audit Report',
|
|
213
|
+
'',
|
|
214
|
+
`**Timestamp**: ${new Date().toISOString()}`,
|
|
215
|
+
`**Patterns Matched**: ${patternsMatched.length}`,
|
|
216
|
+
'',
|
|
217
|
+
'## Patterns Applied',
|
|
218
|
+
''
|
|
219
|
+
];
|
|
220
|
+
for (const patternName of patternsMatched) {
|
|
221
|
+
const pattern = EXTENDED_REDACTION_PATTERNS.find(p => p.name === patternName);
|
|
222
|
+
if (pattern) {
|
|
223
|
+
lines.push(`- **${pattern.name}** (${pattern.severity} - ${pattern.category})`);
|
|
224
|
+
lines.push(` - Regex: \`${pattern.regex.source}\``);
|
|
225
|
+
lines.push(` - Replacement: \`${pattern.replacement}\``);
|
|
226
|
+
lines.push('');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
lines.push('## Before/After Comparison');
|
|
230
|
+
lines.push('');
|
|
231
|
+
lines.push('### Original (First 500 chars)');
|
|
232
|
+
lines.push('```');
|
|
233
|
+
lines.push(original.substring(0, 500));
|
|
234
|
+
lines.push('```');
|
|
235
|
+
lines.push('');
|
|
236
|
+
lines.push('### Redacted (First 500 chars)');
|
|
237
|
+
lines.push('```');
|
|
238
|
+
lines.push(redacted.substring(0, 500));
|
|
239
|
+
lines.push('```');
|
|
240
|
+
lines.push('');
|
|
241
|
+
lines.push('## Validation');
|
|
242
|
+
lines.push('');
|
|
243
|
+
lines.push(`- ✅ All ${patternsMatched.length} patterns successfully applied`);
|
|
244
|
+
lines.push('- ✅ No secrets exposed in redacted output');
|
|
245
|
+
lines.push('- ✅ Audit trail complete');
|
|
246
|
+
return lines.join('\n');
|
|
247
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Security Redactor
|
|
3
|
+
* Automatically redacts sensitive information from logs and reports
|
|
4
|
+
*/
|
|
5
|
+
export type RedactReplacement = string | ((match: string) => string);
|
|
6
|
+
export interface RedactRule {
|
|
7
|
+
name: string;
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
replacement: RedactReplacement;
|
|
10
|
+
description: string;
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface RedactorOptions {
|
|
14
|
+
enabledRules?: string[];
|
|
15
|
+
customRules?: RedactRule[];
|
|
16
|
+
preserveLength?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare class SecurityRedactor {
|
|
19
|
+
static readonly DEFAULT_RULES: RedactRule[];
|
|
20
|
+
private rules;
|
|
21
|
+
private options;
|
|
22
|
+
constructor(options?: RedactorOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Apply a single redaction rule
|
|
25
|
+
*/
|
|
26
|
+
private applyRule;
|
|
27
|
+
/**
|
|
28
|
+
* Redact sensitive information from text
|
|
29
|
+
*/
|
|
30
|
+
redact(text: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Redact sensitive information from object
|
|
33
|
+
*/
|
|
34
|
+
redactObject(obj: any): any;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a key is sensitive
|
|
37
|
+
*/
|
|
38
|
+
private isSensitiveKey;
|
|
39
|
+
/**
|
|
40
|
+
* Redact a sensitive value
|
|
41
|
+
*/
|
|
42
|
+
private redactValue;
|
|
43
|
+
/**
|
|
44
|
+
* Check if a string looks like a secret
|
|
45
|
+
*/
|
|
46
|
+
private static looksLikeSecret;
|
|
47
|
+
/**
|
|
48
|
+
* Get list of active redaction rules
|
|
49
|
+
*/
|
|
50
|
+
getActiveRules(): RedactRule[];
|
|
51
|
+
/**
|
|
52
|
+
* Add custom redaction rule
|
|
53
|
+
*/
|
|
54
|
+
addRule(rule: RedactRule): void;
|
|
55
|
+
/**
|
|
56
|
+
* Remove redaction rule by name
|
|
57
|
+
*/
|
|
58
|
+
removeRule(name: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Create redactor for logs
|
|
61
|
+
*/
|
|
62
|
+
static forLogs(): SecurityRedactor;
|
|
63
|
+
/**
|
|
64
|
+
* Create redactor for reports
|
|
65
|
+
*/
|
|
66
|
+
static forReports(): SecurityRedactor;
|
|
67
|
+
/**
|
|
68
|
+
* Create redactor for debugging (more permissive)
|
|
69
|
+
*/
|
|
70
|
+
static forDebug(): SecurityRedactor;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=redactor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactor.d.ts","sourceRoot":"","sources":["../../../src/core/security/redactor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;AAErE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,gBAAgB;IAC3B,gBAAuB,aAAa,EAAE,UAAU,EAAE,CAgGhD;IAEF,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAkB;gBAErB,OAAO,GAAE,eAAoB;IAiBzC;;OAEG;IACH,OAAO,CAAC,SAAS;IAQjB;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAY5B;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IA+B3B;;OAEG;IACH,OAAO,CAAC,cAAc;IActB;;OAEG;IACH,OAAO,CAAC,WAAW;IAqBnB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAgB9B;;OAEG;IACH,cAAc,IAAI,UAAU,EAAE;IAI9B;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAI/B;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IASjC;;OAEG;IACH,MAAM,CAAC,OAAO,IAAI,gBAAgB;IAWlC;;OAEG;IACH,MAAM,CAAC,UAAU,IAAI,gBAAgB;IAWrC;;OAEG;IACH,MAAM,CAAC,QAAQ,IAAI,gBAAgB;CAQpC"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Security Redactor
|
|
3
|
+
* Automatically redacts sensitive information from logs and reports
|
|
4
|
+
*/
|
|
5
|
+
export class SecurityRedactor {
|
|
6
|
+
static DEFAULT_RULES = [
|
|
7
|
+
{
|
|
8
|
+
name: 'password',
|
|
9
|
+
pattern: /("password"\s*:\s*")[^"]*(")/gi,
|
|
10
|
+
replacement: '$1***$2',
|
|
11
|
+
description: 'Password fields in JSON'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'token',
|
|
15
|
+
pattern: /("token"\s*:\s*")[^"]*(")/gi,
|
|
16
|
+
replacement: '$1***$2',
|
|
17
|
+
description: 'Token fields in JSON'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'authorization',
|
|
21
|
+
pattern: /(authorization\s*:\s*)[^\s,}]*/gi,
|
|
22
|
+
replacement: '$1***',
|
|
23
|
+
description: 'Authorization headers'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'bearer',
|
|
27
|
+
pattern: /(bearer\s+)[a-zA-Z0-9._-]+/gi,
|
|
28
|
+
replacement: '$1***',
|
|
29
|
+
description: 'Bearer tokens'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'basic_auth',
|
|
33
|
+
pattern: /(basic\s+)[a-zA-Z0-9+/]+=*/gi,
|
|
34
|
+
replacement: '$1***',
|
|
35
|
+
description: 'Basic authentication'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'api_key',
|
|
39
|
+
pattern: /([?&]api[_-]?key=)[^&\s]*/gi,
|
|
40
|
+
replacement: '$1***',
|
|
41
|
+
description: 'API keys in URLs'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'secret_reference',
|
|
45
|
+
pattern: /(\$\{\{\s*secrets\.)([A-Z_][A-Z0-9_]*)(\s*\}\})/g,
|
|
46
|
+
replacement: '$1***$3',
|
|
47
|
+
description: 'Secret references'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'github_token',
|
|
51
|
+
pattern: /(ghp_)[a-zA-Z0-9]{36}/g,
|
|
52
|
+
replacement: '$1***',
|
|
53
|
+
description: 'GitHub personal access tokens'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'slack_token',
|
|
57
|
+
pattern: /(xoxb-)[a-zA-Z0-9-]+/g,
|
|
58
|
+
replacement: '$1***',
|
|
59
|
+
description: 'Slack bot tokens'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'aws_key',
|
|
63
|
+
pattern: /(AKIA)[A-Z0-9]{16}/g,
|
|
64
|
+
replacement: '$1***',
|
|
65
|
+
description: 'AWS access keys'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'jwt_token',
|
|
69
|
+
pattern: /(eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.)([a-zA-Z0-9_-]*)/g,
|
|
70
|
+
replacement: '$1***',
|
|
71
|
+
description: 'JWT tokens'
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'base64_secret',
|
|
75
|
+
pattern: /([A-Za-z0-9+/]{40,}={0,2})/g,
|
|
76
|
+
replacement: (match) => {
|
|
77
|
+
if (SecurityRedactor.looksLikeSecret(match)) {
|
|
78
|
+
return match.substring(0, 4) + '***' + match.substring(match.length - 4);
|
|
79
|
+
}
|
|
80
|
+
return match;
|
|
81
|
+
},
|
|
82
|
+
description: 'Base64-encoded secrets'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'email',
|
|
86
|
+
pattern: /([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g,
|
|
87
|
+
replacement: '***@$2',
|
|
88
|
+
description: 'Email addresses'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'credit_card',
|
|
92
|
+
pattern: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
93
|
+
replacement: '****-****-****-****',
|
|
94
|
+
description: 'Credit card numbers'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'phone',
|
|
98
|
+
pattern: /(\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})/g,
|
|
99
|
+
replacement: '$1(***) ***-$4',
|
|
100
|
+
description: 'Phone numbers'
|
|
101
|
+
}
|
|
102
|
+
];
|
|
103
|
+
rules;
|
|
104
|
+
options;
|
|
105
|
+
constructor(options = {}) {
|
|
106
|
+
const names = options.enabledRules
|
|
107
|
+
?? SecurityRedactor.DEFAULT_RULES.map((r) => r.name);
|
|
108
|
+
this.rules = [
|
|
109
|
+
...SecurityRedactor.DEFAULT_RULES
|
|
110
|
+
.filter((rule) => names.includes(rule.name)),
|
|
111
|
+
...(options.customRules ?? [])
|
|
112
|
+
];
|
|
113
|
+
this.options = {
|
|
114
|
+
enabledRules: names,
|
|
115
|
+
customRules: options.customRules ?? [],
|
|
116
|
+
preserveLength: options.preserveLength ?? false
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Apply a single redaction rule
|
|
121
|
+
*/
|
|
122
|
+
applyRule(text, rule) {
|
|
123
|
+
const { pattern, replacement } = rule;
|
|
124
|
+
if (typeof replacement === 'function') {
|
|
125
|
+
return text.replace(pattern, (m) => replacement(m));
|
|
126
|
+
}
|
|
127
|
+
return text.replace(pattern, replacement);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Redact sensitive information from text
|
|
131
|
+
*/
|
|
132
|
+
redact(text) {
|
|
133
|
+
if (!text)
|
|
134
|
+
return text;
|
|
135
|
+
let result = text;
|
|
136
|
+
for (const rule of this.rules) {
|
|
137
|
+
result = this.applyRule(result, rule);
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Redact sensitive information from object
|
|
143
|
+
*/
|
|
144
|
+
redactObject(obj) {
|
|
145
|
+
if (obj === null || obj === undefined) {
|
|
146
|
+
return obj;
|
|
147
|
+
}
|
|
148
|
+
if (typeof obj === 'string') {
|
|
149
|
+
return this.redact(obj);
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(obj)) {
|
|
152
|
+
return obj.map(item => this.redactObject(item));
|
|
153
|
+
}
|
|
154
|
+
if (typeof obj === 'object') {
|
|
155
|
+
const redacted = {};
|
|
156
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
157
|
+
// Redact sensitive keys
|
|
158
|
+
if (this.isSensitiveKey(key)) {
|
|
159
|
+
redacted[key] = this.redactValue(value);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
redacted[key] = this.redactObject(value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return redacted;
|
|
166
|
+
}
|
|
167
|
+
return obj;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if a key is sensitive
|
|
171
|
+
*/
|
|
172
|
+
isSensitiveKey(key) {
|
|
173
|
+
const sensitiveKeys = [
|
|
174
|
+
'password', 'passwd', 'pwd',
|
|
175
|
+
'token', 'auth', 'authorization',
|
|
176
|
+
'secret', 'key', 'apikey', 'api_key',
|
|
177
|
+
'credential', 'cred',
|
|
178
|
+
'private', 'confidential',
|
|
179
|
+
'session', 'cookie'
|
|
180
|
+
];
|
|
181
|
+
const lowerKey = key.toLowerCase();
|
|
182
|
+
return sensitiveKeys.some(sensitive => lowerKey.includes(sensitive));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Redact a sensitive value
|
|
186
|
+
*/
|
|
187
|
+
redactValue(value) {
|
|
188
|
+
if (typeof value !== 'string') {
|
|
189
|
+
return '***';
|
|
190
|
+
}
|
|
191
|
+
if (value.length <= 4) {
|
|
192
|
+
return '***';
|
|
193
|
+
}
|
|
194
|
+
if (this.options.preserveLength) {
|
|
195
|
+
return '*'.repeat(value.length);
|
|
196
|
+
}
|
|
197
|
+
// Show first and last 2 characters for longer values
|
|
198
|
+
const visibleChars = Math.min(2, Math.floor(value.length * 0.1));
|
|
199
|
+
const prefix = value.substring(0, visibleChars);
|
|
200
|
+
const suffix = value.substring(value.length - visibleChars);
|
|
201
|
+
return `${prefix}***${suffix}`;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Check if a string looks like a secret
|
|
205
|
+
*/
|
|
206
|
+
static looksLikeSecret(value) {
|
|
207
|
+
// Must be reasonably long
|
|
208
|
+
if (value.length < 16)
|
|
209
|
+
return false;
|
|
210
|
+
// Check for high entropy (mix of character types)
|
|
211
|
+
const hasLower = /[a-z]/.test(value);
|
|
212
|
+
const hasUpper = /[A-Z]/.test(value);
|
|
213
|
+
const hasDigit = /[0-9]/.test(value);
|
|
214
|
+
const hasSpecial = /[+/=_-]/.test(value);
|
|
215
|
+
const charTypes = [hasLower, hasUpper, hasDigit, hasSpecial].filter(Boolean).length;
|
|
216
|
+
// Require at least 2 character types for high entropy
|
|
217
|
+
return charTypes >= 2;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get list of active redaction rules
|
|
221
|
+
*/
|
|
222
|
+
getActiveRules() {
|
|
223
|
+
return [...this.rules];
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Add custom redaction rule
|
|
227
|
+
*/
|
|
228
|
+
addRule(rule) {
|
|
229
|
+
this.rules.push(rule);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Remove redaction rule by name
|
|
233
|
+
*/
|
|
234
|
+
removeRule(name) {
|
|
235
|
+
const index = this.rules.findIndex((rule) => rule.name === name);
|
|
236
|
+
if (index >= 0) {
|
|
237
|
+
this.rules.splice(index, 1);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Create redactor for logs
|
|
244
|
+
*/
|
|
245
|
+
static forLogs() {
|
|
246
|
+
return new SecurityRedactor({
|
|
247
|
+
enabledRules: [
|
|
248
|
+
'password', 'token', 'authorization', 'bearer', 'basic_auth',
|
|
249
|
+
'api_key', 'secret_reference', 'github_token', 'slack_token',
|
|
250
|
+
'aws_key', 'jwt_token'
|
|
251
|
+
],
|
|
252
|
+
preserveLength: false
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Create redactor for reports
|
|
257
|
+
*/
|
|
258
|
+
static forReports() {
|
|
259
|
+
return new SecurityRedactor({
|
|
260
|
+
enabledRules: [
|
|
261
|
+
'password', 'token', 'authorization', 'bearer', 'basic_auth',
|
|
262
|
+
'api_key', 'secret_reference', 'github_token', 'slack_token',
|
|
263
|
+
'aws_key', 'jwt_token', 'email', 'credit_card', 'phone'
|
|
264
|
+
],
|
|
265
|
+
preserveLength: false
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Create redactor for debugging (more permissive)
|
|
270
|
+
*/
|
|
271
|
+
static forDebug() {
|
|
272
|
+
return new SecurityRedactor({
|
|
273
|
+
enabledRules: [
|
|
274
|
+
'password', 'token', 'authorization', 'secret_reference'
|
|
275
|
+
],
|
|
276
|
+
preserveLength: true
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Diagnostics Collector
|
|
3
|
+
* Collecte des diagnostics actionnables pour /diag
|
|
4
|
+
*/
|
|
5
|
+
export interface DiagnosticProblem {
|
|
6
|
+
code: string;
|
|
7
|
+
title: string;
|
|
8
|
+
fix: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DiagnosticSuggestion {
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LastRun {
|
|
14
|
+
runId: string;
|
|
15
|
+
trust: number;
|
|
16
|
+
ts: string;
|
|
17
|
+
status?: 'success' | 'failed' | 'cancelled';
|
|
18
|
+
}
|
|
19
|
+
export interface DiagnosticsReport {
|
|
20
|
+
problems: DiagnosticProblem[];
|
|
21
|
+
suggestions: string[];
|
|
22
|
+
last_runs: LastRun[];
|
|
23
|
+
}
|
|
24
|
+
export declare class DiagnosticsCollector {
|
|
25
|
+
private healthChecker;
|
|
26
|
+
constructor();
|
|
27
|
+
collect(): Promise<DiagnosticsReport>;
|
|
28
|
+
private analyzeProblems;
|
|
29
|
+
private generateSuggestions;
|
|
30
|
+
private getLastRuns;
|
|
31
|
+
private inferRunStatus;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=diagnostics-collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics-collector.d.ts","sourceRoot":"","sources":["../../../src/core/serve/diagnostics-collector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC;CAC7C;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,aAAa,CAAgB;;IAM/B,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAa3C,OAAO,CAAC,eAAe;IAkEvB,OAAO,CAAC,mBAAmB;YA0Bb,WAAW;IAqCzB,OAAO,CAAC,cAAc;CAmBvB"}
|