network-ai 3.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/LICENSE +21 -0
- package/QUICKSTART.md +260 -0
- package/README.md +604 -0
- package/SKILL.md +568 -0
- package/dist/adapters/adapter-registry.d.ts +94 -0
- package/dist/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/adapters/adapter-registry.js +355 -0
- package/dist/adapters/adapter-registry.js.map +1 -0
- package/dist/adapters/agno-adapter.d.ts +112 -0
- package/dist/adapters/agno-adapter.d.ts.map +1 -0
- package/dist/adapters/agno-adapter.js +140 -0
- package/dist/adapters/agno-adapter.js.map +1 -0
- package/dist/adapters/autogen-adapter.d.ts +67 -0
- package/dist/adapters/autogen-adapter.d.ts.map +1 -0
- package/dist/adapters/autogen-adapter.js +141 -0
- package/dist/adapters/autogen-adapter.js.map +1 -0
- package/dist/adapters/base-adapter.d.ts +51 -0
- package/dist/adapters/base-adapter.d.ts.map +1 -0
- package/dist/adapters/base-adapter.js +103 -0
- package/dist/adapters/base-adapter.js.map +1 -0
- package/dist/adapters/crewai-adapter.d.ts +72 -0
- package/dist/adapters/crewai-adapter.d.ts.map +1 -0
- package/dist/adapters/crewai-adapter.js +148 -0
- package/dist/adapters/crewai-adapter.js.map +1 -0
- package/dist/adapters/custom-adapter.d.ts +74 -0
- package/dist/adapters/custom-adapter.d.ts.map +1 -0
- package/dist/adapters/custom-adapter.js +142 -0
- package/dist/adapters/custom-adapter.js.map +1 -0
- package/dist/adapters/dspy-adapter.d.ts +70 -0
- package/dist/adapters/dspy-adapter.d.ts.map +1 -0
- package/dist/adapters/dspy-adapter.js +127 -0
- package/dist/adapters/dspy-adapter.js.map +1 -0
- package/dist/adapters/haystack-adapter.d.ts +83 -0
- package/dist/adapters/haystack-adapter.d.ts.map +1 -0
- package/dist/adapters/haystack-adapter.js +149 -0
- package/dist/adapters/haystack-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +47 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +56 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/langchain-adapter.d.ts +51 -0
- package/dist/adapters/langchain-adapter.d.ts.map +1 -0
- package/dist/adapters/langchain-adapter.js +134 -0
- package/dist/adapters/langchain-adapter.js.map +1 -0
- package/dist/adapters/llamaindex-adapter.d.ts +89 -0
- package/dist/adapters/llamaindex-adapter.d.ts.map +1 -0
- package/dist/adapters/llamaindex-adapter.js +135 -0
- package/dist/adapters/llamaindex-adapter.js.map +1 -0
- package/dist/adapters/mcp-adapter.d.ts +90 -0
- package/dist/adapters/mcp-adapter.d.ts.map +1 -0
- package/dist/adapters/mcp-adapter.js +200 -0
- package/dist/adapters/mcp-adapter.js.map +1 -0
- package/dist/adapters/openai-assistants-adapter.d.ts +94 -0
- package/dist/adapters/openai-assistants-adapter.d.ts.map +1 -0
- package/dist/adapters/openai-assistants-adapter.js +130 -0
- package/dist/adapters/openai-assistants-adapter.js.map +1 -0
- package/dist/adapters/openclaw-adapter.d.ts +21 -0
- package/dist/adapters/openclaw-adapter.d.ts.map +1 -0
- package/dist/adapters/openclaw-adapter.js +140 -0
- package/dist/adapters/openclaw-adapter.js.map +1 -0
- package/dist/adapters/semantic-kernel-adapter.d.ts +73 -0
- package/dist/adapters/semantic-kernel-adapter.d.ts.map +1 -0
- package/dist/adapters/semantic-kernel-adapter.js +123 -0
- package/dist/adapters/semantic-kernel-adapter.js.map +1 -0
- package/dist/index.d.ts +379 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1428 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/blackboard-validator.d.ts +205 -0
- package/dist/lib/blackboard-validator.d.ts.map +1 -0
- package/dist/lib/blackboard-validator.js +756 -0
- package/dist/lib/blackboard-validator.js.map +1 -0
- package/dist/lib/locked-blackboard.d.ts +174 -0
- package/dist/lib/locked-blackboard.d.ts.map +1 -0
- package/dist/lib/locked-blackboard.js +654 -0
- package/dist/lib/locked-blackboard.js.map +1 -0
- package/dist/lib/swarm-utils.d.ts +136 -0
- package/dist/lib/swarm-utils.d.ts.map +1 -0
- package/dist/lib/swarm-utils.js +510 -0
- package/dist/lib/swarm-utils.js.map +1 -0
- package/dist/security.d.ts +269 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +713 -0
- package/dist/security.js.map +1 -0
- package/package.json +84 -0
- package/scripts/blackboard.py +819 -0
- package/scripts/check_permission.py +331 -0
- package/scripts/revoke_token.py +243 -0
- package/scripts/swarm_guard.py +1140 -0
- package/scripts/validate_token.py +97 -0
- package/types/agent-adapter.d.ts +244 -0
- package/types/openclaw-core.d.ts +52 -0
package/dist/security.js
ADDED
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SwarmOrchestrator Security Module
|
|
4
|
+
*
|
|
5
|
+
* This module addresses security vulnerabilities in the multi-agent system:
|
|
6
|
+
*
|
|
7
|
+
* 1. Token Security - HMAC-signed tokens with expiration
|
|
8
|
+
* 2. Input Sanitization - Prevent injection attacks
|
|
9
|
+
* 3. Rate Limiting - Prevent DoS from rogue agents
|
|
10
|
+
* 4. Audit Integrity - Cryptographically signed audit logs
|
|
11
|
+
* 5. Data Encryption - Encrypt sensitive blackboard entries
|
|
12
|
+
* 6. Permission Hardening - Prevent privilege escalation
|
|
13
|
+
* 7. Path Traversal Protection - Sanitize file paths
|
|
14
|
+
*
|
|
15
|
+
* @module SwarmSecurity
|
|
16
|
+
* @version 1.0.0
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.DEFAULT_CONFIG = exports.SecureSwarmGateway = exports.SecurityError = exports.PermissionHardener = exports.DataEncryptor = exports.SecureAuditLogger = exports.RateLimiter = exports.InputSanitizer = exports.SecureTokenManager = void 0;
|
|
20
|
+
const crypto_1 = require("crypto");
|
|
21
|
+
const fs_1 = require("fs");
|
|
22
|
+
const path_1 = require("path");
|
|
23
|
+
const DEFAULT_CONFIG = {
|
|
24
|
+
tokenSecret: process.env.SWARM_TOKEN_SECRET || (0, crypto_1.randomBytes)(32).toString('hex'),
|
|
25
|
+
tokenAlgorithm: 'sha256',
|
|
26
|
+
maxTokenAge: 300000, // 5 minutes
|
|
27
|
+
maxRequestsPerMinute: 100,
|
|
28
|
+
maxFailedAuthAttempts: 5,
|
|
29
|
+
lockoutDuration: 900000, // 15 minutes
|
|
30
|
+
encryptionKey: process.env.SWARM_ENCRYPTION_KEY || (0, crypto_1.randomBytes)(32).toString('hex'),
|
|
31
|
+
encryptSensitiveData: true,
|
|
32
|
+
signAuditLogs: true,
|
|
33
|
+
auditLogPath: './security-audit.log',
|
|
34
|
+
allowedBasePath: process.cwd(),
|
|
35
|
+
};
|
|
36
|
+
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
37
|
+
class SecureTokenManager {
|
|
38
|
+
config;
|
|
39
|
+
revokedTokens = new Set();
|
|
40
|
+
constructor(config = {}) {
|
|
41
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate a cryptographically signed token
|
|
45
|
+
*/
|
|
46
|
+
generateToken(agentId, resourceType, scope) {
|
|
47
|
+
const tokenId = (0, crypto_1.randomBytes)(16).toString('hex');
|
|
48
|
+
const issuedAt = Date.now();
|
|
49
|
+
const expiresAt = issuedAt + this.config.maxTokenAge;
|
|
50
|
+
// Create token payload
|
|
51
|
+
const payload = `${tokenId}:${agentId}:${resourceType}:${scope}:${issuedAt}:${expiresAt}`;
|
|
52
|
+
// Sign the payload
|
|
53
|
+
const signature = this.sign(payload);
|
|
54
|
+
return {
|
|
55
|
+
tokenId,
|
|
56
|
+
agentId,
|
|
57
|
+
resourceType,
|
|
58
|
+
scope,
|
|
59
|
+
issuedAt,
|
|
60
|
+
expiresAt,
|
|
61
|
+
signature,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate a token's authenticity and expiration
|
|
66
|
+
*/
|
|
67
|
+
validateToken(token) {
|
|
68
|
+
// Check if revoked
|
|
69
|
+
if (this.revokedTokens.has(token.tokenId)) {
|
|
70
|
+
return { valid: false, reason: 'Token has been revoked' };
|
|
71
|
+
}
|
|
72
|
+
// Check expiration
|
|
73
|
+
if (Date.now() > token.expiresAt) {
|
|
74
|
+
return { valid: false, reason: 'Token has expired' };
|
|
75
|
+
}
|
|
76
|
+
// Verify signature
|
|
77
|
+
const payload = `${token.tokenId}:${token.agentId}:${token.resourceType}:${token.scope}:${token.issuedAt}:${token.expiresAt}`;
|
|
78
|
+
const expectedSignature = this.sign(payload);
|
|
79
|
+
if (!this.constantTimeCompare(token.signature, expectedSignature)) {
|
|
80
|
+
return { valid: false, reason: 'Invalid token signature' };
|
|
81
|
+
}
|
|
82
|
+
return { valid: true };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Revoke a token
|
|
86
|
+
*/
|
|
87
|
+
revokeToken(tokenId) {
|
|
88
|
+
this.revokedTokens.add(tokenId);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* HMAC sign a payload
|
|
92
|
+
*/
|
|
93
|
+
sign(payload) {
|
|
94
|
+
return (0, crypto_1.createHmac)(this.config.tokenAlgorithm, this.config.tokenSecret)
|
|
95
|
+
.update(payload)
|
|
96
|
+
.digest('hex');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Constant-time string comparison to prevent timing attacks
|
|
100
|
+
*/
|
|
101
|
+
constantTimeCompare(a, b) {
|
|
102
|
+
if (a.length !== b.length)
|
|
103
|
+
return false;
|
|
104
|
+
let result = 0;
|
|
105
|
+
for (let i = 0; i < a.length; i++) {
|
|
106
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
107
|
+
}
|
|
108
|
+
return result === 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.SecureTokenManager = SecureTokenManager;
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// 2. INPUT SANITIZER
|
|
114
|
+
// ============================================================================
|
|
115
|
+
class InputSanitizer {
|
|
116
|
+
// Dangerous patterns that could indicate injection attempts
|
|
117
|
+
static DANGEROUS_PATTERNS = [
|
|
118
|
+
/\$\{.*\}/g, // Template injection
|
|
119
|
+
/<script.*?>.*?<\/script>/gi, // XSS
|
|
120
|
+
/javascript:/gi, // JavaScript protocol
|
|
121
|
+
/on\w+\s*=/gi, // Event handlers
|
|
122
|
+
/\.\.\//g, // Path traversal
|
|
123
|
+
/[;&|`$]/g, // Command injection chars
|
|
124
|
+
/__proto__/gi, // Prototype pollution
|
|
125
|
+
/constructor/gi, // Prototype pollution
|
|
126
|
+
];
|
|
127
|
+
/**
|
|
128
|
+
* Sanitize a string input
|
|
129
|
+
*/
|
|
130
|
+
static sanitizeString(input, maxLength = 10000) {
|
|
131
|
+
if (typeof input !== 'string') {
|
|
132
|
+
throw new SecurityError('Input must be a string', 'INVALID_INPUT_TYPE');
|
|
133
|
+
}
|
|
134
|
+
// Truncate to max length
|
|
135
|
+
let sanitized = input.slice(0, maxLength);
|
|
136
|
+
// Remove dangerous patterns
|
|
137
|
+
for (const pattern of this.DANGEROUS_PATTERNS) {
|
|
138
|
+
sanitized = sanitized.replace(pattern, '');
|
|
139
|
+
}
|
|
140
|
+
// Encode special characters
|
|
141
|
+
sanitized = sanitized
|
|
142
|
+
.replace(/&/g, '&')
|
|
143
|
+
.replace(/</g, '<')
|
|
144
|
+
.replace(/>/g, '>')
|
|
145
|
+
.replace(/"/g, '"')
|
|
146
|
+
.replace(/'/g, ''');
|
|
147
|
+
return sanitized;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Sanitize an object recursively
|
|
151
|
+
*/
|
|
152
|
+
static sanitizeObject(obj, depth = 0, maxDepth = 10) {
|
|
153
|
+
if (depth > maxDepth) {
|
|
154
|
+
throw new SecurityError('Object nesting too deep', 'MAX_DEPTH_EXCEEDED');
|
|
155
|
+
}
|
|
156
|
+
if (obj === null || obj === undefined) {
|
|
157
|
+
return obj;
|
|
158
|
+
}
|
|
159
|
+
if (typeof obj === 'string') {
|
|
160
|
+
return this.sanitizeString(obj);
|
|
161
|
+
}
|
|
162
|
+
if (typeof obj === 'number' || typeof obj === 'boolean') {
|
|
163
|
+
return obj;
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(obj)) {
|
|
166
|
+
return obj.map(item => this.sanitizeObject(item, depth + 1, maxDepth));
|
|
167
|
+
}
|
|
168
|
+
if (typeof obj === 'object') {
|
|
169
|
+
const sanitized = {};
|
|
170
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
171
|
+
// Sanitize keys too
|
|
172
|
+
const sanitizedKey = this.sanitizeString(key, 100);
|
|
173
|
+
// Block prototype pollution attempts
|
|
174
|
+
if (sanitizedKey === '__proto__' || sanitizedKey === 'constructor' || sanitizedKey === 'prototype') {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
sanitized[sanitizedKey] = this.sanitizeObject(value, depth + 1, maxDepth);
|
|
178
|
+
}
|
|
179
|
+
return sanitized;
|
|
180
|
+
}
|
|
181
|
+
return undefined; // Unknown types are dropped
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Validate and sanitize an agent ID
|
|
185
|
+
*/
|
|
186
|
+
static sanitizeAgentId(agentId) {
|
|
187
|
+
if (typeof agentId !== 'string' || agentId.length === 0) {
|
|
188
|
+
throw new SecurityError('Invalid agent ID', 'INVALID_AGENT_ID');
|
|
189
|
+
}
|
|
190
|
+
// Agent IDs should be alphanumeric with underscores/hyphens only
|
|
191
|
+
const sanitized = agentId.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
192
|
+
if (sanitized.length === 0 || sanitized.length > 64) {
|
|
193
|
+
throw new SecurityError('Agent ID format invalid', 'INVALID_AGENT_ID_FORMAT');
|
|
194
|
+
}
|
|
195
|
+
return sanitized;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Validate and sanitize a file path
|
|
199
|
+
*/
|
|
200
|
+
static sanitizePath(inputPath, basePath) {
|
|
201
|
+
// Normalize the path
|
|
202
|
+
const normalized = (0, path_1.normalize)(inputPath);
|
|
203
|
+
// Resolve to absolute
|
|
204
|
+
const absolute = (0, path_1.isAbsolute)(normalized)
|
|
205
|
+
? normalized
|
|
206
|
+
: (0, path_1.join)(basePath, normalized);
|
|
207
|
+
// Ensure it's within the allowed base path
|
|
208
|
+
const resolvedBase = (0, path_1.normalize)(basePath);
|
|
209
|
+
const resolvedPath = (0, path_1.normalize)(absolute);
|
|
210
|
+
if (!resolvedPath.startsWith(resolvedBase)) {
|
|
211
|
+
throw new SecurityError('Path traversal attempt detected', 'PATH_TRAVERSAL');
|
|
212
|
+
}
|
|
213
|
+
return resolvedPath;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.InputSanitizer = InputSanitizer;
|
|
217
|
+
class RateLimiter {
|
|
218
|
+
limits = new Map();
|
|
219
|
+
config;
|
|
220
|
+
constructor(config = {}) {
|
|
221
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check if an agent is rate limited
|
|
225
|
+
*/
|
|
226
|
+
isRateLimited(agentId) {
|
|
227
|
+
const entry = this.limits.get(agentId);
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
if (!entry) {
|
|
230
|
+
this.limits.set(agentId, {
|
|
231
|
+
count: 1,
|
|
232
|
+
windowStart: now,
|
|
233
|
+
failedAttempts: 0,
|
|
234
|
+
lockedUntil: null,
|
|
235
|
+
});
|
|
236
|
+
return { limited: false };
|
|
237
|
+
}
|
|
238
|
+
// Check if locked out
|
|
239
|
+
if (entry.lockedUntil && now < entry.lockedUntil) {
|
|
240
|
+
return {
|
|
241
|
+
limited: true,
|
|
242
|
+
retryAfter: Math.ceil((entry.lockedUntil - now) / 1000)
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Reset window if expired (1 minute)
|
|
246
|
+
if (now - entry.windowStart > 60000) {
|
|
247
|
+
entry.count = 1;
|
|
248
|
+
entry.windowStart = now;
|
|
249
|
+
entry.lockedUntil = null;
|
|
250
|
+
return { limited: false };
|
|
251
|
+
}
|
|
252
|
+
// Increment counter
|
|
253
|
+
entry.count++;
|
|
254
|
+
// Check if over limit
|
|
255
|
+
if (entry.count > this.config.maxRequestsPerMinute) {
|
|
256
|
+
return {
|
|
257
|
+
limited: true,
|
|
258
|
+
retryAfter: Math.ceil((entry.windowStart + 60000 - now) / 1000)
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return { limited: false };
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Record a failed authentication attempt
|
|
265
|
+
*/
|
|
266
|
+
recordFailedAuth(agentId) {
|
|
267
|
+
const entry = this.limits.get(agentId) || {
|
|
268
|
+
count: 0,
|
|
269
|
+
windowStart: Date.now(),
|
|
270
|
+
failedAttempts: 0,
|
|
271
|
+
lockedUntil: null,
|
|
272
|
+
};
|
|
273
|
+
entry.failedAttempts++;
|
|
274
|
+
if (entry.failedAttempts >= this.config.maxFailedAuthAttempts) {
|
|
275
|
+
entry.lockedUntil = Date.now() + this.config.lockoutDuration;
|
|
276
|
+
this.limits.set(agentId, entry);
|
|
277
|
+
return { locked: true };
|
|
278
|
+
}
|
|
279
|
+
this.limits.set(agentId, entry);
|
|
280
|
+
return {
|
|
281
|
+
locked: false,
|
|
282
|
+
attemptsRemaining: this.config.maxFailedAuthAttempts - entry.failedAttempts
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Reset failed attempts after successful auth
|
|
287
|
+
*/
|
|
288
|
+
resetFailedAttempts(agentId) {
|
|
289
|
+
const entry = this.limits.get(agentId);
|
|
290
|
+
if (entry) {
|
|
291
|
+
entry.failedAttempts = 0;
|
|
292
|
+
entry.lockedUntil = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get rate limit status for an agent
|
|
297
|
+
*/
|
|
298
|
+
getStatus(agentId) {
|
|
299
|
+
return this.limits.get(agentId) || null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
exports.RateLimiter = RateLimiter;
|
|
303
|
+
class SecureAuditLogger {
|
|
304
|
+
config;
|
|
305
|
+
previousHash = '';
|
|
306
|
+
constructor(config = {}) {
|
|
307
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
308
|
+
this.initializeLog();
|
|
309
|
+
}
|
|
310
|
+
initializeLog() {
|
|
311
|
+
const logPath = this.config.auditLogPath;
|
|
312
|
+
if (!(0, fs_1.existsSync)(logPath)) {
|
|
313
|
+
(0, fs_1.writeFileSync)(logPath, '');
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// Continue the hash chain from the last entry so integrity
|
|
317
|
+
// verification works across process restarts.
|
|
318
|
+
try {
|
|
319
|
+
const content = (0, fs_1.readFileSync)(logPath, 'utf-8').trim();
|
|
320
|
+
if (content) {
|
|
321
|
+
const lines = content.split('\n').filter((l) => l);
|
|
322
|
+
const lastLine = lines[lines.length - 1];
|
|
323
|
+
const lastEntry = JSON.parse(lastLine);
|
|
324
|
+
if (lastEntry.signature) {
|
|
325
|
+
this.previousHash = lastEntry.signature;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// If we can't read the last entry, start fresh chain
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Log a security event with cryptographic integrity
|
|
336
|
+
*/
|
|
337
|
+
log(eventType, agentId, action, outcome, details = {}, resource) {
|
|
338
|
+
const entry = {
|
|
339
|
+
timestamp: new Date().toISOString(),
|
|
340
|
+
eventId: (0, crypto_1.randomBytes)(8).toString('hex'),
|
|
341
|
+
eventType,
|
|
342
|
+
agentId: InputSanitizer.sanitizeAgentId(agentId),
|
|
343
|
+
action,
|
|
344
|
+
resource,
|
|
345
|
+
outcome,
|
|
346
|
+
details: InputSanitizer.sanitizeObject(details),
|
|
347
|
+
};
|
|
348
|
+
// Sign the entry if configured
|
|
349
|
+
if (this.config.signAuditLogs) {
|
|
350
|
+
const payload = JSON.stringify({
|
|
351
|
+
...entry,
|
|
352
|
+
previousHash: this.previousHash,
|
|
353
|
+
});
|
|
354
|
+
entry.signature = (0, crypto_1.createHmac)('sha256', this.config.tokenSecret)
|
|
355
|
+
.update(payload)
|
|
356
|
+
.digest('hex');
|
|
357
|
+
this.previousHash = entry.signature ?? '';
|
|
358
|
+
}
|
|
359
|
+
// Append to log file
|
|
360
|
+
const logLine = JSON.stringify(entry) + '\n';
|
|
361
|
+
(0, fs_1.appendFileSync)(this.config.auditLogPath, logLine);
|
|
362
|
+
return entry;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Log a permission request
|
|
366
|
+
*/
|
|
367
|
+
logPermissionRequest(agentId, resourceType, scope, granted, reason) {
|
|
368
|
+
this.log('PERMISSION_REQUEST', agentId, `request_${resourceType}`, granted ? 'success' : 'denied', { resourceType, scope, reason }, resourceType);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Log a security violation
|
|
372
|
+
*/
|
|
373
|
+
logViolation(agentId, violationType, details) {
|
|
374
|
+
this.log('SECURITY_VIOLATION', agentId, violationType, 'denied', details);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Verify audit log integrity
|
|
378
|
+
*/
|
|
379
|
+
verifyLogIntegrity() {
|
|
380
|
+
const logContent = (0, fs_1.readFileSync)(this.config.auditLogPath, 'utf-8');
|
|
381
|
+
const lines = logContent.trim().split('\n').filter((l) => l);
|
|
382
|
+
let previousHash = '';
|
|
383
|
+
const invalidEntries = [];
|
|
384
|
+
for (let i = 0; i < lines.length; i++) {
|
|
385
|
+
try {
|
|
386
|
+
const entry = JSON.parse(lines[i]);
|
|
387
|
+
if (entry.signature) {
|
|
388
|
+
const { signature, ...rest } = entry;
|
|
389
|
+
const payload = JSON.stringify({
|
|
390
|
+
...rest,
|
|
391
|
+
previousHash,
|
|
392
|
+
});
|
|
393
|
+
const expectedSignature = (0, crypto_1.createHmac)('sha256', this.config.tokenSecret)
|
|
394
|
+
.update(payload)
|
|
395
|
+
.digest('hex');
|
|
396
|
+
if (signature !== expectedSignature) {
|
|
397
|
+
invalidEntries.push(i);
|
|
398
|
+
}
|
|
399
|
+
previousHash = signature;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
invalidEntries.push(i);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
valid: invalidEntries.length === 0,
|
|
408
|
+
invalidEntries,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
exports.SecureAuditLogger = SecureAuditLogger;
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// 5. DATA ENCRYPTION
|
|
415
|
+
// ============================================================================
|
|
416
|
+
class DataEncryptor {
|
|
417
|
+
key;
|
|
418
|
+
algorithm = 'aes-256-gcm';
|
|
419
|
+
constructor(encryptionKey) {
|
|
420
|
+
// Derive a proper key from the provided key
|
|
421
|
+
this.key = (0, crypto_1.scryptSync)(encryptionKey, 'swarm-salt', 32);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Encrypt sensitive data
|
|
425
|
+
*/
|
|
426
|
+
encrypt(data) {
|
|
427
|
+
const iv = (0, crypto_1.randomBytes)(16);
|
|
428
|
+
const cipher = (0, crypto_1.createCipheriv)(this.algorithm, this.key, iv);
|
|
429
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
430
|
+
encrypted += cipher.final('hex');
|
|
431
|
+
const authTag = cipher.getAuthTag();
|
|
432
|
+
// Return iv:authTag:encryptedData
|
|
433
|
+
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Decrypt sensitive data
|
|
437
|
+
*/
|
|
438
|
+
decrypt(encryptedData) {
|
|
439
|
+
const parts = encryptedData.split(':');
|
|
440
|
+
if (parts.length !== 3) {
|
|
441
|
+
throw new SecurityError('Invalid encrypted data format', 'INVALID_ENCRYPTED_FORMAT');
|
|
442
|
+
}
|
|
443
|
+
const [ivHex, authTagHex, encrypted] = parts;
|
|
444
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
445
|
+
const authTag = Buffer.from(authTagHex, 'hex');
|
|
446
|
+
const decipher = (0, crypto_1.createDecipheriv)(this.algorithm, this.key, iv);
|
|
447
|
+
decipher.setAuthTag(authTag);
|
|
448
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
449
|
+
decrypted += decipher.final('utf8');
|
|
450
|
+
return decrypted;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Encrypt an object
|
|
454
|
+
*/
|
|
455
|
+
encryptObject(obj) {
|
|
456
|
+
return this.encrypt(JSON.stringify(obj));
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Decrypt to object
|
|
460
|
+
*/
|
|
461
|
+
decryptObject(encryptedData) {
|
|
462
|
+
return JSON.parse(this.decrypt(encryptedData));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
exports.DataEncryptor = DataEncryptor;
|
|
466
|
+
class PermissionHardener {
|
|
467
|
+
trustPolicies = new Map();
|
|
468
|
+
auditLogger;
|
|
469
|
+
constructor(auditLogger, defaultPolicies) {
|
|
470
|
+
this.auditLogger = auditLogger;
|
|
471
|
+
this.initializeDefaultPolicies(defaultPolicies);
|
|
472
|
+
}
|
|
473
|
+
initializeDefaultPolicies(customPolicies) {
|
|
474
|
+
if (customPolicies && customPolicies.length > 0) {
|
|
475
|
+
for (const policy of customPolicies) {
|
|
476
|
+
this.trustPolicies.set(policy.agentId, {
|
|
477
|
+
agentId: policy.agentId,
|
|
478
|
+
trustLevel: policy.trustLevel,
|
|
479
|
+
allowedResources: policy.allowedResources,
|
|
480
|
+
maxScope: policy.maxScope ?? ['read'],
|
|
481
|
+
createdBy: 'SYSTEM',
|
|
482
|
+
immutable: policy.immutable ?? false,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// Fallback: universal defaults that cover common domains
|
|
488
|
+
this.trustPolicies.set('orchestrator', {
|
|
489
|
+
agentId: 'orchestrator',
|
|
490
|
+
trustLevel: 0.9,
|
|
491
|
+
allowedResources: ['*'],
|
|
492
|
+
maxScope: ['read', 'write', 'execute', 'delegate'],
|
|
493
|
+
createdBy: 'SYSTEM',
|
|
494
|
+
immutable: true,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Register or update a trust policy for an agent at runtime.
|
|
499
|
+
*/
|
|
500
|
+
registerPolicy(policy) {
|
|
501
|
+
const existing = this.trustPolicies.get(policy.agentId);
|
|
502
|
+
if (existing?.immutable)
|
|
503
|
+
return; // Cannot overwrite immutable policies
|
|
504
|
+
this.trustPolicies.set(policy.agentId, {
|
|
505
|
+
agentId: policy.agentId,
|
|
506
|
+
trustLevel: policy.trustLevel,
|
|
507
|
+
allowedResources: policy.allowedResources,
|
|
508
|
+
maxScope: policy.maxScope ?? ['read'],
|
|
509
|
+
createdBy: 'RUNTIME',
|
|
510
|
+
immutable: policy.immutable ?? false,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Check if an agent can access a resource
|
|
515
|
+
*/
|
|
516
|
+
canAccess(agentId, resourceType, requestedScope) {
|
|
517
|
+
const policy = this.trustPolicies.get(agentId);
|
|
518
|
+
if (!policy) {
|
|
519
|
+
this.auditLogger.logViolation(agentId, 'UNKNOWN_AGENT', { resourceType, requestedScope });
|
|
520
|
+
return { allowed: false, reason: 'Agent has no trust policy' };
|
|
521
|
+
}
|
|
522
|
+
// Check resource access (support '*' wildcard)
|
|
523
|
+
if (!policy.allowedResources.includes('*') && !policy.allowedResources.includes(resourceType)) {
|
|
524
|
+
this.auditLogger.logViolation(agentId, 'RESOURCE_NOT_ALLOWED', {
|
|
525
|
+
resourceType,
|
|
526
|
+
allowedResources: policy.allowedResources
|
|
527
|
+
});
|
|
528
|
+
return { allowed: false, reason: `Agent not allowed to access ${resourceType}` };
|
|
529
|
+
}
|
|
530
|
+
// Check scope
|
|
531
|
+
const scopeMatch = policy.maxScope.some(s => requestedScope.startsWith(s));
|
|
532
|
+
if (!scopeMatch) {
|
|
533
|
+
this.auditLogger.logViolation(agentId, 'SCOPE_EXCEEDED', {
|
|
534
|
+
requestedScope,
|
|
535
|
+
maxScope: policy.maxScope,
|
|
536
|
+
});
|
|
537
|
+
return { allowed: false, reason: 'Requested scope exceeds allowed scope' };
|
|
538
|
+
}
|
|
539
|
+
return { allowed: true };
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Attempt to modify trust level (with escalation prevention)
|
|
543
|
+
*/
|
|
544
|
+
modifyTrustLevel(requestingAgent, targetAgent, newTrustLevel) {
|
|
545
|
+
const requestorPolicy = this.trustPolicies.get(requestingAgent);
|
|
546
|
+
const targetPolicy = this.trustPolicies.get(targetAgent);
|
|
547
|
+
// Only orchestrator can modify trust
|
|
548
|
+
if (requestingAgent !== 'orchestrator') {
|
|
549
|
+
this.auditLogger.logViolation(requestingAgent, 'UNAUTHORIZED_TRUST_MODIFICATION', {
|
|
550
|
+
targetAgent,
|
|
551
|
+
attemptedTrustLevel: newTrustLevel,
|
|
552
|
+
});
|
|
553
|
+
return { success: false, reason: 'Only orchestrator can modify trust levels' };
|
|
554
|
+
}
|
|
555
|
+
// Cannot modify immutable policies
|
|
556
|
+
if (targetPolicy?.immutable) {
|
|
557
|
+
return { success: false, reason: 'Cannot modify immutable policy' };
|
|
558
|
+
}
|
|
559
|
+
// Cannot set trust higher than your own
|
|
560
|
+
if (requestorPolicy && newTrustLevel > requestorPolicy.trustLevel) {
|
|
561
|
+
this.auditLogger.logViolation(requestingAgent, 'PRIVILEGE_ESCALATION_ATTEMPT', {
|
|
562
|
+
targetAgent,
|
|
563
|
+
attemptedTrustLevel: newTrustLevel,
|
|
564
|
+
requestorTrustLevel: requestorPolicy.trustLevel,
|
|
565
|
+
});
|
|
566
|
+
return { success: false, reason: 'Cannot grant trust level higher than your own' };
|
|
567
|
+
}
|
|
568
|
+
// Apply the modification
|
|
569
|
+
if (targetPolicy) {
|
|
570
|
+
targetPolicy.trustLevel = newTrustLevel;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
this.trustPolicies.set(targetAgent, {
|
|
574
|
+
agentId: targetAgent,
|
|
575
|
+
trustLevel: newTrustLevel,
|
|
576
|
+
allowedResources: [],
|
|
577
|
+
maxScope: ['read'],
|
|
578
|
+
createdBy: requestingAgent,
|
|
579
|
+
immutable: false,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return { success: true };
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Get policy for an agent
|
|
586
|
+
*/
|
|
587
|
+
getPolicy(agentId) {
|
|
588
|
+
return this.trustPolicies.get(agentId);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
exports.PermissionHardener = PermissionHardener;
|
|
592
|
+
// ============================================================================
|
|
593
|
+
// 7. SECURITY ERROR CLASS
|
|
594
|
+
// ============================================================================
|
|
595
|
+
class SecurityError extends Error {
|
|
596
|
+
code;
|
|
597
|
+
constructor(message, code) {
|
|
598
|
+
super(message);
|
|
599
|
+
this.name = 'SecurityError';
|
|
600
|
+
this.code = code;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
exports.SecurityError = SecurityError;
|
|
604
|
+
// ============================================================================
|
|
605
|
+
// 8. SECURE SWARM GATEWAY (Integration Point)
|
|
606
|
+
// ============================================================================
|
|
607
|
+
class SecureSwarmGateway {
|
|
608
|
+
tokenManager;
|
|
609
|
+
rateLimiter;
|
|
610
|
+
auditLogger;
|
|
611
|
+
permissionHardener;
|
|
612
|
+
encryptor;
|
|
613
|
+
constructor(config = {}) {
|
|
614
|
+
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
|
615
|
+
this.tokenManager = new SecureTokenManager(fullConfig);
|
|
616
|
+
this.rateLimiter = new RateLimiter(fullConfig);
|
|
617
|
+
this.auditLogger = new SecureAuditLogger(fullConfig);
|
|
618
|
+
this.permissionHardener = new PermissionHardener(this.auditLogger);
|
|
619
|
+
this.encryptor = new DataEncryptor(fullConfig.encryptionKey);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Secure request handler - validates all security requirements
|
|
623
|
+
*/
|
|
624
|
+
async handleSecureRequest(agentId, action, params, token) {
|
|
625
|
+
// 1. Sanitize agent ID
|
|
626
|
+
let sanitizedAgentId;
|
|
627
|
+
try {
|
|
628
|
+
sanitizedAgentId = InputSanitizer.sanitizeAgentId(agentId);
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
this.auditLogger.logViolation(agentId, 'INVALID_AGENT_ID', { error: String(error) });
|
|
632
|
+
return { allowed: false, reason: 'Invalid agent ID' };
|
|
633
|
+
}
|
|
634
|
+
// 2. Check rate limit
|
|
635
|
+
const rateLimit = this.rateLimiter.isRateLimited(sanitizedAgentId);
|
|
636
|
+
if (rateLimit.limited) {
|
|
637
|
+
this.auditLogger.log('RATE_LIMITED', sanitizedAgentId, action, 'denied', {
|
|
638
|
+
retryAfter: rateLimit.retryAfter,
|
|
639
|
+
});
|
|
640
|
+
return { allowed: false, reason: `Rate limited. Retry after ${rateLimit.retryAfter}s` };
|
|
641
|
+
}
|
|
642
|
+
// 3. Validate token if provided
|
|
643
|
+
if (token) {
|
|
644
|
+
const tokenValidation = this.tokenManager.validateToken(token);
|
|
645
|
+
if (!tokenValidation.valid) {
|
|
646
|
+
const failedAuth = this.rateLimiter.recordFailedAuth(sanitizedAgentId);
|
|
647
|
+
this.auditLogger.log('TOKEN_VALIDATION_FAILED', sanitizedAgentId, action, 'denied', {
|
|
648
|
+
reason: tokenValidation.reason,
|
|
649
|
+
locked: failedAuth.locked,
|
|
650
|
+
});
|
|
651
|
+
if (failedAuth.locked) {
|
|
652
|
+
return { allowed: false, reason: 'Account locked due to failed authentication attempts' };
|
|
653
|
+
}
|
|
654
|
+
return { allowed: false, reason: tokenValidation.reason };
|
|
655
|
+
}
|
|
656
|
+
// Reset failed attempts on successful validation
|
|
657
|
+
this.rateLimiter.resetFailedAttempts(sanitizedAgentId);
|
|
658
|
+
}
|
|
659
|
+
// 4. Sanitize parameters
|
|
660
|
+
let sanitizedParams;
|
|
661
|
+
try {
|
|
662
|
+
sanitizedParams = InputSanitizer.sanitizeObject(params);
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
this.auditLogger.logViolation(sanitizedAgentId, 'MALICIOUS_INPUT', {
|
|
666
|
+
action,
|
|
667
|
+
error: String(error),
|
|
668
|
+
});
|
|
669
|
+
return { allowed: false, reason: 'Invalid input parameters' };
|
|
670
|
+
}
|
|
671
|
+
// 5. Log successful request
|
|
672
|
+
this.auditLogger.log('REQUEST_PROCESSED', sanitizedAgentId, action, 'success', {
|
|
673
|
+
paramKeys: Object.keys(sanitizedParams),
|
|
674
|
+
});
|
|
675
|
+
return { allowed: true, sanitizedParams };
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Request a new permission grant
|
|
679
|
+
*/
|
|
680
|
+
async requestPermission(agentId, resourceType, scope, justification) {
|
|
681
|
+
const sanitizedAgentId = InputSanitizer.sanitizeAgentId(agentId);
|
|
682
|
+
// Check if agent can access this resource
|
|
683
|
+
const accessCheck = this.permissionHardener.canAccess(sanitizedAgentId, resourceType, scope);
|
|
684
|
+
if (!accessCheck.allowed) {
|
|
685
|
+
this.auditLogger.logPermissionRequest(sanitizedAgentId, resourceType, scope, false, accessCheck.reason);
|
|
686
|
+
return { granted: false, reason: accessCheck.reason };
|
|
687
|
+
}
|
|
688
|
+
// Generate secure token
|
|
689
|
+
const token = this.tokenManager.generateToken(sanitizedAgentId, resourceType, scope);
|
|
690
|
+
this.auditLogger.logPermissionRequest(sanitizedAgentId, resourceType, scope, true);
|
|
691
|
+
return { granted: true, token };
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Encrypt sensitive data for blackboard storage
|
|
695
|
+
*/
|
|
696
|
+
encryptSensitiveData(data) {
|
|
697
|
+
return this.encryptor.encryptObject(data);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Decrypt sensitive data from blackboard
|
|
701
|
+
*/
|
|
702
|
+
decryptSensitiveData(encryptedData) {
|
|
703
|
+
return this.encryptor.decryptObject(encryptedData);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Verify audit log integrity
|
|
707
|
+
*/
|
|
708
|
+
verifyAuditIntegrity() {
|
|
709
|
+
return this.auditLogger.verifyLogIntegrity();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
exports.SecureSwarmGateway = SecureSwarmGateway;
|
|
713
|
+
//# sourceMappingURL=security.js.map
|