@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.
- package/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/__tests__/incremental.test.d.ts +1 -1
- package/dist/src/analysis/__tests__/incremental.test.js +1 -1
- package/dist/src/analysis/__tests__/integration.test.d.ts +1 -1
- package/dist/src/analysis/__tests__/integration.test.js +1 -1
- package/dist/src/analysis/__tests__/performance.test.d.ts +1 -1
- package/dist/src/analysis/__tests__/performance.test.js +1 -1
- package/dist/src/analysis/adapters/__tests__/python.test.d.ts +1 -1
- package/dist/src/analysis/adapters/__tests__/python.test.js +1 -1
- package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts +1 -1
- package/dist/src/analysis/adapters/__tests__/typescript.test.js +1 -1
- package/dist/src/analysis/adapters/base.d.ts +1 -1
- package/dist/src/analysis/adapters/base.js +1 -1
- package/dist/src/analysis/adapters/factory.d.ts +1 -1
- package/dist/src/analysis/adapters/factory.js +1 -1
- package/dist/src/analysis/adapters/go.d.ts +1 -1
- package/dist/src/analysis/adapters/go.js +1 -1
- package/dist/src/analysis/adapters/index.d.ts +1 -1
- package/dist/src/analysis/adapters/index.js +1 -1
- package/dist/src/analysis/adapters/java-parsers.d.ts +1 -1
- package/dist/src/analysis/adapters/java-parsers.d.ts.map +1 -1
- package/dist/src/analysis/adapters/java-parsers.js +10 -3
- package/dist/src/analysis/adapters/java-parsers.js.map +1 -1
- package/dist/src/analysis/adapters/java.d.ts +1 -1
- package/dist/src/analysis/adapters/java.js +1 -1
- package/dist/src/analysis/adapters/python-frameworks.d.ts +1 -1
- package/dist/src/analysis/adapters/python-frameworks.js +1 -1
- package/dist/src/analysis/adapters/python.d.ts +1 -1
- package/dist/src/analysis/adapters/python.js +1 -1
- package/dist/src/analysis/adapters/rust-parsers.d.ts +1 -1
- package/dist/src/analysis/adapters/rust-parsers.js +1 -1
- package/dist/src/analysis/adapters/rust.d.ts +1 -1
- package/dist/src/analysis/adapters/rust.js +1 -1
- package/dist/src/analysis/adapters/typescript.d.ts +1 -1
- package/dist/src/analysis/adapters/typescript.js +1 -1
- package/dist/src/analysis/aggregator.d.ts +1 -1
- package/dist/src/analysis/aggregator.js +1 -1
- package/dist/src/analysis/cache.d.ts +1 -1
- package/dist/src/analysis/cache.js +1 -1
- package/dist/src/analysis/file-streamer.d.ts +1 -1
- package/dist/src/analysis/file-streamer.js +1 -1
- package/dist/src/analysis/incremental-parser.d.ts +1 -1
- package/dist/src/analysis/incremental-parser.js +1 -1
- package/dist/src/analysis/incremental.d.ts +1 -1
- package/dist/src/analysis/incremental.js +1 -1
- package/dist/src/analysis/index.d.ts +1 -1
- package/dist/src/analysis/index.js +1 -1
- package/dist/src/analysis/language-detector.d.ts +1 -1
- package/dist/src/analysis/language-detector.js +1 -1
- package/dist/src/analysis/memory-monitor.d.ts +1 -1
- package/dist/src/analysis/memory-monitor.js +1 -1
- package/dist/src/analysis/metrics.d.ts +1 -1
- package/dist/src/analysis/metrics.js +1 -1
- package/dist/src/analysis/router.d.ts +1 -1
- package/dist/src/analysis/router.js +1 -1
- package/dist/src/analysis/tree-cache.d.ts +1 -1
- package/dist/src/analysis/tree-cache.js +1 -1
- package/dist/src/analysis/tree-sitter/manager.d.ts +1 -1
- package/dist/src/analysis/tree-sitter/manager.js +1 -1
- package/dist/src/analysis/types.d.ts +1 -1
- package/dist/src/analysis/types.js +1 -1
- package/dist/src/analysis/worker-pool.d.ts +1 -1
- package/dist/src/analysis/worker-pool.js +1 -1
- package/dist/src/analysis/worker-types.d.ts +1 -1
- package/dist/src/analysis/worker-types.js +1 -1
- package/dist/src/analysis/worker-utils.d.ts +1 -1
- package/dist/src/analysis/worker-utils.js +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/config/index.d.ts +49 -1
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +166 -3
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/config/index.test.d.ts +11 -0
- package/dist/src/config/index.test.d.ts.map +1 -0
- package/dist/src/config/index.test.js +288 -0
- package/dist/src/config/index.test.js.map +1 -0
- 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/db/quarantine-approvals-schema.d.ts +37 -0
- package/dist/src/db/quarantine-approvals-schema.d.ts.map +1 -0
- package/dist/src/db/quarantine-approvals-schema.js +71 -0
- package/dist/src/db/quarantine-approvals-schema.js.map +1 -0
- 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 +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -2
- 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/ApprovalRepository.d.ts +148 -0
- package/dist/src/repositories/quarantine/ApprovalRepository.d.ts.map +1 -0
- package/dist/src/repositories/quarantine/ApprovalRepository.js +212 -0
- package/dist/src/repositories/quarantine/ApprovalRepository.js.map +1 -0
- 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/repositories/quarantine/index.d.ts +2 -0
- package/dist/src/repositories/quarantine/index.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/index.js +1 -0
- package/dist/src/repositories/quarantine/index.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/security/scanner/SecurityScanner.formatters.js +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.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 +157 -0
- package/dist/src/services/quarantine/QuarantineService.d.ts.map +1 -0
- package/dist/src/services/quarantine/QuarantineService.js +464 -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/RawUrlSourceAdapter.security.test.js +2 -1
- package/dist/tests/RawUrlSourceAdapter.security.test.js.map +1 -1
- package/dist/tests/SecurityScanner.test.js +2 -2
- package/dist/tests/SecurityScanner.test.js.map +1 -1
- package/dist/tests/adapters-factory.test.d.ts +1 -1
- package/dist/tests/adapters-factory.test.js +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 +426 -0
- package/dist/tests/integration/QuarantineService.test.js.map +1 -0
- package/dist/tests/integration/neural/e2e-learning.test.d.ts +1 -1
- package/dist/tests/integration/neural/e2e-learning.test.js +1 -1
- package/dist/tests/integration/neural/personalization.test.d.ts +1 -1
- package/dist/tests/integration/neural/personalization.test.js +1 -1
- package/dist/tests/integration/neural/preference-learner.test.d.ts +1 -1
- package/dist/tests/integration/neural/preference-learner.test.js +1 -1
- package/dist/tests/integration/neural/privacy.test.d.ts +1 -1
- package/dist/tests/integration/neural/privacy.test.js +1 -1
- package/dist/tests/integration/neural/signal-collection.test.d.ts +1 -1
- package/dist/tests/integration/neural/signal-collection.test.js +1 -1
- package/dist/tests/language-detector.test.d.ts +1 -1
- package/dist/tests/language-detector.test.js +1 -1
- package/dist/tests/unit/approval-repository.test.d.ts +9 -0
- package/dist/tests/unit/approval-repository.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-repository.test.js +509 -0
- package/dist/tests/unit/approval-repository.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,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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"}
|