@objectstack/core 0.9.1 → 1.0.0
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/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
- package/CHANGELOG.md +21 -0
- package/PHASE2_IMPLEMENTATION.md +388 -0
- package/README.md +12 -341
- package/REFACTORING_SUMMARY.md +40 -0
- package/dist/api-registry-plugin.test.js +23 -21
- package/dist/api-registry.test.js +2 -2
- package/dist/dependency-resolver.d.ts +62 -0
- package/dist/dependency-resolver.d.ts.map +1 -0
- package/dist/dependency-resolver.js +317 -0
- package/dist/dependency-resolver.test.d.ts +2 -0
- package/dist/dependency-resolver.test.d.ts.map +1 -0
- package/dist/dependency-resolver.test.js +241 -0
- package/dist/health-monitor.d.ts +65 -0
- package/dist/health-monitor.d.ts.map +1 -0
- package/dist/health-monitor.js +269 -0
- package/dist/health-monitor.test.d.ts +2 -0
- package/dist/health-monitor.test.d.ts.map +1 -0
- package/dist/health-monitor.test.js +68 -0
- package/dist/hot-reload.d.ts +79 -0
- package/dist/hot-reload.d.ts.map +1 -0
- package/dist/hot-reload.js +313 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/kernel-base.d.ts +2 -2
- package/dist/kernel-base.js +2 -2
- package/dist/kernel.d.ts +89 -31
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +430 -73
- package/dist/kernel.test.js +375 -122
- package/dist/lite-kernel.d.ts +55 -0
- package/dist/lite-kernel.d.ts.map +1 -0
- package/dist/lite-kernel.js +112 -0
- package/dist/lite-kernel.test.d.ts +2 -0
- package/dist/lite-kernel.test.d.ts.map +1 -0
- package/dist/lite-kernel.test.js +161 -0
- package/dist/logger.d.ts +2 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +26 -7
- package/dist/plugin-loader.d.ts +15 -0
- package/dist/plugin-loader.d.ts.map +1 -1
- package/dist/plugin-loader.js +40 -10
- package/dist/plugin-loader.test.js +9 -0
- package/dist/security/index.d.ts +3 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +4 -0
- package/dist/security/permission-manager.d.ts +96 -0
- package/dist/security/permission-manager.d.ts.map +1 -0
- package/dist/security/permission-manager.js +235 -0
- package/dist/security/permission-manager.test.d.ts +2 -0
- package/dist/security/permission-manager.test.d.ts.map +1 -0
- package/dist/security/permission-manager.test.js +220 -0
- package/dist/security/plugin-permission-enforcer.d.ts +1 -1
- package/dist/security/sandbox-runtime.d.ts +115 -0
- package/dist/security/sandbox-runtime.d.ts.map +1 -0
- package/dist/security/sandbox-runtime.js +310 -0
- package/dist/security/security-scanner.d.ts +92 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +273 -0
- package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
- package/examples/phase2-integration.ts +355 -0
- package/package.json +3 -2
- package/src/api-registry-plugin.test.ts +23 -21
- package/src/api-registry.test.ts +2 -2
- package/src/dependency-resolver.test.ts +287 -0
- package/src/dependency-resolver.ts +388 -0
- package/src/health-monitor.test.ts +81 -0
- package/src/health-monitor.ts +316 -0
- package/src/hot-reload.ts +388 -0
- package/src/index.ts +6 -1
- package/src/kernel-base.ts +2 -2
- package/src/kernel.test.ts +471 -134
- package/src/kernel.ts +518 -76
- package/src/lite-kernel.test.ts +200 -0
- package/src/lite-kernel.ts +135 -0
- package/src/logger.ts +28 -7
- package/src/plugin-loader.test.ts +10 -1
- package/src/plugin-loader.ts +49 -13
- package/src/security/index.ts +19 -0
- package/src/security/permission-manager.test.ts +256 -0
- package/src/security/permission-manager.ts +336 -0
- package/src/security/plugin-permission-enforcer.test.ts +1 -1
- package/src/security/plugin-permission-enforcer.ts +1 -1
- package/src/security/sandbox-runtime.ts +432 -0
- package/src/security/security-scanner.ts +365 -0
- package/dist/enhanced-kernel.d.ts +0 -103
- package/dist/enhanced-kernel.d.ts.map +0 -1
- package/dist/enhanced-kernel.js +0 -403
- package/dist/enhanced-kernel.test.d.ts +0 -2
- package/dist/enhanced-kernel.test.d.ts.map +0 -1
- package/dist/enhanced-kernel.test.js +0 -412
- package/src/enhanced-kernel.test.ts +0 -535
- package/src/enhanced-kernel.ts +0 -496
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Sandbox Runtime
|
|
3
|
+
*
|
|
4
|
+
* Provides isolated execution environments for plugins with resource limits
|
|
5
|
+
* and access controls
|
|
6
|
+
*/
|
|
7
|
+
export class PluginSandboxRuntime {
|
|
8
|
+
constructor(logger) {
|
|
9
|
+
// Active sandboxes (pluginId -> context)
|
|
10
|
+
this.sandboxes = new Map();
|
|
11
|
+
// Resource monitoring intervals
|
|
12
|
+
this.monitoringIntervals = new Map();
|
|
13
|
+
this.logger = logger.child({ component: 'SandboxRuntime' });
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a sandbox for a plugin
|
|
17
|
+
*/
|
|
18
|
+
createSandbox(pluginId, config) {
|
|
19
|
+
if (this.sandboxes.has(pluginId)) {
|
|
20
|
+
throw new Error(`Sandbox already exists for plugin: ${pluginId}`);
|
|
21
|
+
}
|
|
22
|
+
const context = {
|
|
23
|
+
pluginId,
|
|
24
|
+
config,
|
|
25
|
+
startTime: new Date(),
|
|
26
|
+
resourceUsage: {
|
|
27
|
+
memory: { current: 0, peak: 0, limit: config.memory?.maxHeap },
|
|
28
|
+
cpu: { current: 0, average: 0, limit: config.cpu?.maxCpuPercent },
|
|
29
|
+
connections: { current: 0, limit: config.network?.maxConnections },
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
this.sandboxes.set(pluginId, context);
|
|
33
|
+
// Start resource monitoring
|
|
34
|
+
this.startResourceMonitoring(pluginId);
|
|
35
|
+
this.logger.info('Sandbox created', {
|
|
36
|
+
pluginId,
|
|
37
|
+
level: config.level,
|
|
38
|
+
memoryLimit: config.memory?.maxHeap,
|
|
39
|
+
cpuLimit: config.cpu?.maxCpuPercent
|
|
40
|
+
});
|
|
41
|
+
return context;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Destroy a sandbox
|
|
45
|
+
*/
|
|
46
|
+
destroySandbox(pluginId) {
|
|
47
|
+
const context = this.sandboxes.get(pluginId);
|
|
48
|
+
if (!context) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Stop monitoring
|
|
52
|
+
this.stopResourceMonitoring(pluginId);
|
|
53
|
+
this.sandboxes.delete(pluginId);
|
|
54
|
+
this.logger.info('Sandbox destroyed', { pluginId });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if resource access is allowed
|
|
58
|
+
*/
|
|
59
|
+
checkResourceAccess(pluginId, resourceType, resourcePath) {
|
|
60
|
+
const context = this.sandboxes.get(pluginId);
|
|
61
|
+
if (!context) {
|
|
62
|
+
return { allowed: false, reason: 'Sandbox not found' };
|
|
63
|
+
}
|
|
64
|
+
const { config } = context;
|
|
65
|
+
switch (resourceType) {
|
|
66
|
+
case 'file':
|
|
67
|
+
return this.checkFileAccess(config, resourcePath);
|
|
68
|
+
case 'network':
|
|
69
|
+
return this.checkNetworkAccess(config, resourcePath);
|
|
70
|
+
case 'process':
|
|
71
|
+
return this.checkProcessAccess(config);
|
|
72
|
+
case 'env':
|
|
73
|
+
return this.checkEnvAccess(config, resourcePath);
|
|
74
|
+
default:
|
|
75
|
+
return { allowed: false, reason: 'Unknown resource type' };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check file system access
|
|
80
|
+
* WARNING: Uses simple prefix matching. For production, use proper path
|
|
81
|
+
* resolution with path.resolve() and path.normalize() to prevent traversal.
|
|
82
|
+
*/
|
|
83
|
+
checkFileAccess(config, path) {
|
|
84
|
+
if (config.level === 'none') {
|
|
85
|
+
return { allowed: true };
|
|
86
|
+
}
|
|
87
|
+
if (!config.filesystem) {
|
|
88
|
+
return { allowed: false, reason: 'File system access not configured' };
|
|
89
|
+
}
|
|
90
|
+
// If no path specified, check general access
|
|
91
|
+
if (!path) {
|
|
92
|
+
return { allowed: config.filesystem.mode !== 'none' };
|
|
93
|
+
}
|
|
94
|
+
// TODO: Use path.resolve() and path.normalize() for production
|
|
95
|
+
// Check allowed paths
|
|
96
|
+
const allowedPaths = config.filesystem.allowedPaths || [];
|
|
97
|
+
const isAllowed = allowedPaths.some(allowed => {
|
|
98
|
+
// Simple prefix matching - vulnerable to traversal attacks
|
|
99
|
+
// TODO: Use proper path resolution
|
|
100
|
+
return path.startsWith(allowed);
|
|
101
|
+
});
|
|
102
|
+
if (allowedPaths.length > 0 && !isAllowed) {
|
|
103
|
+
return {
|
|
104
|
+
allowed: false,
|
|
105
|
+
reason: `Path not in allowed list: ${path}`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Check denied paths
|
|
109
|
+
const deniedPaths = config.filesystem.deniedPaths || [];
|
|
110
|
+
const isDenied = deniedPaths.some(denied => {
|
|
111
|
+
return path.startsWith(denied);
|
|
112
|
+
});
|
|
113
|
+
if (isDenied) {
|
|
114
|
+
return {
|
|
115
|
+
allowed: false,
|
|
116
|
+
reason: `Path is explicitly denied: ${path}`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { allowed: true };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check network access
|
|
123
|
+
* WARNING: Uses simple string matching. For production, use proper URL
|
|
124
|
+
* parsing with new URL() and check hostname property.
|
|
125
|
+
*/
|
|
126
|
+
checkNetworkAccess(config, url) {
|
|
127
|
+
if (config.level === 'none') {
|
|
128
|
+
return { allowed: true };
|
|
129
|
+
}
|
|
130
|
+
if (!config.network) {
|
|
131
|
+
return { allowed: false, reason: 'Network access not configured' };
|
|
132
|
+
}
|
|
133
|
+
// Check if network access is enabled
|
|
134
|
+
if (config.network.mode === 'none') {
|
|
135
|
+
return { allowed: false, reason: 'Network access disabled' };
|
|
136
|
+
}
|
|
137
|
+
// If no URL specified, check general access
|
|
138
|
+
if (!url) {
|
|
139
|
+
return { allowed: config.network.mode !== 'none' };
|
|
140
|
+
}
|
|
141
|
+
// TODO: Use new URL() and check hostname property for production
|
|
142
|
+
// Check allowed hosts
|
|
143
|
+
const allowedHosts = config.network.allowedHosts || [];
|
|
144
|
+
if (allowedHosts.length > 0) {
|
|
145
|
+
const isAllowed = allowedHosts.some(host => {
|
|
146
|
+
// Simple string matching - vulnerable to bypass
|
|
147
|
+
// TODO: Use proper URL parsing
|
|
148
|
+
return url.includes(host);
|
|
149
|
+
});
|
|
150
|
+
if (!isAllowed) {
|
|
151
|
+
return {
|
|
152
|
+
allowed: false,
|
|
153
|
+
reason: `Host not in allowed list: ${url}`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Check denied hosts
|
|
158
|
+
const deniedHosts = config.network.deniedHosts || [];
|
|
159
|
+
const isDenied = deniedHosts.some(host => {
|
|
160
|
+
return url.includes(host);
|
|
161
|
+
});
|
|
162
|
+
if (isDenied) {
|
|
163
|
+
return {
|
|
164
|
+
allowed: false,
|
|
165
|
+
reason: `Host is blocked: ${url}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return { allowed: true };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check process spawning access
|
|
172
|
+
*/
|
|
173
|
+
checkProcessAccess(config) {
|
|
174
|
+
if (config.level === 'none') {
|
|
175
|
+
return { allowed: true };
|
|
176
|
+
}
|
|
177
|
+
if (!config.process) {
|
|
178
|
+
return { allowed: false, reason: 'Process access not configured' };
|
|
179
|
+
}
|
|
180
|
+
if (!config.process.allowSpawn) {
|
|
181
|
+
return { allowed: false, reason: 'Process spawning not allowed' };
|
|
182
|
+
}
|
|
183
|
+
return { allowed: true };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check environment variable access
|
|
187
|
+
*/
|
|
188
|
+
checkEnvAccess(config, varName) {
|
|
189
|
+
if (config.level === 'none') {
|
|
190
|
+
return { allowed: true };
|
|
191
|
+
}
|
|
192
|
+
if (!config.process) {
|
|
193
|
+
return { allowed: false, reason: 'Environment access not configured' };
|
|
194
|
+
}
|
|
195
|
+
// If no variable specified, check general access
|
|
196
|
+
if (!varName) {
|
|
197
|
+
return { allowed: true };
|
|
198
|
+
}
|
|
199
|
+
// For now, allow all env access if process is configured
|
|
200
|
+
// In a real implementation, would check specific allowed vars
|
|
201
|
+
return { allowed: true };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Check resource limits
|
|
205
|
+
*/
|
|
206
|
+
checkResourceLimits(pluginId) {
|
|
207
|
+
const context = this.sandboxes.get(pluginId);
|
|
208
|
+
if (!context) {
|
|
209
|
+
return { withinLimits: true, violations: [] };
|
|
210
|
+
}
|
|
211
|
+
const violations = [];
|
|
212
|
+
const { resourceUsage, config } = context;
|
|
213
|
+
// Check memory limit
|
|
214
|
+
if (config.memory?.maxHeap &&
|
|
215
|
+
resourceUsage.memory.current > config.memory.maxHeap) {
|
|
216
|
+
violations.push(`Memory limit exceeded: ${resourceUsage.memory.current} > ${config.memory.maxHeap}`);
|
|
217
|
+
}
|
|
218
|
+
// Check CPU limit (would need runtime config)
|
|
219
|
+
if (config.runtime?.resourceLimits?.maxCpu &&
|
|
220
|
+
resourceUsage.cpu.current > config.runtime.resourceLimits.maxCpu) {
|
|
221
|
+
violations.push(`CPU limit exceeded: ${resourceUsage.cpu.current}% > ${config.runtime.resourceLimits.maxCpu}%`);
|
|
222
|
+
}
|
|
223
|
+
// Check connection limit
|
|
224
|
+
if (config.network?.maxConnections &&
|
|
225
|
+
resourceUsage.connections.current > config.network.maxConnections) {
|
|
226
|
+
violations.push(`Connection limit exceeded: ${resourceUsage.connections.current} > ${config.network.maxConnections}`);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
withinLimits: violations.length === 0,
|
|
230
|
+
violations,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get resource usage for a plugin
|
|
235
|
+
*/
|
|
236
|
+
getResourceUsage(pluginId) {
|
|
237
|
+
const context = this.sandboxes.get(pluginId);
|
|
238
|
+
return context?.resourceUsage;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Start monitoring resource usage
|
|
242
|
+
*/
|
|
243
|
+
startResourceMonitoring(pluginId) {
|
|
244
|
+
// Monitor every 5 seconds
|
|
245
|
+
const interval = setInterval(() => {
|
|
246
|
+
this.updateResourceUsage(pluginId);
|
|
247
|
+
}, 5000);
|
|
248
|
+
this.monitoringIntervals.set(pluginId, interval);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Stop monitoring resource usage
|
|
252
|
+
*/
|
|
253
|
+
stopResourceMonitoring(pluginId) {
|
|
254
|
+
const interval = this.monitoringIntervals.get(pluginId);
|
|
255
|
+
if (interval) {
|
|
256
|
+
clearInterval(interval);
|
|
257
|
+
this.monitoringIntervals.delete(pluginId);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Update resource usage statistics
|
|
262
|
+
*
|
|
263
|
+
* NOTE: Currently uses global process.memoryUsage() which tracks the entire
|
|
264
|
+
* Node.js process, not individual plugins. For production, implement proper
|
|
265
|
+
* per-plugin tracking using V8 heap snapshots or allocation tracking at
|
|
266
|
+
* plugin boundaries.
|
|
267
|
+
*/
|
|
268
|
+
updateResourceUsage(pluginId) {
|
|
269
|
+
const context = this.sandboxes.get(pluginId);
|
|
270
|
+
if (!context) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// In a real implementation, this would collect actual metrics
|
|
274
|
+
// For now, this is a placeholder structure
|
|
275
|
+
// Update memory usage (global process memory - not per-plugin)
|
|
276
|
+
// TODO: Implement per-plugin memory tracking
|
|
277
|
+
const memoryUsage = process.memoryUsage();
|
|
278
|
+
context.resourceUsage.memory.current = memoryUsage.heapUsed;
|
|
279
|
+
context.resourceUsage.memory.peak = Math.max(context.resourceUsage.memory.peak, memoryUsage.heapUsed);
|
|
280
|
+
// Update CPU usage (would use process.cpuUsage() or similar)
|
|
281
|
+
// This is a placeholder - real implementation would track per-plugin CPU
|
|
282
|
+
// TODO: Implement per-plugin CPU tracking
|
|
283
|
+
context.resourceUsage.cpu.current = 0;
|
|
284
|
+
// Check for violations
|
|
285
|
+
const { withinLimits, violations } = this.checkResourceLimits(pluginId);
|
|
286
|
+
if (!withinLimits) {
|
|
287
|
+
this.logger.warn('Resource limit violations detected', {
|
|
288
|
+
pluginId,
|
|
289
|
+
violations
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get all active sandboxes
|
|
295
|
+
*/
|
|
296
|
+
getAllSandboxes() {
|
|
297
|
+
return new Map(this.sandboxes);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Shutdown sandbox runtime
|
|
301
|
+
*/
|
|
302
|
+
shutdown() {
|
|
303
|
+
// Stop all monitoring
|
|
304
|
+
for (const pluginId of this.monitoringIntervals.keys()) {
|
|
305
|
+
this.stopResourceMonitoring(pluginId);
|
|
306
|
+
}
|
|
307
|
+
this.sandboxes.clear();
|
|
308
|
+
this.logger.info('Sandbox runtime shutdown complete');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { SecurityVulnerability, SecurityScanResult } from '@objectstack/spec/kernel';
|
|
2
|
+
import type { ObjectLogger } from '../logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Scan Target
|
|
5
|
+
*/
|
|
6
|
+
export interface ScanTarget {
|
|
7
|
+
pluginId: string;
|
|
8
|
+
version: string;
|
|
9
|
+
files?: string[];
|
|
10
|
+
dependencies?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Security Issue
|
|
14
|
+
*/
|
|
15
|
+
export interface SecurityIssue {
|
|
16
|
+
id: string;
|
|
17
|
+
severity: 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
18
|
+
category: 'vulnerability' | 'malware' | 'license' | 'code-quality' | 'configuration';
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
location?: {
|
|
22
|
+
file?: string;
|
|
23
|
+
line?: number;
|
|
24
|
+
column?: number;
|
|
25
|
+
};
|
|
26
|
+
remediation?: string;
|
|
27
|
+
cve?: string;
|
|
28
|
+
cvss?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Plugin Security Scanner
|
|
32
|
+
*
|
|
33
|
+
* Scans plugins for security vulnerabilities, malware, and license issues
|
|
34
|
+
*/
|
|
35
|
+
export declare class PluginSecurityScanner {
|
|
36
|
+
private logger;
|
|
37
|
+
private vulnerabilityDb;
|
|
38
|
+
private scanResults;
|
|
39
|
+
private passThreshold;
|
|
40
|
+
constructor(logger: ObjectLogger, config?: {
|
|
41
|
+
passThreshold?: number;
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Perform a comprehensive security scan on a plugin
|
|
45
|
+
*/
|
|
46
|
+
scan(target: ScanTarget): Promise<SecurityScanResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Scan code for vulnerabilities
|
|
49
|
+
*/
|
|
50
|
+
private scanCode;
|
|
51
|
+
/**
|
|
52
|
+
* Scan dependencies for known vulnerabilities
|
|
53
|
+
*/
|
|
54
|
+
private scanDependencies;
|
|
55
|
+
/**
|
|
56
|
+
* Scan for malware patterns
|
|
57
|
+
*/
|
|
58
|
+
private scanMalware;
|
|
59
|
+
/**
|
|
60
|
+
* Check license compliance
|
|
61
|
+
*/
|
|
62
|
+
private scanLicenses;
|
|
63
|
+
/**
|
|
64
|
+
* Check configuration security
|
|
65
|
+
*/
|
|
66
|
+
private scanConfiguration;
|
|
67
|
+
/**
|
|
68
|
+
* Calculate security score based on issues
|
|
69
|
+
*/
|
|
70
|
+
private calculateSecurityScore;
|
|
71
|
+
/**
|
|
72
|
+
* Add a vulnerability to the database
|
|
73
|
+
*/
|
|
74
|
+
addVulnerability(packageName: string, version: string, vulnerability: SecurityVulnerability): void;
|
|
75
|
+
/**
|
|
76
|
+
* Get scan result from cache
|
|
77
|
+
*/
|
|
78
|
+
getScanResult(pluginId: string, version: string): SecurityScanResult | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Clear scan results cache
|
|
81
|
+
*/
|
|
82
|
+
clearCache(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Update vulnerability database from external source
|
|
85
|
+
*/
|
|
86
|
+
updateVulnerabilityDatabase(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Shutdown security scanner
|
|
89
|
+
*/
|
|
90
|
+
shutdown(): void;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=security-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-scanner.d.ts","sourceRoot":"","sources":["../../src/security/security-scanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC1D,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,GAAG,eAAe,CAAC;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAe;IAG7B,OAAO,CAAC,eAAe,CAA4C;IAGnE,OAAO,CAAC,WAAW,CAAyC;IAE5D,OAAO,CAAC,aAAa,CAAc;gBAEvB,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE;IAOrE;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA8E3D;;OAEG;YACW,QAAQ;IAmBtB;;OAEG;YACW,gBAAgB;IAyC9B;;OAEG;YACW,WAAW;IAmBzB;;OAEG;YACW,YAAY;IAsB1B;;OAEG;YACW,iBAAiB;IAkB/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6B9B;;OAEG;IACH,gBAAgB,CACd,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,qBAAqB,GACnC,IAAI;IAWP;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIhF;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACG,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;IAclD;;OAEG;IACH,QAAQ,IAAI,IAAI;CAMjB"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Security Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans plugins for security vulnerabilities, malware, and license issues
|
|
5
|
+
*/
|
|
6
|
+
export class PluginSecurityScanner {
|
|
7
|
+
constructor(logger, config) {
|
|
8
|
+
// Known vulnerabilities database (CVE cache)
|
|
9
|
+
this.vulnerabilityDb = new Map();
|
|
10
|
+
// Scan results cache
|
|
11
|
+
this.scanResults = new Map();
|
|
12
|
+
this.passThreshold = 70;
|
|
13
|
+
this.logger = logger.child({ component: 'SecurityScanner' });
|
|
14
|
+
if (config?.passThreshold !== undefined) {
|
|
15
|
+
this.passThreshold = config.passThreshold;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Perform a comprehensive security scan on a plugin
|
|
20
|
+
*/
|
|
21
|
+
async scan(target) {
|
|
22
|
+
this.logger.info('Starting security scan', {
|
|
23
|
+
pluginId: target.pluginId,
|
|
24
|
+
version: target.version
|
|
25
|
+
});
|
|
26
|
+
const issues = [];
|
|
27
|
+
try {
|
|
28
|
+
// 1. Scan for code vulnerabilities
|
|
29
|
+
const codeIssues = await this.scanCode(target);
|
|
30
|
+
issues.push(...codeIssues);
|
|
31
|
+
// 2. Scan dependencies for known vulnerabilities
|
|
32
|
+
const depIssues = await this.scanDependencies(target);
|
|
33
|
+
issues.push(...depIssues);
|
|
34
|
+
// 3. Scan for malware patterns
|
|
35
|
+
const malwareIssues = await this.scanMalware(target);
|
|
36
|
+
issues.push(...malwareIssues);
|
|
37
|
+
// 4. Check license compliance
|
|
38
|
+
const licenseIssues = await this.scanLicenses(target);
|
|
39
|
+
issues.push(...licenseIssues);
|
|
40
|
+
// 5. Check configuration security
|
|
41
|
+
const configIssues = await this.scanConfiguration(target);
|
|
42
|
+
issues.push(...configIssues);
|
|
43
|
+
// Calculate security score (0-100, higher is better)
|
|
44
|
+
const score = this.calculateSecurityScore(issues);
|
|
45
|
+
const result = {
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
scanner: { name: 'ObjectStack Security Scanner', version: '1.0.0' },
|
|
48
|
+
status: score >= this.passThreshold ? 'passed' : 'failed',
|
|
49
|
+
vulnerabilities: issues.map(issue => ({
|
|
50
|
+
id: issue.id,
|
|
51
|
+
severity: issue.severity,
|
|
52
|
+
category: issue.category,
|
|
53
|
+
title: issue.title,
|
|
54
|
+
description: issue.description,
|
|
55
|
+
location: issue.location ? `${issue.location.file}:${issue.location.line}` : undefined,
|
|
56
|
+
remediation: issue.remediation,
|
|
57
|
+
affectedVersions: [],
|
|
58
|
+
exploitAvailable: false,
|
|
59
|
+
patchAvailable: false,
|
|
60
|
+
})),
|
|
61
|
+
summary: {
|
|
62
|
+
totalVulnerabilities: issues.length,
|
|
63
|
+
criticalCount: issues.filter(i => i.severity === 'critical').length,
|
|
64
|
+
highCount: issues.filter(i => i.severity === 'high').length,
|
|
65
|
+
mediumCount: issues.filter(i => i.severity === 'medium').length,
|
|
66
|
+
lowCount: issues.filter(i => i.severity === 'low').length,
|
|
67
|
+
infoCount: issues.filter(i => i.severity === 'info').length,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
this.scanResults.set(`${target.pluginId}:${target.version}`, result);
|
|
71
|
+
this.logger.info('Security scan complete', {
|
|
72
|
+
pluginId: target.pluginId,
|
|
73
|
+
score,
|
|
74
|
+
status: result.status,
|
|
75
|
+
summary: result.summary
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
this.logger.error('Security scan failed', {
|
|
81
|
+
pluginId: target.pluginId,
|
|
82
|
+
error
|
|
83
|
+
});
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Scan code for vulnerabilities
|
|
89
|
+
*/
|
|
90
|
+
async scanCode(target) {
|
|
91
|
+
const issues = [];
|
|
92
|
+
// In a real implementation, this would:
|
|
93
|
+
// - Parse code with AST (e.g., using @typescript-eslint/parser)
|
|
94
|
+
// - Check for dangerous patterns (eval, Function constructor, etc.)
|
|
95
|
+
// - Check for XSS vulnerabilities
|
|
96
|
+
// - Check for SQL injection patterns
|
|
97
|
+
// - Check for insecure crypto usage
|
|
98
|
+
// - Check for path traversal vulnerabilities
|
|
99
|
+
this.logger.debug('Code scan complete', {
|
|
100
|
+
pluginId: target.pluginId,
|
|
101
|
+
issuesFound: issues.length
|
|
102
|
+
});
|
|
103
|
+
return issues;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Scan dependencies for known vulnerabilities
|
|
107
|
+
*/
|
|
108
|
+
async scanDependencies(target) {
|
|
109
|
+
const issues = [];
|
|
110
|
+
if (!target.dependencies) {
|
|
111
|
+
return issues;
|
|
112
|
+
}
|
|
113
|
+
// In a real implementation, this would:
|
|
114
|
+
// - Query npm audit API
|
|
115
|
+
// - Check GitHub Advisory Database
|
|
116
|
+
// - Check Snyk vulnerability database
|
|
117
|
+
// - Check OSV (Open Source Vulnerabilities)
|
|
118
|
+
for (const [depName, version] of Object.entries(target.dependencies)) {
|
|
119
|
+
const vulnKey = `${depName}@${version}`;
|
|
120
|
+
const vulnerability = this.vulnerabilityDb.get(vulnKey);
|
|
121
|
+
if (vulnerability) {
|
|
122
|
+
issues.push({
|
|
123
|
+
id: `vuln-${vulnerability.cve || depName}`,
|
|
124
|
+
severity: vulnerability.severity,
|
|
125
|
+
category: 'vulnerability',
|
|
126
|
+
title: `Vulnerable dependency: ${depName}`,
|
|
127
|
+
description: `${depName}@${version} has known security vulnerabilities`,
|
|
128
|
+
remediation: vulnerability.fixedIn
|
|
129
|
+
? `Upgrade to ${vulnerability.fixedIn.join(' or ')}`
|
|
130
|
+
: 'No fix available',
|
|
131
|
+
cve: vulnerability.cve,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
this.logger.debug('Dependency scan complete', {
|
|
136
|
+
pluginId: target.pluginId,
|
|
137
|
+
dependencies: Object.keys(target.dependencies).length,
|
|
138
|
+
vulnerabilities: issues.length
|
|
139
|
+
});
|
|
140
|
+
return issues;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Scan for malware patterns
|
|
144
|
+
*/
|
|
145
|
+
async scanMalware(target) {
|
|
146
|
+
const issues = [];
|
|
147
|
+
// In a real implementation, this would:
|
|
148
|
+
// - Check for obfuscated code
|
|
149
|
+
// - Check for suspicious network activity patterns
|
|
150
|
+
// - Check for crypto mining patterns
|
|
151
|
+
// - Check for data exfiltration patterns
|
|
152
|
+
// - Use ML-based malware detection
|
|
153
|
+
// - Check file hashes against known malware databases
|
|
154
|
+
this.logger.debug('Malware scan complete', {
|
|
155
|
+
pluginId: target.pluginId,
|
|
156
|
+
issuesFound: issues.length
|
|
157
|
+
});
|
|
158
|
+
return issues;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check license compliance
|
|
162
|
+
*/
|
|
163
|
+
async scanLicenses(target) {
|
|
164
|
+
const issues = [];
|
|
165
|
+
if (!target.dependencies) {
|
|
166
|
+
return issues;
|
|
167
|
+
}
|
|
168
|
+
// In a real implementation, this would:
|
|
169
|
+
// - Check license compatibility
|
|
170
|
+
// - Detect GPL contamination
|
|
171
|
+
// - Flag proprietary dependencies
|
|
172
|
+
// - Check for missing licenses
|
|
173
|
+
// - Verify SPDX identifiers
|
|
174
|
+
this.logger.debug('License scan complete', {
|
|
175
|
+
pluginId: target.pluginId,
|
|
176
|
+
issuesFound: issues.length
|
|
177
|
+
});
|
|
178
|
+
return issues;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Check configuration security
|
|
182
|
+
*/
|
|
183
|
+
async scanConfiguration(target) {
|
|
184
|
+
const issues = [];
|
|
185
|
+
// In a real implementation, this would:
|
|
186
|
+
// - Check for hardcoded secrets
|
|
187
|
+
// - Check for weak permissions
|
|
188
|
+
// - Check for insecure defaults
|
|
189
|
+
// - Check for missing security headers
|
|
190
|
+
// - Check CSP policies
|
|
191
|
+
this.logger.debug('Configuration scan complete', {
|
|
192
|
+
pluginId: target.pluginId,
|
|
193
|
+
issuesFound: issues.length
|
|
194
|
+
});
|
|
195
|
+
return issues;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Calculate security score based on issues
|
|
199
|
+
*/
|
|
200
|
+
calculateSecurityScore(issues) {
|
|
201
|
+
// Start with perfect score
|
|
202
|
+
let score = 100;
|
|
203
|
+
// Deduct points based on severity
|
|
204
|
+
for (const issue of issues) {
|
|
205
|
+
switch (issue.severity) {
|
|
206
|
+
case 'critical':
|
|
207
|
+
score -= 20;
|
|
208
|
+
break;
|
|
209
|
+
case 'high':
|
|
210
|
+
score -= 10;
|
|
211
|
+
break;
|
|
212
|
+
case 'medium':
|
|
213
|
+
score -= 5;
|
|
214
|
+
break;
|
|
215
|
+
case 'low':
|
|
216
|
+
score -= 2;
|
|
217
|
+
break;
|
|
218
|
+
case 'info':
|
|
219
|
+
score -= 0;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Ensure score doesn't go below 0
|
|
224
|
+
return Math.max(0, score);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Add a vulnerability to the database
|
|
228
|
+
*/
|
|
229
|
+
addVulnerability(packageName, version, vulnerability) {
|
|
230
|
+
const key = `${packageName}@${version}`;
|
|
231
|
+
this.vulnerabilityDb.set(key, vulnerability);
|
|
232
|
+
this.logger.debug('Vulnerability added to database', {
|
|
233
|
+
package: packageName,
|
|
234
|
+
version,
|
|
235
|
+
cve: vulnerability.cve
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get scan result from cache
|
|
240
|
+
*/
|
|
241
|
+
getScanResult(pluginId, version) {
|
|
242
|
+
return this.scanResults.get(`${pluginId}:${version}`);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Clear scan results cache
|
|
246
|
+
*/
|
|
247
|
+
clearCache() {
|
|
248
|
+
this.scanResults.clear();
|
|
249
|
+
this.logger.debug('Scan results cache cleared');
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Update vulnerability database from external source
|
|
253
|
+
*/
|
|
254
|
+
async updateVulnerabilityDatabase() {
|
|
255
|
+
this.logger.info('Updating vulnerability database');
|
|
256
|
+
// In a real implementation, this would:
|
|
257
|
+
// - Fetch from GitHub Advisory Database
|
|
258
|
+
// - Fetch from npm audit
|
|
259
|
+
// - Fetch from NVD (National Vulnerability Database)
|
|
260
|
+
// - Parse and cache vulnerability data
|
|
261
|
+
this.logger.info('Vulnerability database updated', {
|
|
262
|
+
entries: this.vulnerabilityDb.size
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Shutdown security scanner
|
|
267
|
+
*/
|
|
268
|
+
shutdown() {
|
|
269
|
+
this.vulnerabilityDb.clear();
|
|
270
|
+
this.scanResults.clear();
|
|
271
|
+
this.logger.info('Security scanner shutdown complete');
|
|
272
|
+
}
|
|
273
|
+
}
|