myaidev-method 0.2.19 → 0.2.22
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/.claude/mcp/sparc-orchestrator-server.js +0 -0
- package/.claude/mcp/wordpress-server.js +0 -0
- package/CHANGELOG.md +123 -5
- package/README.md +205 -13
- package/TECHNICAL_ARCHITECTURE.md +64 -2
- package/bin/cli.js +169 -2
- package/dist/mcp/mcp-config.json +138 -1
- package/dist/mcp/openstack-server.js +1607 -0
- package/package.json +2 -2
- package/src/config/workflows.js +532 -0
- package/src/lib/payloadcms-utils.js +206 -0
- package/src/lib/visual-generation-utils.js +445 -294
- package/src/lib/workflow-installer.js +512 -0
- package/src/libs/security/authorization-checker.js +606 -0
- package/src/mcp/openstack-server.js +1607 -0
- package/src/scripts/openstack-setup.sh +110 -0
- package/src/scripts/security/environment-detect.js +425 -0
- package/src/templates/claude/agents/openstack-vm-manager.md +281 -0
- package/src/templates/claude/agents/osint-researcher.md +1075 -0
- package/src/templates/claude/agents/penetration-tester.md +908 -0
- package/src/templates/claude/agents/security-auditor.md +244 -0
- package/src/templates/claude/agents/security-setup.md +1094 -0
- package/src/templates/claude/agents/webapp-security-tester.md +581 -0
- package/src/templates/claude/commands/myai-configure.md +84 -0
- package/src/templates/claude/commands/myai-openstack.md +229 -0
- package/src/templates/claude/commands/sc:security-exploit.md +464 -0
- package/src/templates/claude/commands/sc:security-recon.md +281 -0
- package/src/templates/claude/commands/sc:security-report.md +756 -0
- package/src/templates/claude/commands/sc:security-scan.md +441 -0
- package/src/templates/claude/commands/sc:security-setup.md +501 -0
- package/src/templates/claude/mcp_config.json +44 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MyAIDev Method - Security Authorization Checker
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: This module enforces authorization requirements for all security testing operations.
|
|
5
|
+
* No security tool should be executed without passing authorization validation.
|
|
6
|
+
*
|
|
7
|
+
* @module security/authorization-checker
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import crypto from 'crypto';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Authorization manifest file location
|
|
17
|
+
* This file MUST exist and contain valid authorization data before any security testing
|
|
18
|
+
*/
|
|
19
|
+
const AUTH_MANIFEST_FILE = '.security-authorization.json';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Authorization levels
|
|
23
|
+
*/
|
|
24
|
+
const AuthLevel = {
|
|
25
|
+
NONE: 'none',
|
|
26
|
+
PASSIVE: 'passive', // OSINT only, no direct target interaction
|
|
27
|
+
ACTIVE: 'active', // Active reconnaissance and scanning
|
|
28
|
+
EXPLOITATION: 'exploitation', // Full penetration testing including exploitation
|
|
29
|
+
INTERNAL: 'internal' // Internal network testing
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Scope types
|
|
34
|
+
*/
|
|
35
|
+
const ScopeType = {
|
|
36
|
+
DOMAIN: 'domain',
|
|
37
|
+
IP_RANGE: 'ip_range',
|
|
38
|
+
URL: 'url',
|
|
39
|
+
NETWORK: 'network',
|
|
40
|
+
APPLICATION: 'application'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Authorization Checker Class
|
|
45
|
+
*/
|
|
46
|
+
class AuthorizationChecker {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.manifest = null;
|
|
49
|
+
this.manifestPath = null;
|
|
50
|
+
this.loaded = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Load authorization manifest from file
|
|
55
|
+
* @param {string} [customPath] - Optional custom path to manifest file
|
|
56
|
+
* @returns {Promise<Object>} - Loaded manifest
|
|
57
|
+
* @throws {Error} - If manifest not found or invalid
|
|
58
|
+
*/
|
|
59
|
+
async loadManifest(customPath = null) {
|
|
60
|
+
try {
|
|
61
|
+
// Determine manifest path
|
|
62
|
+
this.manifestPath = customPath || path.join(process.cwd(), AUTH_MANIFEST_FILE);
|
|
63
|
+
|
|
64
|
+
// Check if file exists
|
|
65
|
+
try {
|
|
66
|
+
await fs.access(this.manifestPath);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Authorization manifest not found at ${this.manifestPath}\n` +
|
|
70
|
+
`\nCRITICAL: Security testing requires explicit authorization.\n` +
|
|
71
|
+
`Create ${AUTH_MANIFEST_FILE} with proper authorization details.\n` +
|
|
72
|
+
`\nExample:\n` +
|
|
73
|
+
`{\n` +
|
|
74
|
+
` "engagement_id": "ENG-2025-001",\n` +
|
|
75
|
+
` "client": "Client Name",\n` +
|
|
76
|
+
` "authorized_by": "John Smith",\n` +
|
|
77
|
+
` "authorization_level": "exploitation",\n` +
|
|
78
|
+
` "scope": [...],\n` +
|
|
79
|
+
` "start_date": "2025-11-25",\n` +
|
|
80
|
+
` "end_date": "2025-12-25"\n` +
|
|
81
|
+
`}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Read and parse manifest
|
|
86
|
+
const manifestData = await fs.readFile(this.manifestPath, 'utf8');
|
|
87
|
+
this.manifest = JSON.parse(manifestData);
|
|
88
|
+
|
|
89
|
+
// Validate manifest structure
|
|
90
|
+
await this.validateManifest(this.manifest);
|
|
91
|
+
|
|
92
|
+
this.loaded = true;
|
|
93
|
+
return this.manifest;
|
|
94
|
+
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err.name === 'SyntaxError') {
|
|
97
|
+
throw new Error(`Invalid JSON in authorization manifest: ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate authorization manifest structure and content
|
|
105
|
+
* @param {Object} manifest - Authorization manifest to validate
|
|
106
|
+
* @throws {Error} - If manifest is invalid
|
|
107
|
+
*/
|
|
108
|
+
async validateManifest(manifest) {
|
|
109
|
+
const requiredFields = [
|
|
110
|
+
'engagement_id',
|
|
111
|
+
'client',
|
|
112
|
+
'authorized_by',
|
|
113
|
+
'authorization_level',
|
|
114
|
+
'scope',
|
|
115
|
+
'start_date',
|
|
116
|
+
'end_date'
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
// Check required fields
|
|
120
|
+
for (const field of requiredFields) {
|
|
121
|
+
if (!manifest[field]) {
|
|
122
|
+
throw new Error(`Missing required field in authorization manifest: ${field}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate authorization level
|
|
127
|
+
const validLevels = Object.values(AuthLevel);
|
|
128
|
+
if (!validLevels.includes(manifest.authorization_level)) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Invalid authorization_level: ${manifest.authorization_level}\n` +
|
|
131
|
+
`Must be one of: ${validLevels.join(', ')}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Validate scope is an array
|
|
136
|
+
if (!Array.isArray(manifest.scope) || manifest.scope.length === 0) {
|
|
137
|
+
throw new Error('Scope must be a non-empty array of authorized targets');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Validate each scope item
|
|
141
|
+
for (const scopeItem of manifest.scope) {
|
|
142
|
+
if (!scopeItem.type || !scopeItem.target) {
|
|
143
|
+
throw new Error('Each scope item must have "type" and "target" fields');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const validTypes = Object.values(ScopeType);
|
|
147
|
+
if (!validTypes.includes(scopeItem.type)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Invalid scope type: ${scopeItem.type}\n` +
|
|
150
|
+
`Must be one of: ${validTypes.join(', ')}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Validate dates
|
|
156
|
+
const startDate = new Date(manifest.start_date);
|
|
157
|
+
const endDate = new Date(manifest.end_date);
|
|
158
|
+
const now = new Date();
|
|
159
|
+
|
|
160
|
+
if (isNaN(startDate.getTime())) {
|
|
161
|
+
throw new Error(`Invalid start_date format: ${manifest.start_date}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (isNaN(endDate.getTime())) {
|
|
165
|
+
throw new Error(`Invalid end_date format: ${manifest.end_date}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (endDate <= startDate) {
|
|
169
|
+
throw new Error('end_date must be after start_date');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check if engagement is active
|
|
173
|
+
if (now < startDate) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Engagement has not started yet.\n` +
|
|
176
|
+
`Start date: ${manifest.start_date}\n` +
|
|
177
|
+
`Current date: ${now.toISOString().split('T')[0]}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (now > endDate) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Engagement has expired.\n` +
|
|
184
|
+
`End date: ${manifest.end_date}\n` +
|
|
185
|
+
`Current date: ${now.toISOString().split('T')[0]}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check if a target is within authorized scope
|
|
192
|
+
* @param {string} target - Target to check (domain, IP, URL, etc.)
|
|
193
|
+
* @param {string} [targetType] - Optional target type hint
|
|
194
|
+
* @returns {Promise<boolean>} - True if target is authorized
|
|
195
|
+
*/
|
|
196
|
+
async isAuthorized(target, targetType = null) {
|
|
197
|
+
if (!this.loaded) {
|
|
198
|
+
await this.loadManifest();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check each scope item
|
|
202
|
+
for (const scopeItem of this.manifest.scope) {
|
|
203
|
+
// If type specified, must match
|
|
204
|
+
if (targetType && scopeItem.type !== targetType) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check if target matches scope
|
|
209
|
+
if (this.matchesScope(target, scopeItem)) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if target matches a scope item
|
|
219
|
+
* @param {string} target - Target to check
|
|
220
|
+
* @param {Object} scopeItem - Scope item with type and target
|
|
221
|
+
* @returns {boolean} - True if matches
|
|
222
|
+
*/
|
|
223
|
+
matchesScope(target, scopeItem) {
|
|
224
|
+
const { type, target: scopeTarget } = scopeItem;
|
|
225
|
+
|
|
226
|
+
switch (type) {
|
|
227
|
+
case ScopeType.DOMAIN:
|
|
228
|
+
// Match exact domain or subdomain
|
|
229
|
+
return this.matchesDomain(target, scopeTarget);
|
|
230
|
+
|
|
231
|
+
case ScopeType.IP_RANGE:
|
|
232
|
+
// Match IP address in CIDR range
|
|
233
|
+
return this.matchesIPRange(target, scopeTarget);
|
|
234
|
+
|
|
235
|
+
case ScopeType.URL:
|
|
236
|
+
// Match URL or URL prefix
|
|
237
|
+
return this.matchesURL(target, scopeTarget);
|
|
238
|
+
|
|
239
|
+
case ScopeType.NETWORK:
|
|
240
|
+
// Match network range
|
|
241
|
+
return this.matchesNetwork(target, scopeTarget);
|
|
242
|
+
|
|
243
|
+
case ScopeType.APPLICATION:
|
|
244
|
+
// Match application name
|
|
245
|
+
return this.matchesApplication(target, scopeTarget);
|
|
246
|
+
|
|
247
|
+
default:
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check if target matches domain scope
|
|
254
|
+
* @param {string} target - Target domain
|
|
255
|
+
* @param {string} scopeDomain - Authorized domain
|
|
256
|
+
* @returns {boolean} - True if matches
|
|
257
|
+
*/
|
|
258
|
+
matchesDomain(target, scopeDomain) {
|
|
259
|
+
// Remove protocol and path from target
|
|
260
|
+
const cleanTarget = target.replace(/^https?:\/\//, '').split('/')[0].toLowerCase();
|
|
261
|
+
const cleanScope = scopeDomain.toLowerCase();
|
|
262
|
+
|
|
263
|
+
// Exact match
|
|
264
|
+
if (cleanTarget === cleanScope) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Subdomain match (*.example.com matches sub.example.com)
|
|
269
|
+
if (cleanScope.startsWith('*.')) {
|
|
270
|
+
const baseDomain = cleanScope.substring(2);
|
|
271
|
+
return cleanTarget === baseDomain || cleanTarget.endsWith(`.${baseDomain}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check if target is subdomain of scope
|
|
275
|
+
return cleanTarget.endsWith(`.${cleanScope}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Check if target IP is in authorized range
|
|
280
|
+
* @param {string} target - Target IP address
|
|
281
|
+
* @param {string} scopeRange - CIDR range (e.g., "192.168.1.0/24")
|
|
282
|
+
* @returns {boolean} - True if in range
|
|
283
|
+
*/
|
|
284
|
+
matchesIPRange(target, scopeRange) {
|
|
285
|
+
try {
|
|
286
|
+
// Parse CIDR notation
|
|
287
|
+
const [rangeIP, prefixLength] = scopeRange.split('/');
|
|
288
|
+
const prefix = parseInt(prefixLength, 10);
|
|
289
|
+
|
|
290
|
+
// Convert IPs to integers for comparison
|
|
291
|
+
const targetInt = this.ipToInt(target);
|
|
292
|
+
const rangeInt = this.ipToInt(rangeIP);
|
|
293
|
+
|
|
294
|
+
// Calculate network mask
|
|
295
|
+
const mask = -1 << (32 - prefix);
|
|
296
|
+
|
|
297
|
+
// Check if target is in range
|
|
298
|
+
return (targetInt & mask) === (rangeInt & mask);
|
|
299
|
+
|
|
300
|
+
} catch (err) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Convert IP address string to integer
|
|
307
|
+
* @param {string} ip - IP address
|
|
308
|
+
* @returns {number} - Integer representation
|
|
309
|
+
*/
|
|
310
|
+
ipToInt(ip) {
|
|
311
|
+
const parts = ip.split('.').map(Number);
|
|
312
|
+
return (parts[0] << 24) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Check if target URL matches authorized URL
|
|
317
|
+
* @param {string} target - Target URL
|
|
318
|
+
* @param {string} scopeURL - Authorized URL
|
|
319
|
+
* @returns {boolean} - True if matches
|
|
320
|
+
*/
|
|
321
|
+
matchesURL(target, scopeURL) {
|
|
322
|
+
const cleanTarget = target.toLowerCase();
|
|
323
|
+
const cleanScope = scopeURL.toLowerCase();
|
|
324
|
+
|
|
325
|
+
// Exact match
|
|
326
|
+
if (cleanTarget === cleanScope) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Prefix match (scope URL is prefix of target)
|
|
331
|
+
return cleanTarget.startsWith(cleanScope);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Check if target network matches authorized network
|
|
336
|
+
* @param {string} target - Target network identifier
|
|
337
|
+
* @param {string} scopeNetwork - Authorized network
|
|
338
|
+
* @returns {boolean} - True if matches
|
|
339
|
+
*/
|
|
340
|
+
matchesNetwork(target, scopeNetwork) {
|
|
341
|
+
// Try IP range matching first
|
|
342
|
+
if (scopeNetwork.includes('/')) {
|
|
343
|
+
return this.matchesIPRange(target, scopeNetwork);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Otherwise simple string match
|
|
347
|
+
return target.toLowerCase() === scopeNetwork.toLowerCase();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if target application matches authorized application
|
|
352
|
+
* @param {string} target - Target application name
|
|
353
|
+
* @param {string} scopeApp - Authorized application
|
|
354
|
+
* @returns {boolean} - True if matches
|
|
355
|
+
*/
|
|
356
|
+
matchesApplication(target, scopeApp) {
|
|
357
|
+
return target.toLowerCase() === scopeApp.toLowerCase();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Verify operation is allowed at current authorization level
|
|
362
|
+
* @param {string} requiredLevel - Minimum authorization level required
|
|
363
|
+
* @returns {Promise<boolean>} - True if allowed
|
|
364
|
+
* @throws {Error} - If authorization level insufficient
|
|
365
|
+
*/
|
|
366
|
+
async verifyLevel(requiredLevel) {
|
|
367
|
+
if (!this.loaded) {
|
|
368
|
+
await this.loadManifest();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const levels = [
|
|
372
|
+
AuthLevel.NONE,
|
|
373
|
+
AuthLevel.PASSIVE,
|
|
374
|
+
AuthLevel.ACTIVE,
|
|
375
|
+
AuthLevel.EXPLOITATION,
|
|
376
|
+
AuthLevel.INTERNAL
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
const currentIndex = levels.indexOf(this.manifest.authorization_level);
|
|
380
|
+
const requiredIndex = levels.indexOf(requiredLevel);
|
|
381
|
+
|
|
382
|
+
if (currentIndex < requiredIndex) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Insufficient authorization level.\n` +
|
|
385
|
+
`Required: ${requiredLevel}\n` +
|
|
386
|
+
`Current: ${this.manifest.authorization_level}\n` +
|
|
387
|
+
`\nThis operation requires higher authorization level.`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Create authorization log entry
|
|
396
|
+
* @param {Object} operation - Operation details
|
|
397
|
+
* @returns {Promise<void>}
|
|
398
|
+
*/
|
|
399
|
+
async logOperation(operation) {
|
|
400
|
+
const logEntry = {
|
|
401
|
+
timestamp: new Date().toISOString(),
|
|
402
|
+
engagement_id: this.manifest.engagement_id,
|
|
403
|
+
operation: operation.type,
|
|
404
|
+
target: operation.target,
|
|
405
|
+
result: operation.result,
|
|
406
|
+
user: operation.user || 'system'
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// Append to engagement log file
|
|
410
|
+
const logFile = path.join(
|
|
411
|
+
path.dirname(this.manifestPath),
|
|
412
|
+
`.security-engagement-${this.manifest.engagement_id}.log`
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const logLine = JSON.stringify(logEntry) + '\n';
|
|
416
|
+
await fs.appendFile(logFile, logLine, 'utf8');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get current authorization manifest
|
|
421
|
+
* @returns {Object} - Current manifest
|
|
422
|
+
* @throws {Error} - If manifest not loaded
|
|
423
|
+
*/
|
|
424
|
+
getManifest() {
|
|
425
|
+
if (!this.loaded) {
|
|
426
|
+
throw new Error('Authorization manifest not loaded. Call loadManifest() first.');
|
|
427
|
+
}
|
|
428
|
+
return this.manifest;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get authorization summary
|
|
433
|
+
* @returns {Object} - Authorization summary
|
|
434
|
+
*/
|
|
435
|
+
getSummary() {
|
|
436
|
+
if (!this.loaded) {
|
|
437
|
+
return { authorized: false };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
authorized: true,
|
|
442
|
+
engagement_id: this.manifest.engagement_id,
|
|
443
|
+
client: this.manifest.client,
|
|
444
|
+
level: this.manifest.authorization_level,
|
|
445
|
+
scope_count: this.manifest.scope.length,
|
|
446
|
+
start_date: this.manifest.start_date,
|
|
447
|
+
end_date: this.manifest.end_date,
|
|
448
|
+
days_remaining: this.getDaysRemaining()
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Calculate days remaining in engagement
|
|
454
|
+
* @returns {number} - Days remaining
|
|
455
|
+
*/
|
|
456
|
+
getDaysRemaining() {
|
|
457
|
+
const endDate = new Date(this.manifest.end_date);
|
|
458
|
+
const now = new Date();
|
|
459
|
+
const diffTime = endDate - now;
|
|
460
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
461
|
+
return Math.max(0, diffDays);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Create sample authorization manifest
|
|
466
|
+
* @param {string} outputPath - Path to write sample manifest
|
|
467
|
+
* @returns {Promise<string>} - Path to created file
|
|
468
|
+
*/
|
|
469
|
+
static async createSampleManifest(outputPath = AUTH_MANIFEST_FILE) {
|
|
470
|
+
const sample = {
|
|
471
|
+
engagement_id: `ENG-${new Date().getFullYear()}-001`,
|
|
472
|
+
client: "Client Company Name",
|
|
473
|
+
authorized_by: "John Smith (CTO)",
|
|
474
|
+
authorization_document: "signed_authorization_letter.pdf",
|
|
475
|
+
authorization_level: "exploitation",
|
|
476
|
+
scope: [
|
|
477
|
+
{
|
|
478
|
+
type: "domain",
|
|
479
|
+
target: "*.example.com",
|
|
480
|
+
description: "All subdomains of example.com"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
type: "ip_range",
|
|
484
|
+
target: "192.168.1.0/24",
|
|
485
|
+
description: "Internal network range"
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
type: "url",
|
|
489
|
+
target: "https://app.example.com",
|
|
490
|
+
description: "Web application"
|
|
491
|
+
}
|
|
492
|
+
],
|
|
493
|
+
out_of_scope: [
|
|
494
|
+
"production-db.example.com",
|
|
495
|
+
"backup.example.com"
|
|
496
|
+
],
|
|
497
|
+
start_date: new Date().toISOString().split('T')[0],
|
|
498
|
+
end_date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
|
499
|
+
rules_of_engagement: {
|
|
500
|
+
testing_hours: "24/7",
|
|
501
|
+
exploit_depth: "full_exploitation_allowed",
|
|
502
|
+
data_exfiltration: "proof_of_concept_only",
|
|
503
|
+
service_disruption: "not_allowed",
|
|
504
|
+
social_engineering: "email_only",
|
|
505
|
+
physical_security: "not_authorized"
|
|
506
|
+
},
|
|
507
|
+
contacts: {
|
|
508
|
+
primary: "security@example.com",
|
|
509
|
+
emergency: "+1-555-0123"
|
|
510
|
+
},
|
|
511
|
+
reporting: {
|
|
512
|
+
critical_findings: "immediate_notification",
|
|
513
|
+
regular_updates: "weekly",
|
|
514
|
+
final_report: "within_5_days_of_completion"
|
|
515
|
+
},
|
|
516
|
+
notes: "Penetration test authorized per signed agreement dated 2025-11-25"
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
await fs.writeFile(
|
|
520
|
+
outputPath,
|
|
521
|
+
JSON.stringify(sample, null, 2),
|
|
522
|
+
'utf8'
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
return outputPath;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Global authorization checker instance
|
|
531
|
+
*/
|
|
532
|
+
let globalChecker = null;
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Get or create global authorization checker
|
|
536
|
+
* @returns {AuthorizationChecker} - Global instance
|
|
537
|
+
*/
|
|
538
|
+
function getChecker() {
|
|
539
|
+
if (!globalChecker) {
|
|
540
|
+
globalChecker = new AuthorizationChecker();
|
|
541
|
+
}
|
|
542
|
+
return globalChecker;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Quick authorization check function
|
|
547
|
+
* @param {string} target - Target to check
|
|
548
|
+
* @param {string} level - Required authorization level
|
|
549
|
+
* @returns {Promise<boolean>} - True if authorized
|
|
550
|
+
* @throws {Error} - If not authorized
|
|
551
|
+
*/
|
|
552
|
+
async function checkAuthorization(target, level = AuthLevel.PASSIVE) {
|
|
553
|
+
const checker = getChecker();
|
|
554
|
+
|
|
555
|
+
// Load manifest if not loaded
|
|
556
|
+
if (!checker.loaded) {
|
|
557
|
+
await checker.loadManifest();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Verify authorization level
|
|
561
|
+
await checker.verifyLevel(level);
|
|
562
|
+
|
|
563
|
+
// Check if target is in scope
|
|
564
|
+
const authorized = await checker.isAuthorized(target);
|
|
565
|
+
|
|
566
|
+
if (!authorized) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
`Target not in authorized scope: ${target}\n` +
|
|
569
|
+
`\nAuthorized scope:\n` +
|
|
570
|
+
checker.manifest.scope.map(s => ` - ${s.type}: ${s.target}`).join('\n') +
|
|
571
|
+
`\n\nThis target is NOT authorized for testing.`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Log the operation
|
|
576
|
+
await checker.logOperation({
|
|
577
|
+
type: 'authorization_check',
|
|
578
|
+
target,
|
|
579
|
+
result: 'authorized'
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Require authorization before proceeding
|
|
587
|
+
* @param {string} target - Target to authorize
|
|
588
|
+
* @param {string} level - Required authorization level
|
|
589
|
+
* @returns {Promise<void>}
|
|
590
|
+
* @throws {Error} - If not authorized
|
|
591
|
+
*/
|
|
592
|
+
async function requireAuthorization(target, level = AuthLevel.PASSIVE) {
|
|
593
|
+
await checkAuthorization(target, level);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export {
|
|
597
|
+
AuthorizationChecker,
|
|
598
|
+
AuthLevel,
|
|
599
|
+
ScopeType,
|
|
600
|
+
getChecker,
|
|
601
|
+
checkAuthorization,
|
|
602
|
+
requireAuthorization,
|
|
603
|
+
AuthorizationChecker as default
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
export const createSampleManifest = AuthorizationChecker.createSampleManifest;
|