@skillsmith/core 0.4.9 → 0.4.10
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/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/adapters/java-parsers.d.ts.map +1 -1
- package/dist/src/analysis/adapters/java-parsers.js +9 -2
- package/dist/src/analysis/adapters/java-parsers.js.map +1 -1
- package/dist/src/api/client.d.ts +1 -0
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/schemas.d.ts +11 -4
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +8 -1
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/db/drivers/betterSqlite3Driver.d.ts.map +1 -1
- package/dist/src/db/drivers/betterSqlite3Driver.js +5 -3
- package/dist/src/db/drivers/betterSqlite3Driver.js.map +1 -1
- package/dist/src/exports/services.d.ts +1 -0
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +4 -0
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SkillParser.d.ts +20 -0
- package/dist/src/indexer/SkillParser.d.ts.map +1 -1
- package/dist/src/indexer/SkillParser.js +58 -0
- package/dist/src/indexer/SkillParser.js.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.js +4 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.js.map +1 -1
- package/dist/src/scripts/validation/types.d.ts +2 -2
- package/dist/src/security/audit-types.d.ts +2 -2
- package/dist/src/security/audit-types.d.ts.map +1 -1
- package/dist/src/security/audit-types.js.map +1 -1
- package/dist/src/security/sanitization.d.ts.map +1 -1
- package/dist/src/security/sanitization.js +25 -17
- package/dist/src/security/sanitization.js.map +1 -1
- package/dist/src/services/index.d.ts +9 -0
- package/dist/src/services/index.d.ts.map +1 -0
- package/dist/src/services/index.js +10 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/quarantine/QuarantineService.d.ts +149 -0
- package/dist/src/services/quarantine/QuarantineService.d.ts.map +1 -0
- package/dist/src/services/quarantine/QuarantineService.js +406 -0
- package/dist/src/services/quarantine/QuarantineService.js.map +1 -0
- package/dist/src/services/quarantine/index.d.ts +10 -0
- package/dist/src/services/quarantine/index.d.ts.map +1 -0
- package/dist/src/services/quarantine/index.js +14 -0
- package/dist/src/services/quarantine/index.js.map +1 -0
- package/dist/src/services/quarantine/types.d.ts +127 -0
- package/dist/src/services/quarantine/types.d.ts.map +1 -0
- package/dist/src/services/quarantine/types.js +59 -0
- package/dist/src/services/quarantine/types.js.map +1 -0
- package/dist/src/types/skill.d.ts +6 -1
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/types/skill.js.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +2 -0
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/safe-fs.d.ts +63 -0
- package/dist/src/utils/safe-fs.d.ts.map +1 -0
- package/dist/src/utils/safe-fs.js +119 -0
- package/dist/src/utils/safe-fs.js.map +1 -0
- package/dist/src/validation/input-validators.d.ts.map +1 -1
- package/dist/src/validation/input-validators.js +11 -4
- package/dist/src/validation/input-validators.js.map +1 -1
- package/dist/tests/QuarantineRepository.test.js +39 -2
- package/dist/tests/QuarantineRepository.test.js.map +1 -1
- package/dist/tests/edge-cases/EdgeCases.test.js +5 -2
- package/dist/tests/edge-cases/EdgeCases.test.js.map +1 -1
- package/dist/tests/integration/QuarantineService.test.d.ts +11 -0
- package/dist/tests/integration/QuarantineService.test.d.ts.map +1 -0
- package/dist/tests/integration/QuarantineService.test.js +378 -0
- package/dist/tests/integration/QuarantineService.test.js.map +1 -0
- package/dist/tests/unit/check-references.test.d.ts +2 -0
- package/dist/tests/unit/check-references.test.d.ts.map +1 -0
- package/dist/tests/unit/check-references.test.js +118 -0
- package/dist/tests/unit/check-references.test.js.map +1 -0
- package/dist/tests/utils/safe-fs.test.d.ts +12 -0
- package/dist/tests/utils/safe-fs.test.d.ts.map +1 -0
- package/dist/tests/utils/safe-fs.test.js +116 -0
- package/dist/tests/utils/safe-fs.test.js.map +1 -0
- package/package.json +14 -10
- package/dist/tests/db/driver-parity.integration.test.d.ts +0 -16
- package/dist/tests/db/driver-parity.integration.test.d.ts.map +0 -1
- package/dist/tests/db/driver-parity.integration.test.js +0 -555
- package/dist/tests/db/driver-parity.integration.test.js.map +0 -1
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2269: Quarantine Service with Authentication
|
|
3
|
+
*
|
|
4
|
+
* Service layer for quarantine operations that enforces authentication
|
|
5
|
+
* and authorization. Wraps QuarantineRepository with security controls.
|
|
6
|
+
*
|
|
7
|
+
* VP Engineering Guidance:
|
|
8
|
+
* - Auth belongs in service/handler layer, not repository
|
|
9
|
+
* - Repositories should be pure data access
|
|
10
|
+
*
|
|
11
|
+
* Security Controls:
|
|
12
|
+
* - QUA-002: Requires authenticated session for review operations
|
|
13
|
+
* - Enforces security_reviewer permission for review access
|
|
14
|
+
* - Multi-approval workflow for MALICIOUS severity
|
|
15
|
+
* - Audit logs include verified reviewer identity
|
|
16
|
+
*
|
|
17
|
+
* @module @skillsmith/core/services/quarantine/QuarantineService
|
|
18
|
+
*/
|
|
19
|
+
import { QuarantineServiceError, requirePermission } from './types.js';
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Configuration
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Number of approvals required for MALICIOUS severity reviews
|
|
25
|
+
*/
|
|
26
|
+
const MALICIOUS_APPROVAL_COUNT = 2;
|
|
27
|
+
/**
|
|
28
|
+
* Multi-approval timeout in milliseconds (24 hours)
|
|
29
|
+
*/
|
|
30
|
+
const MULTI_APPROVAL_TIMEOUT_MS = 24 * 60 * 60 * 1000;
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Service Implementation
|
|
33
|
+
// ============================================================================
|
|
34
|
+
/**
|
|
35
|
+
* Quarantine Service with Authentication
|
|
36
|
+
*
|
|
37
|
+
* Provides authenticated access to quarantine operations with:
|
|
38
|
+
* - Session validation
|
|
39
|
+
* - Permission checks (security_reviewer role)
|
|
40
|
+
* - Multi-approval workflow for MALICIOUS severity
|
|
41
|
+
* - Audit logging with verified identities
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const service = new QuarantineService(repository, auditLogger)
|
|
46
|
+
*
|
|
47
|
+
* // Review a quarantined skill (requires authentication)
|
|
48
|
+
* const result = await service.review(
|
|
49
|
+
* session,
|
|
50
|
+
* quarantineId,
|
|
51
|
+
* { reviewStatus: 'approved', reviewNotes: 'Verified safe' }
|
|
52
|
+
* )
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export class QuarantineService {
|
|
56
|
+
repository;
|
|
57
|
+
auditLogger;
|
|
58
|
+
/**
|
|
59
|
+
* In-memory store for pending multi-approvals
|
|
60
|
+
* Key: quarantineId, Value: MultiApprovalStatus
|
|
61
|
+
*
|
|
62
|
+
* Note: In production, this should be persisted to database
|
|
63
|
+
*/
|
|
64
|
+
pendingApprovals = new Map();
|
|
65
|
+
constructor(repository, auditLogger) {
|
|
66
|
+
this.repository = repository;
|
|
67
|
+
this.auditLogger = auditLogger;
|
|
68
|
+
}
|
|
69
|
+
// ==========================================================================
|
|
70
|
+
// Read Operations (require quarantine:read permission)
|
|
71
|
+
// ==========================================================================
|
|
72
|
+
/**
|
|
73
|
+
* Find a quarantine entry by ID
|
|
74
|
+
*
|
|
75
|
+
* @param session - Authenticated session
|
|
76
|
+
* @param id - Quarantine entry ID
|
|
77
|
+
* @returns Quarantine entry or null
|
|
78
|
+
*/
|
|
79
|
+
findById(session, id) {
|
|
80
|
+
requirePermission(session, 'quarantine:read');
|
|
81
|
+
return this.repository.findById(id);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Find quarantine entries for a skill
|
|
85
|
+
*
|
|
86
|
+
* @param session - Authenticated session
|
|
87
|
+
* @param skillId - Skill ID
|
|
88
|
+
* @returns Array of quarantine entries
|
|
89
|
+
*/
|
|
90
|
+
findBySkillId(session, skillId) {
|
|
91
|
+
requirePermission(session, 'quarantine:read');
|
|
92
|
+
return this.repository.findBySkillId(skillId);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Find all quarantine entries with optional filtering
|
|
96
|
+
*
|
|
97
|
+
* @param session - Authenticated session
|
|
98
|
+
* @param filter - Query filters
|
|
99
|
+
* @returns Paginated quarantine results
|
|
100
|
+
*/
|
|
101
|
+
findAll(session, filter) {
|
|
102
|
+
requirePermission(session, 'quarantine:read');
|
|
103
|
+
return this.repository.findAll(filter);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get quarantine statistics
|
|
107
|
+
*
|
|
108
|
+
* @param session - Authenticated session
|
|
109
|
+
* @returns Quarantine statistics
|
|
110
|
+
*/
|
|
111
|
+
getStats(session) {
|
|
112
|
+
requirePermission(session, 'quarantine:read');
|
|
113
|
+
return this.repository.getStats();
|
|
114
|
+
}
|
|
115
|
+
// ==========================================================================
|
|
116
|
+
// Review Operations (require quarantine:review permission)
|
|
117
|
+
// ==========================================================================
|
|
118
|
+
/**
|
|
119
|
+
* Review a quarantine entry with authentication
|
|
120
|
+
*
|
|
121
|
+
* This is the secure replacement for QuarantineRepository.review().
|
|
122
|
+
* It enforces:
|
|
123
|
+
* - Valid authenticated session
|
|
124
|
+
* - security_reviewer permission (quarantine:review)
|
|
125
|
+
* - Multi-approval for MALICIOUS severity (quarantine:review_malicious)
|
|
126
|
+
* - Audit logging with verified reviewer identity
|
|
127
|
+
*
|
|
128
|
+
* @param session - Authenticated session (verified by auth layer)
|
|
129
|
+
* @param quarantineId - Quarantine entry ID to review
|
|
130
|
+
* @param input - Review decision and notes
|
|
131
|
+
* @returns Review result with verified reviewer identity
|
|
132
|
+
* @throws QuarantineServiceError on auth/permission failure
|
|
133
|
+
*/
|
|
134
|
+
review(session, quarantineId, input) {
|
|
135
|
+
// Validate session and require review permission
|
|
136
|
+
requirePermission(session, 'quarantine:review');
|
|
137
|
+
// Get the quarantine entry
|
|
138
|
+
const entry = this.repository.findById(quarantineId);
|
|
139
|
+
if (!entry) {
|
|
140
|
+
throw new QuarantineServiceError(`Quarantine entry not found: ${quarantineId}`, 'NOT_FOUND', {
|
|
141
|
+
quarantineId,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// Check if already reviewed
|
|
145
|
+
if (entry.reviewStatus !== 'pending') {
|
|
146
|
+
throw new QuarantineServiceError(`Quarantine entry already reviewed: ${entry.reviewStatus}`, 'ALREADY_REVIEWED', { quarantineId, currentStatus: entry.reviewStatus });
|
|
147
|
+
}
|
|
148
|
+
// For MALICIOUS severity, require multi-approval workflow
|
|
149
|
+
if (entry.severity === 'MALICIOUS' && input.reviewStatus === 'approved') {
|
|
150
|
+
return this.handleMaliciousApproval(session, quarantineId, entry.skillId, input);
|
|
151
|
+
}
|
|
152
|
+
// For MALICIOUS severity rejection or non-MALICIOUS, check elevated permission
|
|
153
|
+
if (entry.severity === 'MALICIOUS') {
|
|
154
|
+
requirePermission(session, 'quarantine:review_malicious');
|
|
155
|
+
}
|
|
156
|
+
// Perform the review with verified identity
|
|
157
|
+
const reviewResult = this.repository.review(quarantineId, {
|
|
158
|
+
reviewedBy: session.email, // Verified email from session
|
|
159
|
+
reviewStatus: input.reviewStatus,
|
|
160
|
+
reviewNotes: input.reviewNotes,
|
|
161
|
+
});
|
|
162
|
+
if (!reviewResult) {
|
|
163
|
+
throw new QuarantineServiceError('Failed to review quarantine entry', 'INVALID_INPUT', {
|
|
164
|
+
quarantineId,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Log audit event with full session details
|
|
168
|
+
this.auditLogger.log({
|
|
169
|
+
event_type: 'quarantine_authenticated_review',
|
|
170
|
+
actor: 'reviewer',
|
|
171
|
+
resource: entry.skillId,
|
|
172
|
+
action: 'review',
|
|
173
|
+
result: 'success',
|
|
174
|
+
metadata: {
|
|
175
|
+
quarantineId,
|
|
176
|
+
reviewStatus: input.reviewStatus,
|
|
177
|
+
reviewer: {
|
|
178
|
+
userId: session.userId,
|
|
179
|
+
email: session.email,
|
|
180
|
+
displayName: session.displayName,
|
|
181
|
+
},
|
|
182
|
+
sessionId: session.sessionId,
|
|
183
|
+
severity: entry.severity,
|
|
184
|
+
canImport: reviewResult.canImport,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
success: true,
|
|
189
|
+
approved: reviewResult.approved,
|
|
190
|
+
skillId: reviewResult.skillId,
|
|
191
|
+
severity: reviewResult.severity,
|
|
192
|
+
canImport: reviewResult.canImport,
|
|
193
|
+
warnings: reviewResult.warnings,
|
|
194
|
+
reviewedBy: {
|
|
195
|
+
userId: session.userId,
|
|
196
|
+
email: session.email,
|
|
197
|
+
displayName: session.displayName,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// ==========================================================================
|
|
202
|
+
// Multi-Approval Workflow (MALICIOUS Severity)
|
|
203
|
+
// ==========================================================================
|
|
204
|
+
/**
|
|
205
|
+
* Handle approval for MALICIOUS severity skills
|
|
206
|
+
*
|
|
207
|
+
* MALICIOUS severity requires multiple reviewers to approve
|
|
208
|
+
* before a skill can be unquarantined. This prevents single
|
|
209
|
+
* reviewer compromise from allowing malicious skills.
|
|
210
|
+
*
|
|
211
|
+
* @param session - Authenticated session
|
|
212
|
+
* @param quarantineId - Quarantine entry ID
|
|
213
|
+
* @param skillId - Skill ID
|
|
214
|
+
* @param input - Review input
|
|
215
|
+
* @returns Review result with multi-approval status
|
|
216
|
+
*/
|
|
217
|
+
handleMaliciousApproval(session, quarantineId, skillId, input) {
|
|
218
|
+
// Require elevated permission for MALICIOUS review
|
|
219
|
+
requirePermission(session, 'quarantine:review_malicious');
|
|
220
|
+
// Get or create pending approval status
|
|
221
|
+
let approvalStatus = this.pendingApprovals.get(quarantineId);
|
|
222
|
+
if (!approvalStatus) {
|
|
223
|
+
// Start new multi-approval workflow
|
|
224
|
+
approvalStatus = {
|
|
225
|
+
quarantineId,
|
|
226
|
+
requiredApprovals: MALICIOUS_APPROVAL_COUNT,
|
|
227
|
+
currentApprovals: [],
|
|
228
|
+
isComplete: false,
|
|
229
|
+
startedAt: new Date(),
|
|
230
|
+
};
|
|
231
|
+
this.pendingApprovals.set(quarantineId, approvalStatus);
|
|
232
|
+
}
|
|
233
|
+
// Check if this reviewer already approved
|
|
234
|
+
const existingApproval = approvalStatus.currentApprovals.find((a) => a.reviewerId === session.userId);
|
|
235
|
+
if (existingApproval) {
|
|
236
|
+
throw new QuarantineServiceError('You have already approved this entry', 'ALREADY_REVIEWED', {
|
|
237
|
+
quarantineId,
|
|
238
|
+
previousApprovalAt: existingApproval.approvedAt.toISOString(),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// Check for approval timeout
|
|
242
|
+
const timeSinceStart = Date.now() - approvalStatus.startedAt.getTime();
|
|
243
|
+
if (timeSinceStart > MULTI_APPROVAL_TIMEOUT_MS) {
|
|
244
|
+
// Reset approval workflow
|
|
245
|
+
this.pendingApprovals.delete(quarantineId);
|
|
246
|
+
throw new QuarantineServiceError('Multi-approval workflow timed out. Please start again.', 'INVALID_INPUT', { quarantineId, timeoutMs: MULTI_APPROVAL_TIMEOUT_MS });
|
|
247
|
+
}
|
|
248
|
+
// Add this approval
|
|
249
|
+
const approval = {
|
|
250
|
+
reviewerId: session.userId,
|
|
251
|
+
reviewerEmail: session.email,
|
|
252
|
+
approvedAt: new Date(),
|
|
253
|
+
notes: input.reviewNotes,
|
|
254
|
+
};
|
|
255
|
+
approvalStatus.currentApprovals.push(approval);
|
|
256
|
+
// Log the approval
|
|
257
|
+
this.auditLogger.log({
|
|
258
|
+
event_type: 'quarantine_multi_approval',
|
|
259
|
+
actor: 'reviewer',
|
|
260
|
+
resource: skillId,
|
|
261
|
+
action: 'approve',
|
|
262
|
+
result: 'success',
|
|
263
|
+
metadata: {
|
|
264
|
+
quarantineId,
|
|
265
|
+
approvalNumber: approvalStatus.currentApprovals.length,
|
|
266
|
+
requiredApprovals: approvalStatus.requiredApprovals,
|
|
267
|
+
reviewer: {
|
|
268
|
+
userId: session.userId,
|
|
269
|
+
email: session.email,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
// Check if we have enough approvals
|
|
274
|
+
if (approvalStatus.currentApprovals.length >= approvalStatus.requiredApprovals) {
|
|
275
|
+
// Complete the approval workflow
|
|
276
|
+
approvalStatus.isComplete = true;
|
|
277
|
+
approvalStatus.completedAt = new Date();
|
|
278
|
+
// Perform the actual review
|
|
279
|
+
const reviewResult = this.repository.review(quarantineId, {
|
|
280
|
+
reviewedBy: approvalStatus.currentApprovals.map((a) => a.reviewerEmail).join(', '),
|
|
281
|
+
reviewStatus: 'approved',
|
|
282
|
+
reviewNotes: `Multi-approval complete: ${approvalStatus.currentApprovals.length} reviewers approved. ${input.reviewNotes || ''}`,
|
|
283
|
+
});
|
|
284
|
+
// Clean up pending approval
|
|
285
|
+
this.pendingApprovals.delete(quarantineId);
|
|
286
|
+
// Log completion
|
|
287
|
+
this.auditLogger.log({
|
|
288
|
+
event_type: 'quarantine_multi_approval_complete',
|
|
289
|
+
actor: 'reviewer',
|
|
290
|
+
resource: skillId,
|
|
291
|
+
action: 'complete',
|
|
292
|
+
result: 'success',
|
|
293
|
+
metadata: {
|
|
294
|
+
quarantineId,
|
|
295
|
+
approvals: approvalStatus.currentApprovals.map((a) => ({
|
|
296
|
+
reviewerId: a.reviewerId,
|
|
297
|
+
email: a.reviewerEmail,
|
|
298
|
+
approvedAt: a.approvedAt.toISOString(),
|
|
299
|
+
})),
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
approved: true,
|
|
305
|
+
skillId,
|
|
306
|
+
severity: 'MALICIOUS',
|
|
307
|
+
canImport: reviewResult?.canImport ?? false,
|
|
308
|
+
warnings: [
|
|
309
|
+
'MALICIOUS skill approved through multi-approval workflow',
|
|
310
|
+
`Approved by: ${approvalStatus.currentApprovals.map((a) => a.reviewerEmail).join(', ')}`,
|
|
311
|
+
],
|
|
312
|
+
reviewedBy: {
|
|
313
|
+
userId: session.userId,
|
|
314
|
+
email: session.email,
|
|
315
|
+
displayName: session.displayName,
|
|
316
|
+
},
|
|
317
|
+
multiApprovalStatus: approvalStatus,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// Need more approvals
|
|
321
|
+
return {
|
|
322
|
+
success: true,
|
|
323
|
+
approved: false,
|
|
324
|
+
skillId,
|
|
325
|
+
severity: 'MALICIOUS',
|
|
326
|
+
canImport: false,
|
|
327
|
+
warnings: [
|
|
328
|
+
`Multi-approval in progress: ${approvalStatus.currentApprovals.length}/${approvalStatus.requiredApprovals} approvals received`,
|
|
329
|
+
`Requires ${approvalStatus.requiredApprovals - approvalStatus.currentApprovals.length} more approval(s)`,
|
|
330
|
+
],
|
|
331
|
+
reviewedBy: {
|
|
332
|
+
userId: session.userId,
|
|
333
|
+
email: session.email,
|
|
334
|
+
displayName: session.displayName,
|
|
335
|
+
},
|
|
336
|
+
multiApprovalStatus: approvalStatus,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get pending multi-approval status for a quarantine entry
|
|
341
|
+
*
|
|
342
|
+
* @param session - Authenticated session
|
|
343
|
+
* @param quarantineId - Quarantine entry ID
|
|
344
|
+
* @returns Multi-approval status or null
|
|
345
|
+
*/
|
|
346
|
+
getMultiApprovalStatus(session, quarantineId) {
|
|
347
|
+
requirePermission(session, 'quarantine:read');
|
|
348
|
+
return this.pendingApprovals.get(quarantineId) ?? null;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Cancel a pending multi-approval workflow
|
|
352
|
+
*
|
|
353
|
+
* @param session - Authenticated session (requires admin)
|
|
354
|
+
* @param quarantineId - Quarantine entry ID
|
|
355
|
+
* @returns Whether the cancellation was successful
|
|
356
|
+
*/
|
|
357
|
+
cancelMultiApproval(session, quarantineId) {
|
|
358
|
+
requirePermission(session, 'quarantine:admin');
|
|
359
|
+
const status = this.pendingApprovals.get(quarantineId);
|
|
360
|
+
if (!status) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
this.pendingApprovals.delete(quarantineId);
|
|
364
|
+
this.auditLogger.log({
|
|
365
|
+
event_type: 'quarantine_multi_approval_cancelled',
|
|
366
|
+
actor: 'reviewer',
|
|
367
|
+
resource: quarantineId,
|
|
368
|
+
action: 'cancel',
|
|
369
|
+
result: 'success',
|
|
370
|
+
metadata: {
|
|
371
|
+
quarantineId,
|
|
372
|
+
cancelledBy: session.email,
|
|
373
|
+
pendingApprovals: status.currentApprovals.length,
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
// ==========================================================================
|
|
379
|
+
// Admin Operations (require quarantine:admin permission)
|
|
380
|
+
// ==========================================================================
|
|
381
|
+
/**
|
|
382
|
+
* Create a quarantine entry (admin only)
|
|
383
|
+
*
|
|
384
|
+
* @param session - Authenticated session
|
|
385
|
+
* @param input - Quarantine creation input
|
|
386
|
+
* @returns Created quarantine entry
|
|
387
|
+
*/
|
|
388
|
+
create(session, input) {
|
|
389
|
+
requirePermission(session, 'quarantine:create');
|
|
390
|
+
return this.repository.create(input);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Delete a quarantine entry (admin only)
|
|
394
|
+
*
|
|
395
|
+
* @param session - Authenticated session
|
|
396
|
+
* @param id - Quarantine entry ID
|
|
397
|
+
* @returns Whether the entry was deleted
|
|
398
|
+
*/
|
|
399
|
+
delete(session, id) {
|
|
400
|
+
requirePermission(session, 'quarantine:delete');
|
|
401
|
+
// Cancel any pending multi-approval
|
|
402
|
+
this.pendingApprovals.delete(id);
|
|
403
|
+
return this.repository.delete(id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=QuarantineService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuarantineService.js","sourceRoot":"","sources":["../../../../src/services/quarantine/QuarantineService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2269: Quarantine Service Module
|
|
3
|
+
*
|
|
4
|
+
* Authenticated quarantine operations with multi-approval workflow.
|
|
5
|
+
*
|
|
6
|
+
* @module @skillsmith/core/services/quarantine
|
|
7
|
+
*/
|
|
8
|
+
export { QuarantineService } from './QuarantineService.js';
|
|
9
|
+
export { type QuarantinePermission, type AuthenticatedSession, type ApprovalRecord, type MultiApprovalStatus, type AuthenticatedReviewInput, type AuthenticatedReviewResult, type QuarantineServiceErrorCode, QuarantineServiceError, hasPermission, isSessionValid, requirePermission, } from './types.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/services/quarantine/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAE/B,sBAAsB,EAEtB,aAAa,EACb,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2269: Quarantine Service Module
|
|
3
|
+
*
|
|
4
|
+
* Authenticated quarantine operations with multi-approval workflow.
|
|
5
|
+
*
|
|
6
|
+
* @module @skillsmith/core/services/quarantine
|
|
7
|
+
*/
|
|
8
|
+
export { QuarantineService } from './QuarantineService.js';
|
|
9
|
+
export {
|
|
10
|
+
// Error class
|
|
11
|
+
QuarantineServiceError,
|
|
12
|
+
// Helper functions
|
|
13
|
+
hasPermission, isSessionValid, requirePermission, } from './types.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/quarantine/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO;AASL,cAAc;AACd,sBAAsB;AACtB,mBAAmB;AACnB,aAAa,EACb,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2269: Quarantine Service Types
|
|
3
|
+
*
|
|
4
|
+
* Authentication and authorization types for quarantine review operations.
|
|
5
|
+
* Part of QUA-002 security fix: No authentication on quarantine review.
|
|
6
|
+
*
|
|
7
|
+
* @module @skillsmith/core/services/quarantine/types
|
|
8
|
+
*/
|
|
9
|
+
import type { QuarantineSeverity, QuarantineReviewStatus } from '../../db/quarantine-schema.js';
|
|
10
|
+
/**
|
|
11
|
+
* User permissions for quarantine operations
|
|
12
|
+
*/
|
|
13
|
+
export type QuarantinePermission = 'quarantine:read' | 'quarantine:create' | 'quarantine:review' | 'quarantine:review_malicious' | 'quarantine:delete' | 'quarantine:admin';
|
|
14
|
+
/**
|
|
15
|
+
* Authenticated session for quarantine operations
|
|
16
|
+
*
|
|
17
|
+
* All quarantine review operations require a valid session with
|
|
18
|
+
* appropriate permissions. This ensures audit trails contain
|
|
19
|
+
* verified reviewer identities.
|
|
20
|
+
*/
|
|
21
|
+
export interface AuthenticatedSession {
|
|
22
|
+
/** Unique user identifier (from auth provider) */
|
|
23
|
+
userId: string;
|
|
24
|
+
/** User's email address (verified) */
|
|
25
|
+
email: string;
|
|
26
|
+
/** User's display name */
|
|
27
|
+
displayName: string;
|
|
28
|
+
/** User's assigned permissions */
|
|
29
|
+
permissions: QuarantinePermission[];
|
|
30
|
+
/** Session token ID for audit logging */
|
|
31
|
+
sessionId: string;
|
|
32
|
+
/** Session expiration timestamp */
|
|
33
|
+
expiresAt: Date;
|
|
34
|
+
/** Organization/team ID (for team-based permissions) */
|
|
35
|
+
organizationId?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Approval record for multi-approval workflow
|
|
39
|
+
*/
|
|
40
|
+
export interface ApprovalRecord {
|
|
41
|
+
/** Reviewer user ID */
|
|
42
|
+
reviewerId: string;
|
|
43
|
+
/** Reviewer email */
|
|
44
|
+
reviewerEmail: string;
|
|
45
|
+
/** Timestamp of approval */
|
|
46
|
+
approvedAt: Date;
|
|
47
|
+
/** Optional notes from reviewer */
|
|
48
|
+
notes?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Multi-approval status for MALICIOUS severity reviews
|
|
52
|
+
*/
|
|
53
|
+
export interface MultiApprovalStatus {
|
|
54
|
+
/** Quarantine entry ID */
|
|
55
|
+
quarantineId: string;
|
|
56
|
+
/** Required number of approvals */
|
|
57
|
+
requiredApprovals: number;
|
|
58
|
+
/** Current approvals received */
|
|
59
|
+
currentApprovals: ApprovalRecord[];
|
|
60
|
+
/** Whether all required approvals have been received */
|
|
61
|
+
isComplete: boolean;
|
|
62
|
+
/** Timestamp when first approval was received */
|
|
63
|
+
startedAt: Date;
|
|
64
|
+
/** Timestamp when approval completed (if complete) */
|
|
65
|
+
completedAt?: Date;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Input for authenticated review operation
|
|
69
|
+
*/
|
|
70
|
+
export interface AuthenticatedReviewInput {
|
|
71
|
+
/** Review status decision */
|
|
72
|
+
reviewStatus: QuarantineReviewStatus;
|
|
73
|
+
/** Optional review notes */
|
|
74
|
+
reviewNotes?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Result from authenticated review operation
|
|
78
|
+
*/
|
|
79
|
+
export interface AuthenticatedReviewResult {
|
|
80
|
+
/** Whether the review was successful */
|
|
81
|
+
success: boolean;
|
|
82
|
+
/** Whether the skill is approved for import */
|
|
83
|
+
approved: boolean;
|
|
84
|
+
/** Skill ID that was reviewed */
|
|
85
|
+
skillId: string;
|
|
86
|
+
/** Severity of the quarantined skill */
|
|
87
|
+
severity: QuarantineSeverity;
|
|
88
|
+
/** Whether the skill can be imported */
|
|
89
|
+
canImport: boolean;
|
|
90
|
+
/** Any warnings about the skill */
|
|
91
|
+
warnings: string[];
|
|
92
|
+
/** Verified reviewer identity */
|
|
93
|
+
reviewedBy: {
|
|
94
|
+
userId: string;
|
|
95
|
+
email: string;
|
|
96
|
+
displayName: string;
|
|
97
|
+
};
|
|
98
|
+
/** For MALICIOUS severity: multi-approval status */
|
|
99
|
+
multiApprovalStatus?: MultiApprovalStatus;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Error codes for quarantine service operations
|
|
103
|
+
*/
|
|
104
|
+
export type QuarantineServiceErrorCode = 'UNAUTHORIZED' | 'FORBIDDEN' | 'SESSION_EXPIRED' | 'INSUFFICIENT_PERMISSIONS' | 'MULTI_APPROVAL_REQUIRED' | 'ALREADY_REVIEWED' | 'NOT_FOUND' | 'INVALID_INPUT';
|
|
105
|
+
/**
|
|
106
|
+
* Service error with typed error codes
|
|
107
|
+
*/
|
|
108
|
+
export declare class QuarantineServiceError extends Error {
|
|
109
|
+
readonly code: QuarantineServiceErrorCode;
|
|
110
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
111
|
+
constructor(message: string, code: QuarantineServiceErrorCode, details?: Record<string, unknown> | undefined);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if session has a specific permission
|
|
115
|
+
*/
|
|
116
|
+
export declare function hasPermission(session: AuthenticatedSession, permission: QuarantinePermission): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Check if session is valid (not expired)
|
|
119
|
+
*/
|
|
120
|
+
export declare function isSessionValid(session: AuthenticatedSession): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Validate session and check permission
|
|
123
|
+
*
|
|
124
|
+
* @throws QuarantineServiceError if session is invalid or lacks permission
|
|
125
|
+
*/
|
|
126
|
+
export declare function requirePermission(session: AuthenticatedSession, permission: QuarantinePermission): void;
|
|
127
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/services/quarantine/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2269: Quarantine Service Types
|
|
3
|
+
*
|
|
4
|
+
* Authentication and authorization types for quarantine review operations.
|
|
5
|
+
* Part of QUA-002 security fix: No authentication on quarantine review.
|
|
6
|
+
*
|
|
7
|
+
* @module @skillsmith/core/services/quarantine/types
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Service error with typed error codes
|
|
11
|
+
*/
|
|
12
|
+
export class QuarantineServiceError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
details;
|
|
15
|
+
constructor(message, code, details) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.details = details;
|
|
19
|
+
this.name = 'QuarantineServiceError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Permission Helpers
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Check if session has a specific permission
|
|
27
|
+
*/
|
|
28
|
+
export function hasPermission(session, permission) {
|
|
29
|
+
// Admin has all permissions
|
|
30
|
+
if (session.permissions.includes('quarantine:admin')) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return session.permissions.includes(permission);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if session is valid (not expired)
|
|
37
|
+
*/
|
|
38
|
+
export function isSessionValid(session) {
|
|
39
|
+
return session.expiresAt > new Date();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate session and check permission
|
|
43
|
+
*
|
|
44
|
+
* @throws QuarantineServiceError if session is invalid or lacks permission
|
|
45
|
+
*/
|
|
46
|
+
export function requirePermission(session, permission) {
|
|
47
|
+
if (!isSessionValid(session)) {
|
|
48
|
+
throw new QuarantineServiceError('Session has expired', 'SESSION_EXPIRED', {
|
|
49
|
+
expiresAt: session.expiresAt.toISOString(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (!hasPermission(session, permission)) {
|
|
53
|
+
throw new QuarantineServiceError(`Permission denied: ${permission} required`, 'INSUFFICIENT_PERMISSIONS', {
|
|
54
|
+
required: permission,
|
|
55
|
+
available: session.permissions,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/services/quarantine/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8JH;;GAEG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAG7B;IACA;IAHlB,YACE,OAAe,EACC,IAAgC,EAChC,OAAiC;QAEjD,KAAK,CAAC,OAAO,CAAC,CAAA;QAHE,SAAI,GAAJ,IAAI,CAA4B;QAChC,YAAO,GAAP,OAAO,CAA0B;QAGjD,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAA;IACtC,CAAC;CACF;AAED,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,OAA6B,EAC7B,UAAgC;IAEhC,4BAA4B;IAC5B,IAAI,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACrD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;AACjD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAA6B;IAC1D,OAAO,OAAO,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAA;AACvC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAA6B,EAC7B,UAAgC;IAEhC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,sBAAsB,CAAC,qBAAqB,EAAE,iBAAiB,EAAE;YACzE,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE;SAC3C,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,sBAAsB,CAC9B,sBAAsB,UAAU,WAAW,EAC3C,0BAA0B,EAC1B;YACE,QAAQ,EAAE,UAAU;YACpB,SAAS,EAAE,OAAO,CAAC,WAAW;SAC/B,CACF,CAAA;IACH,CAAC;AACH,CAAC"}
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* SMI-1809: Added 'local' for local skills from ~/.claude/skills/
|
|
6
|
+
*
|
|
7
|
+
* NOTE: 'local' is a client-only tier for skills discovered on disk.
|
|
8
|
+
* It is NOT stored in the database — the skills table CHECK constraint
|
|
9
|
+
* only allows: verified, curated, community, experimental, unknown.
|
|
10
|
+
* Never pass 'local' to database upsert operations.
|
|
6
11
|
*/
|
|
7
|
-
export type TrustTier = 'verified' | 'community' | 'experimental' | 'unknown' | 'local';
|
|
12
|
+
export type TrustTier = 'verified' | 'curated' | 'community' | 'experimental' | 'unknown' | 'local';
|
|
8
13
|
/**
|
|
9
14
|
* SMI-1631: Skill roles for role-based recommendations
|
|
10
15
|
* Used to filter and prioritize skills based on their primary purpose
|