@skillsmith/core 0.4.9 → 0.4.11

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 (197) hide show
  1. package/README.md +1 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/analysis/__tests__/incremental.test.d.ts +1 -1
  4. package/dist/src/analysis/__tests__/incremental.test.js +1 -1
  5. package/dist/src/analysis/__tests__/integration.test.d.ts +1 -1
  6. package/dist/src/analysis/__tests__/integration.test.js +1 -1
  7. package/dist/src/analysis/__tests__/performance.test.d.ts +1 -1
  8. package/dist/src/analysis/__tests__/performance.test.js +1 -1
  9. package/dist/src/analysis/adapters/__tests__/python.test.d.ts +1 -1
  10. package/dist/src/analysis/adapters/__tests__/python.test.js +1 -1
  11. package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts +1 -1
  12. package/dist/src/analysis/adapters/__tests__/typescript.test.js +1 -1
  13. package/dist/src/analysis/adapters/base.d.ts +1 -1
  14. package/dist/src/analysis/adapters/base.js +1 -1
  15. package/dist/src/analysis/adapters/factory.d.ts +1 -1
  16. package/dist/src/analysis/adapters/factory.js +1 -1
  17. package/dist/src/analysis/adapters/go.d.ts +1 -1
  18. package/dist/src/analysis/adapters/go.js +1 -1
  19. package/dist/src/analysis/adapters/index.d.ts +1 -1
  20. package/dist/src/analysis/adapters/index.js +1 -1
  21. package/dist/src/analysis/adapters/java-parsers.d.ts +1 -1
  22. package/dist/src/analysis/adapters/java-parsers.d.ts.map +1 -1
  23. package/dist/src/analysis/adapters/java-parsers.js +10 -3
  24. package/dist/src/analysis/adapters/java-parsers.js.map +1 -1
  25. package/dist/src/analysis/adapters/java.d.ts +1 -1
  26. package/dist/src/analysis/adapters/java.js +1 -1
  27. package/dist/src/analysis/adapters/python-frameworks.d.ts +1 -1
  28. package/dist/src/analysis/adapters/python-frameworks.js +1 -1
  29. package/dist/src/analysis/adapters/python.d.ts +1 -1
  30. package/dist/src/analysis/adapters/python.js +1 -1
  31. package/dist/src/analysis/adapters/rust-parsers.d.ts +1 -1
  32. package/dist/src/analysis/adapters/rust-parsers.js +1 -1
  33. package/dist/src/analysis/adapters/rust.d.ts +1 -1
  34. package/dist/src/analysis/adapters/rust.js +1 -1
  35. package/dist/src/analysis/adapters/typescript.d.ts +1 -1
  36. package/dist/src/analysis/adapters/typescript.js +1 -1
  37. package/dist/src/analysis/aggregator.d.ts +1 -1
  38. package/dist/src/analysis/aggregator.js +1 -1
  39. package/dist/src/analysis/cache.d.ts +1 -1
  40. package/dist/src/analysis/cache.js +1 -1
  41. package/dist/src/analysis/file-streamer.d.ts +1 -1
  42. package/dist/src/analysis/file-streamer.js +1 -1
  43. package/dist/src/analysis/incremental-parser.d.ts +1 -1
  44. package/dist/src/analysis/incremental-parser.js +1 -1
  45. package/dist/src/analysis/incremental.d.ts +1 -1
  46. package/dist/src/analysis/incremental.js +1 -1
  47. package/dist/src/analysis/index.d.ts +1 -1
  48. package/dist/src/analysis/index.js +1 -1
  49. package/dist/src/analysis/language-detector.d.ts +1 -1
  50. package/dist/src/analysis/language-detector.js +1 -1
  51. package/dist/src/analysis/memory-monitor.d.ts +1 -1
  52. package/dist/src/analysis/memory-monitor.js +1 -1
  53. package/dist/src/analysis/metrics.d.ts +1 -1
  54. package/dist/src/analysis/metrics.js +1 -1
  55. package/dist/src/analysis/router.d.ts +1 -1
  56. package/dist/src/analysis/router.js +1 -1
  57. package/dist/src/analysis/tree-cache.d.ts +1 -1
  58. package/dist/src/analysis/tree-cache.js +1 -1
  59. package/dist/src/analysis/tree-sitter/manager.d.ts +1 -1
  60. package/dist/src/analysis/tree-sitter/manager.js +1 -1
  61. package/dist/src/analysis/types.d.ts +1 -1
  62. package/dist/src/analysis/types.js +1 -1
  63. package/dist/src/analysis/worker-pool.d.ts +1 -1
  64. package/dist/src/analysis/worker-pool.js +1 -1
  65. package/dist/src/analysis/worker-types.d.ts +1 -1
  66. package/dist/src/analysis/worker-types.js +1 -1
  67. package/dist/src/analysis/worker-utils.d.ts +1 -1
  68. package/dist/src/analysis/worker-utils.js +1 -1
  69. package/dist/src/api/client.d.ts +1 -0
  70. package/dist/src/api/client.d.ts.map +1 -1
  71. package/dist/src/api/client.js.map +1 -1
  72. package/dist/src/api/schemas.d.ts +11 -4
  73. package/dist/src/api/schemas.d.ts.map +1 -1
  74. package/dist/src/api/schemas.js +8 -1
  75. package/dist/src/api/schemas.js.map +1 -1
  76. package/dist/src/config/index.d.ts +49 -1
  77. package/dist/src/config/index.d.ts.map +1 -1
  78. package/dist/src/config/index.js +166 -3
  79. package/dist/src/config/index.js.map +1 -1
  80. package/dist/src/config/index.test.d.ts +11 -0
  81. package/dist/src/config/index.test.d.ts.map +1 -0
  82. package/dist/src/config/index.test.js +288 -0
  83. package/dist/src/config/index.test.js.map +1 -0
  84. package/dist/src/db/drivers/betterSqlite3Driver.d.ts.map +1 -1
  85. package/dist/src/db/drivers/betterSqlite3Driver.js +5 -3
  86. package/dist/src/db/drivers/betterSqlite3Driver.js.map +1 -1
  87. package/dist/src/db/quarantine-approvals-schema.d.ts +37 -0
  88. package/dist/src/db/quarantine-approvals-schema.d.ts.map +1 -0
  89. package/dist/src/db/quarantine-approvals-schema.js +71 -0
  90. package/dist/src/db/quarantine-approvals-schema.js.map +1 -0
  91. package/dist/src/exports/services.d.ts +1 -0
  92. package/dist/src/exports/services.d.ts.map +1 -1
  93. package/dist/src/exports/services.js +4 -0
  94. package/dist/src/exports/services.js.map +1 -1
  95. package/dist/src/index.d.ts +4 -1
  96. package/dist/src/index.d.ts.map +1 -1
  97. package/dist/src/index.js +8 -2
  98. package/dist/src/index.js.map +1 -1
  99. package/dist/src/indexer/SkillParser.d.ts +20 -0
  100. package/dist/src/indexer/SkillParser.d.ts.map +1 -1
  101. package/dist/src/indexer/SkillParser.js +58 -0
  102. package/dist/src/indexer/SkillParser.js.map +1 -1
  103. package/dist/src/repositories/quarantine/ApprovalRepository.d.ts +148 -0
  104. package/dist/src/repositories/quarantine/ApprovalRepository.d.ts.map +1 -0
  105. package/dist/src/repositories/quarantine/ApprovalRepository.js +212 -0
  106. package/dist/src/repositories/quarantine/ApprovalRepository.js.map +1 -0
  107. package/dist/src/repositories/quarantine/QuarantineRepository.d.ts.map +1 -1
  108. package/dist/src/repositories/quarantine/QuarantineRepository.js +4 -1
  109. package/dist/src/repositories/quarantine/QuarantineRepository.js.map +1 -1
  110. package/dist/src/repositories/quarantine/index.d.ts +2 -0
  111. package/dist/src/repositories/quarantine/index.d.ts.map +1 -1
  112. package/dist/src/repositories/quarantine/index.js +1 -0
  113. package/dist/src/repositories/quarantine/index.js.map +1 -1
  114. package/dist/src/scripts/validation/types.d.ts +2 -2
  115. package/dist/src/security/audit-types.d.ts +2 -2
  116. package/dist/src/security/audit-types.d.ts.map +1 -1
  117. package/dist/src/security/audit-types.js.map +1 -1
  118. package/dist/src/security/sanitization.d.ts.map +1 -1
  119. package/dist/src/security/sanitization.js +25 -17
  120. package/dist/src/security/sanitization.js.map +1 -1
  121. package/dist/src/security/scanner/SecurityScanner.formatters.js +1 -1
  122. package/dist/src/security/scanner/SecurityScanner.formatters.js.map +1 -1
  123. package/dist/src/services/index.d.ts +9 -0
  124. package/dist/src/services/index.d.ts.map +1 -0
  125. package/dist/src/services/index.js +10 -0
  126. package/dist/src/services/index.js.map +1 -0
  127. package/dist/src/services/quarantine/QuarantineService.d.ts +157 -0
  128. package/dist/src/services/quarantine/QuarantineService.d.ts.map +1 -0
  129. package/dist/src/services/quarantine/QuarantineService.js +464 -0
  130. package/dist/src/services/quarantine/QuarantineService.js.map +1 -0
  131. package/dist/src/services/quarantine/index.d.ts +10 -0
  132. package/dist/src/services/quarantine/index.d.ts.map +1 -0
  133. package/dist/src/services/quarantine/index.js +14 -0
  134. package/dist/src/services/quarantine/index.js.map +1 -0
  135. package/dist/src/services/quarantine/types.d.ts +127 -0
  136. package/dist/src/services/quarantine/types.d.ts.map +1 -0
  137. package/dist/src/services/quarantine/types.js +59 -0
  138. package/dist/src/services/quarantine/types.js.map +1 -0
  139. package/dist/src/types/skill.d.ts +6 -1
  140. package/dist/src/types/skill.d.ts.map +1 -1
  141. package/dist/src/types/skill.js.map +1 -1
  142. package/dist/src/types.d.ts +1 -1
  143. package/dist/src/types.d.ts.map +1 -1
  144. package/dist/src/utils/index.d.ts +1 -0
  145. package/dist/src/utils/index.d.ts.map +1 -1
  146. package/dist/src/utils/index.js +2 -0
  147. package/dist/src/utils/index.js.map +1 -1
  148. package/dist/src/utils/safe-fs.d.ts +63 -0
  149. package/dist/src/utils/safe-fs.d.ts.map +1 -0
  150. package/dist/src/utils/safe-fs.js +119 -0
  151. package/dist/src/utils/safe-fs.js.map +1 -0
  152. package/dist/src/validation/input-validators.d.ts.map +1 -1
  153. package/dist/src/validation/input-validators.js +11 -4
  154. package/dist/src/validation/input-validators.js.map +1 -1
  155. package/dist/tests/QuarantineRepository.test.js +39 -2
  156. package/dist/tests/QuarantineRepository.test.js.map +1 -1
  157. package/dist/tests/RawUrlSourceAdapter.security.test.js +2 -1
  158. package/dist/tests/RawUrlSourceAdapter.security.test.js.map +1 -1
  159. package/dist/tests/SecurityScanner.test.js +2 -2
  160. package/dist/tests/SecurityScanner.test.js.map +1 -1
  161. package/dist/tests/adapters-factory.test.d.ts +1 -1
  162. package/dist/tests/adapters-factory.test.js +1 -1
  163. package/dist/tests/edge-cases/EdgeCases.test.js +5 -2
  164. package/dist/tests/edge-cases/EdgeCases.test.js.map +1 -1
  165. package/dist/tests/integration/QuarantineService.test.d.ts +11 -0
  166. package/dist/tests/integration/QuarantineService.test.d.ts.map +1 -0
  167. package/dist/tests/integration/QuarantineService.test.js +426 -0
  168. package/dist/tests/integration/QuarantineService.test.js.map +1 -0
  169. package/dist/tests/integration/neural/e2e-learning.test.d.ts +1 -1
  170. package/dist/tests/integration/neural/e2e-learning.test.js +1 -1
  171. package/dist/tests/integration/neural/personalization.test.d.ts +1 -1
  172. package/dist/tests/integration/neural/personalization.test.js +1 -1
  173. package/dist/tests/integration/neural/preference-learner.test.d.ts +1 -1
  174. package/dist/tests/integration/neural/preference-learner.test.js +1 -1
  175. package/dist/tests/integration/neural/privacy.test.d.ts +1 -1
  176. package/dist/tests/integration/neural/privacy.test.js +1 -1
  177. package/dist/tests/integration/neural/signal-collection.test.d.ts +1 -1
  178. package/dist/tests/integration/neural/signal-collection.test.js +1 -1
  179. package/dist/tests/language-detector.test.d.ts +1 -1
  180. package/dist/tests/language-detector.test.js +1 -1
  181. package/dist/tests/unit/approval-repository.test.d.ts +9 -0
  182. package/dist/tests/unit/approval-repository.test.d.ts.map +1 -0
  183. package/dist/tests/unit/approval-repository.test.js +509 -0
  184. package/dist/tests/unit/approval-repository.test.js.map +1 -0
  185. package/dist/tests/unit/check-references.test.d.ts +2 -0
  186. package/dist/tests/unit/check-references.test.d.ts.map +1 -0
  187. package/dist/tests/unit/check-references.test.js +118 -0
  188. package/dist/tests/unit/check-references.test.js.map +1 -0
  189. package/dist/tests/utils/safe-fs.test.d.ts +12 -0
  190. package/dist/tests/utils/safe-fs.test.d.ts.map +1 -0
  191. package/dist/tests/utils/safe-fs.test.js +116 -0
  192. package/dist/tests/utils/safe-fs.test.js.map +1 -0
  193. package/package.json +14 -10
  194. package/dist/tests/db/driver-parity.integration.test.d.ts +0 -16
  195. package/dist/tests/db/driver-parity.integration.test.d.ts.map +0 -1
  196. package/dist/tests/db/driver-parity.integration.test.js +0 -555
  197. package/dist/tests/db/driver-parity.integration.test.js.map +0 -1
