myaidev-method 0.2.18 → 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.
Files changed (31) hide show
  1. package/.claude/mcp/sparc-orchestrator-server.js +0 -0
  2. package/.claude/mcp/wordpress-server.js +0 -0
  3. package/CHANGELOG.md +145 -0
  4. package/README.md +205 -13
  5. package/TECHNICAL_ARCHITECTURE.md +64 -2
  6. package/bin/cli.js +169 -2
  7. package/dist/mcp/mcp-config.json +138 -1
  8. package/dist/mcp/openstack-server.js +1607 -0
  9. package/package.json +2 -2
  10. package/src/config/workflows.js +532 -0
  11. package/src/lib/payloadcms-utils.js +343 -10
  12. package/src/lib/visual-generation-utils.js +445 -294
  13. package/src/lib/workflow-installer.js +512 -0
  14. package/src/libs/security/authorization-checker.js +606 -0
  15. package/src/mcp/openstack-server.js +1607 -0
  16. package/src/scripts/openstack-setup.sh +110 -0
  17. package/src/scripts/security/environment-detect.js +425 -0
  18. package/src/templates/claude/agents/openstack-vm-manager.md +281 -0
  19. package/src/templates/claude/agents/osint-researcher.md +1075 -0
  20. package/src/templates/claude/agents/penetration-tester.md +908 -0
  21. package/src/templates/claude/agents/security-auditor.md +244 -0
  22. package/src/templates/claude/agents/security-setup.md +1094 -0
  23. package/src/templates/claude/agents/webapp-security-tester.md +581 -0
  24. package/src/templates/claude/commands/myai-configure.md +84 -0
  25. package/src/templates/claude/commands/myai-openstack.md +229 -0
  26. package/src/templates/claude/commands/sc:security-exploit.md +464 -0
  27. package/src/templates/claude/commands/sc:security-recon.md +281 -0
  28. package/src/templates/claude/commands/sc:security-report.md +756 -0
  29. package/src/templates/claude/commands/sc:security-scan.md +441 -0
  30. package/src/templates/claude/commands/sc:security-setup.md +501 -0
  31. 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;