@@ -0,0 +1,464 @@
1
+ /**
2
+ * SMI-2269: Quarantine Service with Authentication
3
+ * SMI-2277: Persist multi-approval state to database
4
+ *
5
+ * Service layer for quarantine operations that enforces authentication
6
+ * and authorization. Wraps QuarantineRepository with security controls.
7
+ *
8
+ * VP Engineering Guidance:
9
+ * - Auth belongs in service/handler layer, not repository
10
+ * - Repositories should be pure data access
11
+ *
12
+ * Security Controls:
13
+ * - QUA-002: Requires authenticated session for review operations
14
+ * - Enforces security_reviewer permission for review access
15
+ * - Multi-approval workflow for MALICIOUS severity
16
+ * - Audit logs include verified reviewer identity
17
+ * - Approval state persisted to database (survives restarts)
18
+ *
19
+ * @module @skillsmith/core/services/quarantine/QuarantineService
20
+ */
21
+ import { QuarantineServiceError, requirePermission } from './types.js';
22
+ // ============================================================================
23
+ // Configuration
24
+ // ============================================================================
25
+ /**
26
+ * Number of approvals required for MALICIOUS severity reviews
27
+ */
28
+ const MALICIOUS_APPROVAL_COUNT = 2;
29
+ /**
30
+ * Multi-approval timeout in milliseconds (24 hours)
31
+ */
32
+ const MULTI_APPROVAL_TIMEOUT_MS = 24 * 60 * 60 * 1000;
33
+ // ============================================================================
34
+ // Service Implementation
35
+ // ============================================================================
36
+ /**
37
+ * Quarantine Service with Authentication
38
+ *
39
+ * Provides authenticated access to quarantine operations with:
40
+ * - Session validation
41
+ * - Permission checks (security_reviewer role)
42
+ * - Multi-approval workflow for MALICIOUS severity
43
+ * - Audit logging with verified identities
44
+ * - Database-persisted approval state (SMI-2277)
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const service = new QuarantineService(repository, approvalRepository, auditLogger)
49
+ *
50
+ * // Review a quarantined skill (requires authentication)
51
+ * const result = await service.review(
52
+ * session,
53
+ * quarantineId,
54
+ * { reviewStatus: 'approved', reviewNotes: 'Verified safe' }
55
+ * )
56
+ * ```
57
+ */
58
+ export class QuarantineService {
59
+ repository;
60
+ approvalRepository;
61
+ auditLogger;
62
+ constructor(repository, approvalRepository, auditLogger) {
63
+ this.repository = repository;
64
+ this.approvalRepository = approvalRepository;
65
+ this.auditLogger = auditLogger;
66
+ }
67
+ // ==========================================================================
68
+ // Read Operations (require quarantine:read permission)
69
+ // ==========================================================================
70
+ /**
71
+ * Find a quarantine entry by ID
72
+ *
73
+ * @param session - Authenticated session
74
+ * @param id - Quarantine entry ID
75
+ * @returns Quarantine entry or null
76
+ */
77
+ findById(session, id) {
78
+ requirePermission(session, 'quarantine:read');
79
+ return this.repository.findById(id);
80
+ }
81
+ /**
82
+ * Find quarantine entries for a skill
83
+ *
84
+ * @param session - Authenticated session
85
+ * @param skillId - Skill ID
86
+ * @returns Array of quarantine entries
87
+ */
88
+ findBySkillId(session, skillId) {
89
+ requirePermission(session, 'quarantine:read');
90
+ return this.repository.findBySkillId(skillId);
91
+ }
92
+ /**
93
+ * Find all quarantine entries with optional filtering
94
+ *
95
+ * @param session - Authenticated session
96
+ * @param filter - Query filters
97
+ * @returns Paginated quarantine results
98
+ */
99
+ findAll(session, filter) {
100
+ requirePermission(session, 'quarantine:read');
101
+ return this.repository.findAll(filter);
102
+ }
103
+ /**
104
+ * Get quarantine statistics
105
+ *
106
+ * @param session - Authenticated session
107
+ * @returns Quarantine statistics
108
+ */
109
+ getStats(session) {
110
+ requirePermission(session, 'quarantine:read');
111
+ return this.repository.getStats();
112
+ }
113
+ // ==========================================================================
114
+ // Review Operations (require quarantine:review permission)
115
+ // ==========================================================================
116
+ /**
117
+ * Review a quarantine entry with authentication
118
+ *
119
+ * This is the secure replacement for QuarantineRepository.review().
120
+ * It enforces:
121
+ * - Valid authenticated session
122
+ * - security_reviewer permission (quarantine:review)
123
+ * - Multi-approval for MALICIOUS severity (quarantine:review_malicious)
124
+ * - Audit logging with verified reviewer identity
125
+ *
126
+ * @param session - Authenticated session (verified by auth layer)
127
+ * @param quarantineId - Quarantine entry ID to review
128
+ * @param input - Review decision and notes
129
+ * @returns Review result with verified reviewer identity
130
+ * @throws QuarantineServiceError on auth/permission failure
131
+ */
132
+ review(session, quarantineId, input) {
133
+ // Validate session and require review permission
134
+ requirePermission(session, 'quarantine:review');
135
+ // Get the quarantine entry
136
+ const entry = this.repository.findById(quarantineId);
137
+ if (!entry) {
138
+ throw new QuarantineServiceError(`Quarantine entry not found: ${quarantineId}`, 'NOT_FOUND', {
139
+ quarantineId,
140
+ });
141
+ }
142
+ // Check if already reviewed
143
+ if (entry.reviewStatus !== 'pending') {
144
+ throw new QuarantineServiceError(`Quarantine entry already reviewed: ${entry.reviewStatus}`, 'ALREADY_REVIEWED', { quarantineId, currentStatus: entry.reviewStatus });
145
+ }
146
+ // For MALICIOUS severity, require multi-approval workflow
147
+ if (entry.severity === 'MALICIOUS' && input.reviewStatus === 'approved') {
148
+ return this.handleMaliciousApproval(session, quarantineId, entry.skillId, input);
149
+ }
150
+ // For MALICIOUS severity rejection or non-MALICIOUS, check elevated permission
151
+ if (entry.severity === 'MALICIOUS') {
152
+ requirePermission(session, 'quarantine:review_malicious');
153
+ }
154
+ // Perform the review with verified identity
155
+ const reviewResult = this.repository.review(quarantineId, {
156
+ reviewedBy: session.email, // Verified email from session
157
+ reviewStatus: input.reviewStatus,
158
+ reviewNotes: input.reviewNotes,
159
+ });
160
+ if (!reviewResult) {
161
+ throw new QuarantineServiceError('Failed to review quarantine entry', 'INVALID_INPUT', {
162
+ quarantineId,
163
+ });
164
+ }
165
+ // Log audit event with full session details
166
+ this.auditLogger.log({
167
+ event_type: 'quarantine_authenticated_review',
168
+ actor: 'reviewer',
169
+ resource: entry.skillId,
170
+ action: 'review',
171
+ result: 'success',
172
+ metadata: {
173
+ quarantineId,
174
+ reviewStatus: input.reviewStatus,
175
+ reviewer: {
176
+ userId: session.userId,
177
+ email: session.email,
178
+ displayName: session.displayName,
179
+ },
180
+ sessionId: session.sessionId,
181
+ severity: entry.severity,
182
+ canImport: reviewResult.canImport,
183
+ },
184
+ });
185
+ return {
186
+ success: true,
187
+ approved: reviewResult.approved,
188
+ skillId: reviewResult.skillId,
189
+ severity: reviewResult.severity,
190
+ canImport: reviewResult.canImport,
191
+ warnings: reviewResult.warnings,
192
+ reviewedBy: {
193
+ userId: session.userId,
194
+ email: session.email,
195
+ displayName: session.displayName,
196
+ },
197
+ };
198
+ }
199
+ // ==========================================================================
200
+ // Multi-Approval Workflow (MALICIOUS Severity)
201
+ // ==========================================================================
202
+ /**
203
+ * Handle approval for MALICIOUS severity skills
204
+ *
205
+ * MALICIOUS severity requires multiple reviewers to approve
206
+ * before a skill can be unquarantined. This prevents single
207
+ * reviewer compromise from allowing malicious skills.
208
+ *
209
+ * Approval state is persisted to the database (SMI-2277) so
210
+ * pending approvals survive service restarts.
211
+ *
212
+ * @param session - Authenticated session
213
+ * @param quarantineId - Quarantine entry ID
214
+ * @param skillId - Skill ID
215
+ * @param input - Review input
216
+ * @returns Review result with multi-approval status
217
+ */
218
+ handleMaliciousApproval(session, quarantineId, skillId, input) {
219
+ // Require elevated permission for MALICIOUS review
220
+ requirePermission(session, 'quarantine:review_malicious');
221
+ // Check if this reviewer already approved (database-backed)
222
+ if (this.approvalRepository.hasReviewerApproved(quarantineId, session.userId)) {
223
+ // Retrieve existing approval for error details
224
+ const existingApprovals = this.approvalRepository.getPendingApprovals(quarantineId);
225
+ const existing = existingApprovals.find((a) => a.reviewerId === session.userId);
226
+ throw new QuarantineServiceError('You have already approved this entry', 'ALREADY_REVIEWED', {
227
+ quarantineId,
228
+ previousApprovalAt: existing?.createdAt ?? 'unknown',
229
+ });
230
+ }
231
+ // Check for approval timeout
232
+ const startTime = this.approvalRepository.getWorkflowStartTime(quarantineId);
233
+ if (startTime) {
234
+ const timeSinceStart = Date.now() - new Date(startTime).getTime();
235
+ if (timeSinceStart > MULTI_APPROVAL_TIMEOUT_MS) {
236
+ // Capture existing approvals before clearing for audit
237
+ const expiredApprovals = this.approvalRepository.getPendingApprovals(quarantineId);
238
+ // Reset approval workflow
239
+ this.approvalRepository.clearApprovals(quarantineId);
240
+ // Log timeout event so cleared reviewer work is auditable
241
+ this.auditLogger.log({
242
+ event_type: 'quarantine_multi_approval_timeout',
243
+ actor: 'system',
244
+ resource: skillId,
245
+ action: 'timeout',
246
+ result: 'success',
247
+ metadata: {
248
+ quarantineId,
249
+ timeoutMs: MULTI_APPROVAL_TIMEOUT_MS,
250
+ expiredApprovals: expiredApprovals.map((a) => ({
251
+ reviewerId: a.reviewerId,
252
+ email: a.reviewerEmail,
253
+ approvedAt: a.createdAt,
254
+ })),
255
+ triggeredBy: {
256
+ userId: session.userId,
257
+ email: session.email,
258
+ },
259
+ },
260
+ });
261
+ throw new QuarantineServiceError('Multi-approval workflow timed out. Please start again.', 'INVALID_INPUT', { quarantineId, timeoutMs: MULTI_APPROVAL_TIMEOUT_MS });
262
+ }
263
+ }
264
+ // Record this approval in the database
265
+ this.approvalRepository.recordApproval({
266
+ skillId: quarantineId,
267
+ reviewerId: session.userId,
268
+ reviewerEmail: session.email,
269
+ decision: 'approved',
270
+ reason: input.reviewNotes,
271
+ requiredApprovals: MALICIOUS_APPROVAL_COUNT,
272
+ });
273
+ // Get current approval count and all pending approvals
274
+ const pendingApprovals = this.approvalRepository.getPendingApprovals(quarantineId);
275
+ const approvalCount = pendingApprovals.filter((a) => a.decision === 'approved').length;
276
+ // Build the multi-approval status from database state
277
+ const approvalStatus = this.buildMultiApprovalStatus(quarantineId, pendingApprovals);
278
+ // Log the approval
279
+ this.auditLogger.log({
280
+ event_type: 'quarantine_multi_approval',
281
+ actor: 'reviewer',
282
+ resource: skillId,
283
+ action: 'approve',
284
+ result: 'success',
285
+ metadata: {
286
+ quarantineId,
287
+ approvalNumber: approvalCount,
288
+ requiredApprovals: MALICIOUS_APPROVAL_COUNT,
289
+ reviewer: {
290
+ userId: session.userId,
291
+ email: session.email,
292
+ },
293
+ },
294
+ });
295
+ // Check if we have enough approvals
296
+ if (approvalCount >= MALICIOUS_APPROVAL_COUNT) {
297
+ // Mark approvals as complete in database
298
+ this.approvalRepository.markComplete(quarantineId);
299
+ approvalStatus.isComplete = true;
300
+ approvalStatus.completedAt = new Date();
301
+ // Perform the actual review
302
+ const approverEmails = pendingApprovals
303
+ .filter((a) => a.decision === 'approved')
304
+ .map((a) => a.reviewerEmail);
305
+ const reviewResult = this.repository.review(quarantineId, {
306
+ reviewedBy: approverEmails.join(', '),
307
+ reviewStatus: 'approved',
308
+ reviewNotes: `Multi-approval complete: ${approvalCount} reviewers approved. ${input.reviewNotes || ''}`,
309
+ });
310
+ // Log completion
311
+ this.auditLogger.log({
312
+ event_type: 'quarantine_multi_approval_complete',
313
+ actor: 'reviewer',
314
+ resource: skillId,
315
+ action: 'complete',
316
+ result: 'success',
317
+ metadata: {
318
+ quarantineId,
319
+ approvals: pendingApprovals
320
+ .filter((a) => a.decision === 'approved')
321
+ .map((a) => ({
322
+ reviewerId: a.reviewerId,
323
+ email: a.reviewerEmail,
324
+ approvedAt: a.createdAt,
325
+ })),
326
+ },
327
+ });
328
+ return {
329
+ success: true,
330
+ approved: true,
331
+ skillId,
332
+ severity: 'MALICIOUS',
333
+ canImport: reviewResult?.canImport ?? false,
334
+ warnings: [
335
+ 'MALICIOUS skill approved through multi-approval workflow',
336
+ `Approved by: ${approverEmails.join(', ')}`,
337
+ ],
338
+ reviewedBy: {
339
+ userId: session.userId,
340
+ email: session.email,
341
+ displayName: session.displayName,
342
+ },
343
+ multiApprovalStatus: approvalStatus,
344
+ };
345
+ }
346
+ // Need more approvals
347
+ return {
348
+ success: true,
349
+ approved: false,
350
+ skillId,
351
+ severity: 'MALICIOUS',
352
+ canImport: false,
353
+ warnings: [
354
+ `Multi-approval in progress: ${approvalCount}/${MALICIOUS_APPROVAL_COUNT} approvals received`,
355
+ `Requires ${MALICIOUS_APPROVAL_COUNT - approvalCount} more approval(s)`,
356
+ ],
357
+ reviewedBy: {
358
+ userId: session.userId,
359
+ email: session.email,
360
+ displayName: session.displayName,
361
+ },
362
+ multiApprovalStatus: approvalStatus,
363
+ };
364
+ }
365
+ /**
366
+ * Get pending multi-approval status for a quarantine entry
367
+ *
368
+ * @param session - Authenticated session
369
+ * @param quarantineId - Quarantine entry ID
370
+ * @returns Multi-approval status or null
371
+ */
372
+ getMultiApprovalStatus(session, quarantineId) {
373
+ requirePermission(session, 'quarantine:read');
374
+ const pendingApprovals = this.approvalRepository.getPendingApprovals(quarantineId);
375
+ if (pendingApprovals.length === 0) {
376
+ return null;
377
+ }
378
+ return this.buildMultiApprovalStatus(quarantineId, pendingApprovals);
379
+ }
380
+ /**
381
+ * Cancel a pending multi-approval workflow
382
+ *
383
+ * @param session - Authenticated session (requires admin)
384
+ * @param quarantineId - Quarantine entry ID
385
+ * @returns Whether the cancellation was successful
386
+ */
387
+ cancelMultiApproval(session, quarantineId) {
388
+ requirePermission(session, 'quarantine:admin');
389
+ const pendingApprovals = this.approvalRepository.getPendingApprovals(quarantineId);
390
+ if (pendingApprovals.length === 0) {
391
+ return false;
392
+ }
393
+ this.approvalRepository.clearApprovals(quarantineId);
394
+ this.auditLogger.log({
395
+ event_type: 'quarantine_multi_approval_cancelled',
396
+ actor: 'reviewer',
397
+ resource: quarantineId,
398
+ action: 'cancel',
399
+ result: 'success',
400
+ metadata: {
401
+ quarantineId,
402
+ cancelledBy: session.email,
403
+ pendingApprovals: pendingApprovals.length,
404
+ },
405
+ });
406
+ return true;
407
+ }
408
+ // ==========================================================================
409
+ // Admin Operations (require quarantine:admin permission)
410
+ // ==========================================================================
411
+ /**
412
+ * Create a quarantine entry (admin only)
413
+ *
414
+ * @param session - Authenticated session
415
+ * @param input - Quarantine creation input
416
+ * @returns Created quarantine entry
417
+ */
418
+ create(session, input) {
419
+ requirePermission(session, 'quarantine:create');
420
+ return this.repository.create(input);
421
+ }
422
+ /**
423
+ * Delete a quarantine entry (admin only)
424
+ *
425
+ * @param session - Authenticated session
426
+ * @param id - Quarantine entry ID
427
+ * @returns Whether the entry was deleted
428
+ */
429
+ delete(session, id) {
430
+ requirePermission(session, 'quarantine:delete');
431
+ // Cancel any pending multi-approval
432
+ this.approvalRepository.clearApprovals(id);
433
+ return this.repository.delete(id);
434
+ }
435
+ // ==========================================================================
436
+ // Private Helpers
437
+ // ==========================================================================
438
+ /**
439
+ * Build a MultiApprovalStatus from database rows
440
+ *
441
+ * Converts persisted approval entries into the MultiApprovalStatus
442
+ * interface expected by consumers.
443
+ */
444
+ buildMultiApprovalStatus(quarantineId, approvals) {
445
+ const currentApprovals = approvals.map((a) => ({
446
+ reviewerId: a.reviewerId,
447
+ reviewerEmail: a.reviewerEmail,
448
+ approvedAt: new Date(a.createdAt),
449
+ notes: a.reason ?? undefined,
450
+ }));
451
+ const startedAt = approvals.length > 0 ? new Date(approvals[0].createdAt) : new Date();
452
+ const isComplete = approvals.some((a) => a.isComplete);
453
+ const completedEntry = approvals.find((a) => a.completedAt);
454
+ return {
455
+ quarantineId,
456
+ requiredApprovals: MALICIOUS_APPROVAL_COUNT,
457
+ currentApprovals,
458
+ isComplete,
459
+ startedAt,
460
+ completedAt: completedEntry ? new Date(completedEntry.completedAt) : undefined,
461
+ };
462
+ }
463
+ }
464
+ //# 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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"}