aura-security 0.4.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +446 -0
  3. package/deploy/AWS-DEPLOYMENT.md +358 -0
  4. package/deploy/terraform/main.tf +362 -0
  5. package/deploy/terraform/terraform.tfvars.example +6 -0
  6. package/dist/agents/base.d.ts +44 -0
  7. package/dist/agents/base.js +96 -0
  8. package/dist/agents/index.d.ts +14 -0
  9. package/dist/agents/index.js +17 -0
  10. package/dist/agents/policy/evaluator.d.ts +15 -0
  11. package/dist/agents/policy/evaluator.js +183 -0
  12. package/dist/agents/policy/index.d.ts +12 -0
  13. package/dist/agents/policy/index.js +15 -0
  14. package/dist/agents/policy/validator.d.ts +15 -0
  15. package/dist/agents/policy/validator.js +182 -0
  16. package/dist/agents/scanners/gitleaks.d.ts +14 -0
  17. package/dist/agents/scanners/gitleaks.js +155 -0
  18. package/dist/agents/scanners/grype.d.ts +14 -0
  19. package/dist/agents/scanners/grype.js +109 -0
  20. package/dist/agents/scanners/index.d.ts +15 -0
  21. package/dist/agents/scanners/index.js +27 -0
  22. package/dist/agents/scanners/npm-audit.d.ts +13 -0
  23. package/dist/agents/scanners/npm-audit.js +129 -0
  24. package/dist/agents/scanners/semgrep.d.ts +14 -0
  25. package/dist/agents/scanners/semgrep.js +131 -0
  26. package/dist/agents/scanners/trivy.d.ts +14 -0
  27. package/dist/agents/scanners/trivy.js +122 -0
  28. package/dist/agents/types.d.ts +137 -0
  29. package/dist/agents/types.js +91 -0
  30. package/dist/auditor/index.d.ts +3 -0
  31. package/dist/auditor/index.js +2 -0
  32. package/dist/auditor/pipeline.d.ts +19 -0
  33. package/dist/auditor/pipeline.js +240 -0
  34. package/dist/auditor/validator.d.ts +17 -0
  35. package/dist/auditor/validator.js +58 -0
  36. package/dist/aura/client.d.ts +29 -0
  37. package/dist/aura/client.js +125 -0
  38. package/dist/aura/index.d.ts +4 -0
  39. package/dist/aura/index.js +2 -0
  40. package/dist/aura/server.d.ts +45 -0
  41. package/dist/aura/server.js +343 -0
  42. package/dist/cli.d.ts +17 -0
  43. package/dist/cli.js +1433 -0
  44. package/dist/client/index.d.ts +41 -0
  45. package/dist/client/index.js +170 -0
  46. package/dist/compliance/index.d.ts +40 -0
  47. package/dist/compliance/index.js +292 -0
  48. package/dist/database/index.d.ts +77 -0
  49. package/dist/database/index.js +395 -0
  50. package/dist/index.d.ts +25 -0
  51. package/dist/index.js +762 -0
  52. package/dist/integrations/aura-scanner.d.ts +69 -0
  53. package/dist/integrations/aura-scanner.js +155 -0
  54. package/dist/integrations/aws-scanner.d.ts +63 -0
  55. package/dist/integrations/aws-scanner.js +624 -0
  56. package/dist/integrations/config.d.ts +69 -0
  57. package/dist/integrations/config.js +212 -0
  58. package/dist/integrations/github.d.ts +45 -0
  59. package/dist/integrations/github.js +201 -0
  60. package/dist/integrations/gitlab.d.ts +36 -0
  61. package/dist/integrations/gitlab.js +110 -0
  62. package/dist/integrations/index.d.ts +11 -0
  63. package/dist/integrations/index.js +11 -0
  64. package/dist/integrations/local-scanner.d.ts +146 -0
  65. package/dist/integrations/local-scanner.js +1654 -0
  66. package/dist/integrations/notifications.d.ts +99 -0
  67. package/dist/integrations/notifications.js +305 -0
  68. package/dist/integrations/scanners.d.ts +57 -0
  69. package/dist/integrations/scanners.js +217 -0
  70. package/dist/integrations/slop-scanner.d.ts +69 -0
  71. package/dist/integrations/slop-scanner.js +155 -0
  72. package/dist/integrations/webhook.d.ts +37 -0
  73. package/dist/integrations/webhook.js +256 -0
  74. package/dist/orchestrator/index.d.ts +72 -0
  75. package/dist/orchestrator/index.js +187 -0
  76. package/dist/output/index.d.ts +152 -0
  77. package/dist/output/index.js +399 -0
  78. package/dist/pipeline/index.d.ts +72 -0
  79. package/dist/pipeline/index.js +313 -0
  80. package/dist/sbom/index.d.ts +94 -0
  81. package/dist/sbom/index.js +298 -0
  82. package/dist/schemas/index.d.ts +2 -0
  83. package/dist/schemas/index.js +2 -0
  84. package/dist/schemas/input.schema.d.ts +87 -0
  85. package/dist/schemas/input.schema.js +44 -0
  86. package/dist/schemas/output.schema.d.ts +115 -0
  87. package/dist/schemas/output.schema.js +64 -0
  88. package/dist/serve-visualizer.d.ts +2 -0
  89. package/dist/serve-visualizer.js +78 -0
  90. package/dist/slop/client.d.ts +29 -0
  91. package/dist/slop/client.js +125 -0
  92. package/dist/slop/index.d.ts +4 -0
  93. package/dist/slop/index.js +2 -0
  94. package/dist/slop/server.d.ts +45 -0
  95. package/dist/slop/server.js +343 -0
  96. package/dist/types/events.d.ts +62 -0
  97. package/dist/types/events.js +2 -0
  98. package/dist/types/index.d.ts +1 -0
  99. package/dist/types/index.js +1 -0
  100. package/dist/visualizer/index.d.ts +4 -0
  101. package/dist/visualizer/index.js +181 -0
  102. package/dist/websocket/index.d.ts +88 -0
  103. package/dist/websocket/index.js +195 -0
  104. package/dist/zones/index.d.ts +7 -0
  105. package/dist/zones/index.js +7 -0
  106. package/dist/zones/manager.d.ts +101 -0
  107. package/dist/zones/manager.js +304 -0
  108. package/dist/zones/types.d.ts +78 -0
  109. package/dist/zones/types.js +33 -0
  110. package/package.json +84 -0
  111. package/visualizer/app.js +0 -0
  112. package/visualizer/index-minimal.html +1771 -0
  113. package/visualizer/index.html +2933 -0
  114. package/visualizer/landing.html +1328 -0
  115. package/visualizer/styles.css +0 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1433 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aurasecurity CLI
4
+ *
5
+ * A security auditor that can scan local directories, git repos, and more.
6
+ * Works standalone (no server needed) or connected to Aura server.
7
+ *
8
+ * Usage:
9
+ * aura-security scan <path> Scan a local directory for security issues
10
+ * aura-security serve Start the Aura server
11
+ * aura-security visualizer Start the 3D visualizer
12
+ * aura-security status Show server status
13
+ * aura-security audit [file] Run audit via server
14
+ * aura-security logs Show audit log entries
15
+ * aura-security watch Watch for new audits
16
+ */
17
+ import { visualize, visualizeState, visualizeCompact } from './visualizer/index.js';
18
+ import { LocalScanner, quickLocalScan } from './integrations/local-scanner.js';
19
+ import { scanAWS } from './integrations/aws-scanner.js';
20
+ import { generateSBOM } from './sbom/index.js';
21
+ import { checkLicenses, POLICY_NAMES } from './compliance/index.js';
22
+ import { toSARIF, toJUnit, toJSONSummary, toGitLabReport } from './output/index.js';
23
+ import { existsSync, writeFileSync, mkdirSync } from 'fs';
24
+ import { join, resolve, basename } from 'path';
25
+ import { spawnSync } from 'child_process';
26
+ const AURA_URL = process.env.AURA_URL ?? 'http://127.0.0.1:3000';
27
+ const VERSION = '0.3.0';
28
+ // ANSI colors for terminal output
29
+ const colors = {
30
+ reset: '\x1b[0m',
31
+ bold: '\x1b[1m',
32
+ dim: '\x1b[2m',
33
+ red: '\x1b[31m',
34
+ green: '\x1b[32m',
35
+ yellow: '\x1b[33m',
36
+ blue: '\x1b[34m',
37
+ magenta: '\x1b[35m',
38
+ cyan: '\x1b[36m',
39
+ white: '\x1b[37m',
40
+ bgRed: '\x1b[41m',
41
+ bgYellow: '\x1b[43m',
42
+ bgGreen: '\x1b[42m',
43
+ };
44
+ function c(color, text) {
45
+ return `${colors[color]}${text}${colors.reset}`;
46
+ }
47
+ const SECURITY_TOOLS = [
48
+ {
49
+ name: 'gitleaks',
50
+ command: 'gitleaks',
51
+ description: 'Secrets detection in code and git history',
52
+ category: 'secrets',
53
+ install: { brew: 'brew install gitleaks', apt: 'apt install gitleaks', other: 'https://github.com/gitleaks/gitleaks#installing' },
54
+ license: 'MIT (Free)',
55
+ required: true
56
+ },
57
+ {
58
+ name: 'trivy',
59
+ command: 'trivy',
60
+ description: 'Vulnerability scanner for containers and filesystems',
61
+ category: 'vulnerabilities',
62
+ install: { brew: 'brew install trivy', apt: 'apt install trivy', other: 'https://github.com/aquasecurity/trivy#installation' },
63
+ license: 'Apache 2.0 (Free)',
64
+ required: true
65
+ },
66
+ {
67
+ name: 'grype',
68
+ command: 'grype',
69
+ description: 'Universal vulnerability scanner',
70
+ category: 'vulnerabilities',
71
+ install: { brew: 'brew install grype', other: 'curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh' },
72
+ license: 'Apache 2.0 (Free)',
73
+ required: false
74
+ },
75
+ {
76
+ name: 'semgrep',
77
+ command: 'semgrep',
78
+ description: 'Static analysis (SAST) for code security',
79
+ category: 'sast',
80
+ install: { pip: 'pip install semgrep', brew: 'brew install semgrep' },
81
+ license: 'LGPL 2.1 (Free, Pro rules paid)',
82
+ required: false
83
+ },
84
+ {
85
+ name: 'checkov',
86
+ command: 'checkov',
87
+ description: 'Infrastructure as Code (IaC) security scanner',
88
+ category: 'iac',
89
+ install: { pip: 'pip install checkov', brew: 'brew install checkov' },
90
+ license: 'Apache 2.0 (Free)',
91
+ required: false
92
+ },
93
+ {
94
+ name: 'hadolint',
95
+ command: 'hadolint',
96
+ description: 'Dockerfile linter and security checker',
97
+ category: 'container',
98
+ install: { brew: 'brew install hadolint', other: 'https://github.com/hadolint/hadolint#install' },
99
+ license: 'GPL-3.0 (Free)',
100
+ required: false
101
+ },
102
+ {
103
+ name: 'pip-audit',
104
+ command: 'pip-audit',
105
+ description: 'Python package vulnerability scanner',
106
+ category: 'vulnerabilities',
107
+ install: { pip: 'pip install pip-audit' },
108
+ license: 'Apache 2.0 (Free)',
109
+ required: false
110
+ },
111
+ {
112
+ name: 'govulncheck',
113
+ command: 'govulncheck',
114
+ description: 'Go module vulnerability scanner',
115
+ category: 'vulnerabilities',
116
+ install: { go: 'go install golang.org/x/vuln/cmd/govulncheck@latest' },
117
+ license: 'BSD-3-Clause (Free)',
118
+ required: false
119
+ },
120
+ {
121
+ name: 'cargo-audit',
122
+ command: 'cargo-audit',
123
+ description: 'Rust crate vulnerability scanner',
124
+ category: 'vulnerabilities',
125
+ install: { cargo: 'cargo install cargo-audit' },
126
+ license: 'Apache 2.0 / MIT (Free)',
127
+ required: false
128
+ },
129
+ {
130
+ name: 'bundle-audit',
131
+ command: 'bundle-audit',
132
+ description: 'Ruby gem vulnerability scanner',
133
+ category: 'vulnerabilities',
134
+ install: { gem: 'gem install bundler-audit' },
135
+ license: 'GPLv3 (Free)',
136
+ required: false
137
+ }
138
+ ];
139
+ function isToolInstalled(command) {
140
+ try {
141
+ const result = spawnSync(command, ['--version'], {
142
+ encoding: 'utf-8',
143
+ timeout: 5000,
144
+ stdio: ['pipe', 'pipe', 'pipe']
145
+ });
146
+ if (result.status === 0) {
147
+ const version = (result.stdout || result.stderr || '').split('\n')[0].trim().slice(0, 50);
148
+ return { installed: true, version };
149
+ }
150
+ return { installed: false };
151
+ }
152
+ catch {
153
+ return { installed: false };
154
+ }
155
+ }
156
+ async function runDoctor() {
157
+ console.log('');
158
+ console.log(c('cyan', '╔═══════════════════════════════════════════════════════════════╗'));
159
+ console.log(c('cyan', '║') + c('bold', ' AURASECURITY - Security Tools Check ') + c('cyan', '║'));
160
+ console.log(c('cyan', '╚═══════════════════════════════════════════════════════════════╝'));
161
+ console.log('');
162
+ // Check Node.js
163
+ console.log(c('bold', '► Runtime Environment'));
164
+ console.log(` ${c('green', '✓')} Node.js ${process.version}`);
165
+ console.log(` ${c('green', '✓')} Platform: ${process.platform} (${process.arch})`);
166
+ console.log('');
167
+ // Check Git
168
+ const gitCheck = isToolInstalled('git');
169
+ console.log(c('bold', '► Required Tools'));
170
+ if (gitCheck.installed) {
171
+ console.log(` ${c('green', '✓')} git ${c('dim', gitCheck.version || '')}`);
172
+ }
173
+ else {
174
+ console.log(` ${c('red', '✗')} git - ${c('yellow', 'Required for remote repo scanning')}`);
175
+ }
176
+ // Group tools by category
177
+ const categories = {
178
+ secrets: { name: 'Secrets Detection', tools: [] },
179
+ vulnerabilities: { name: 'Vulnerability Scanning', tools: [] },
180
+ sast: { name: 'Static Analysis (SAST)', tools: [] },
181
+ iac: { name: 'Infrastructure as Code', tools: [] },
182
+ container: { name: 'Container Security', tools: [] },
183
+ license: { name: 'License Compliance', tools: [] }
184
+ };
185
+ for (const tool of SECURITY_TOOLS) {
186
+ categories[tool.category].tools.push(tool);
187
+ }
188
+ let installedCount = 0;
189
+ let totalCount = 0;
190
+ const missingTools = [];
191
+ for (const [, category] of Object.entries(categories)) {
192
+ if (category.tools.length === 0)
193
+ continue;
194
+ console.log('');
195
+ console.log(c('bold', `► ${category.name}`));
196
+ for (const tool of category.tools) {
197
+ totalCount++;
198
+ const check = isToolInstalled(tool.command);
199
+ if (check.installed) {
200
+ installedCount++;
201
+ console.log(` ${c('green', '✓')} ${tool.name} ${c('dim', check.version || '')}`);
202
+ console.log(` ${c('dim', tool.description)}`);
203
+ console.log(` ${c('dim', `License: ${tool.license}`)}`);
204
+ }
205
+ else {
206
+ missingTools.push(tool);
207
+ const marker = tool.required ? c('red', '✗') : c('yellow', '○');
208
+ const status = tool.required ? c('red', 'MISSING') : c('yellow', 'optional');
209
+ console.log(` ${marker} ${tool.name} [${status}]`);
210
+ console.log(` ${c('dim', tool.description)}`);
211
+ }
212
+ }
213
+ }
214
+ // Native capabilities
215
+ console.log('');
216
+ console.log(c('bold', '► Native Capabilities (No External Tools Needed)'));
217
+ console.log(` ${c('green', '✓')} Regex-based secret detection (fallback)`);
218
+ console.log(` ${c('green', '✓')} Service/technology discovery`);
219
+ console.log(` ${c('green', '✓')} Codebase structure mapping`);
220
+ console.log(` ${c('green', '✓')} Environment file scanning`);
221
+ console.log(` ${c('green', '✓')} Git repository analysis`);
222
+ console.log(` ${c('green', '✓')} Language auto-detection`);
223
+ console.log(` ${c('green', '✓')} npm audit (built into npm)`);
224
+ // Summary
225
+ console.log('');
226
+ console.log('─'.repeat(67));
227
+ const pct = Math.round((installedCount / totalCount) * 100);
228
+ const pctColor = pct >= 80 ? 'green' : pct >= 50 ? 'yellow' : 'red';
229
+ console.log(`Tools installed: ${c(pctColor, `${installedCount}/${totalCount}`)} (${pct}%)`);
230
+ if (missingTools.length > 0) {
231
+ console.log('');
232
+ console.log(c('yellow', '► Installation Commands for Missing Tools:'));
233
+ console.log('');
234
+ for (const tool of missingTools) {
235
+ console.log(` ${c('cyan', tool.name)}:`);
236
+ if (tool.install.brew)
237
+ console.log(` macOS: ${tool.install.brew}`);
238
+ if (tool.install.apt)
239
+ console.log(` Linux: ${tool.install.apt}`);
240
+ if (tool.install.pip)
241
+ console.log(` pip: ${tool.install.pip}`);
242
+ if (tool.install.npm)
243
+ console.log(` npm: ${tool.install.npm}`);
244
+ if (tool.install.go)
245
+ console.log(` go: ${tool.install.go}`);
246
+ if (tool.install.cargo)
247
+ console.log(` cargo: ${tool.install.cargo}`);
248
+ if (tool.install.gem)
249
+ console.log(` gem: ${tool.install.gem}`);
250
+ if (tool.install.other)
251
+ console.log(` other: ${tool.install.other}`);
252
+ }
253
+ }
254
+ // Quick install script suggestion
255
+ const requiredMissing = missingTools.filter(t => t.required);
256
+ if (requiredMissing.length > 0) {
257
+ console.log('');
258
+ console.log(c('yellow', '⚠ Some required tools are missing. Quick install (macOS):'));
259
+ console.log('');
260
+ console.log(c('cyan', ` brew install ${requiredMissing.filter(t => t.install.brew).map(t => t.name).join(' ')}`));
261
+ }
262
+ else {
263
+ console.log('');
264
+ console.log(c('green', '✓ All required tools are installed!'));
265
+ }
266
+ console.log('');
267
+ }
268
+ async function main() {
269
+ const args = process.argv.slice(2);
270
+ const command = args[0];
271
+ // Handle flags
272
+ if (args.includes('--version') || args.includes('-v')) {
273
+ console.log(`aura-security v${VERSION}`);
274
+ process.exit(0);
275
+ }
276
+ if (args.includes('--help') || args.includes('-h') || !command) {
277
+ showHelp();
278
+ process.exit(0);
279
+ }
280
+ switch (command) {
281
+ case 'init':
282
+ await runInit(args.slice(1));
283
+ break;
284
+ case 'doctor':
285
+ case 'check':
286
+ await runDoctor();
287
+ break;
288
+ case 'scan':
289
+ await runScan(args.slice(1));
290
+ break;
291
+ case 'aws':
292
+ await runAWSScan(args.slice(1));
293
+ break;
294
+ case 'sbom':
295
+ await runSBOM(args.slice(1));
296
+ break;
297
+ case 'license-check':
298
+ await runLicenseCheck(args.slice(1));
299
+ break;
300
+ case 'serve':
301
+ await startServer();
302
+ break;
303
+ case 'visualizer':
304
+ await startVisualizer();
305
+ break;
306
+ case 'status':
307
+ await showStatus();
308
+ break;
309
+ case 'audit':
310
+ await runAudit(args.slice(1));
311
+ break;
312
+ case 'logs':
313
+ await showLogs();
314
+ break;
315
+ case 'watch':
316
+ await watchMode();
317
+ break;
318
+ default:
319
+ console.error(c('red', `Unknown command: ${command}`));
320
+ console.log('Run aura-security --help for usage information.');
321
+ process.exit(1);
322
+ }
323
+ }
324
+ // ============ INIT COMMAND ============
325
+ async function runInit(args) {
326
+ const targetDir = args[0] ? resolve(args[0]) : process.cwd();
327
+ const configDir = join(targetDir, '.aura-security');
328
+ const configFile = join(configDir, 'config.json');
329
+ const gitignorePath = join(targetDir, '.gitignore');
330
+ console.log('');
331
+ console.log(c('cyan', '🔧 Initializing aurasecurity configuration...'));
332
+ console.log('');
333
+ // Check if already initialized
334
+ if (existsSync(configFile)) {
335
+ console.log(c('yellow', `Configuration already exists at: ${configFile}`));
336
+ console.log('');
337
+ console.log('To reinitialize, delete the .aura-security directory first.');
338
+ process.exit(0);
339
+ }
340
+ // Create config directory
341
+ if (!existsSync(configDir)) {
342
+ mkdirSync(configDir, { recursive: true });
343
+ console.log(c('green', '✓') + ` Created directory: ${configDir}`);
344
+ }
345
+ // Default configuration
346
+ const defaultConfig = {
347
+ "$schema": "https://raw.githubusercontent.com/aurasecurity/aura-security/main/schemas/config.schema.json",
348
+ "version": "1.0",
349
+ "project": {
350
+ "name": basename(targetDir),
351
+ "description": "Security audit configuration"
352
+ },
353
+ "scanning": {
354
+ "enabled": true,
355
+ "secrets": true,
356
+ "packages": true,
357
+ "sast": true,
358
+ "maxDepth": 5,
359
+ "exclude": [
360
+ "node_modules",
361
+ ".git",
362
+ "dist",
363
+ "build",
364
+ ".next",
365
+ "coverage",
366
+ "__pycache__",
367
+ "venv",
368
+ ".venv"
369
+ ]
370
+ },
371
+ "tools": {
372
+ "gitleaks": {
373
+ "enabled": true,
374
+ "configPath": null
375
+ },
376
+ "trivy": {
377
+ "enabled": true,
378
+ "severity": ["CRITICAL", "HIGH", "MEDIUM"]
379
+ },
380
+ "semgrep": {
381
+ "enabled": true,
382
+ "config": "auto"
383
+ }
384
+ },
385
+ "server": {
386
+ "port": 3000,
387
+ "visualizerPort": 8080
388
+ },
389
+ "thresholds": {
390
+ "failOnCritical": true,
391
+ "failOnHigh": false,
392
+ "maxCritical": 0,
393
+ "maxHigh": 5
394
+ },
395
+ "notifications": {
396
+ "slack": {
397
+ "enabled": false,
398
+ "webhookUrl": null
399
+ },
400
+ "discord": {
401
+ "enabled": false,
402
+ "webhookUrl": null
403
+ }
404
+ },
405
+ "integrations": {
406
+ "github": {
407
+ "enabled": false,
408
+ "token": null,
409
+ "createCheckRuns": true,
410
+ "commentOnPR": true
411
+ },
412
+ "gitlab": {
413
+ "enabled": false,
414
+ "token": null
415
+ },
416
+ "aws": {
417
+ "enabled": false,
418
+ "region": "us-east-1",
419
+ "services": ["iam", "s3", "ec2", "lambda", "rds"]
420
+ }
421
+ }
422
+ };
423
+ // Write config file
424
+ writeFileSync(configFile, JSON.stringify(defaultConfig, null, 2));
425
+ console.log(c('green', '✓') + ` Created config: ${configFile}`);
426
+ // Create a sample .env.example
427
+ const envExamplePath = join(configDir, '.env.example');
428
+ const envExample = `# aurasecurity Environment Variables
429
+ # Copy this to .env and fill in your values
430
+
431
+ # Server Configuration
432
+ AURA_PORT=3000
433
+ VISUALIZER_PORT=8080
434
+
435
+ # GitHub Integration (optional)
436
+ GITHUB_TOKEN=
437
+
438
+ # GitLab Integration (optional)
439
+ GITLAB_TOKEN=
440
+
441
+ # Slack Notifications (optional)
442
+ SLACK_WEBHOOK_URL=
443
+
444
+ # Discord Notifications (optional)
445
+ DISCORD_WEBHOOK_URL=
446
+
447
+ # AWS Integration (optional)
448
+ AWS_REGION=us-east-1
449
+ AWS_ACCESS_KEY_ID=
450
+ AWS_SECRET_ACCESS_KEY=
451
+ `;
452
+ writeFileSync(envExamplePath, envExample);
453
+ console.log(c('green', '✓') + ` Created: ${envExamplePath}`);
454
+ // Update .gitignore if it exists
455
+ if (existsSync(gitignorePath)) {
456
+ const { readFileSync } = await import('fs');
457
+ const gitignore = readFileSync(gitignorePath, 'utf-8');
458
+ const linesToAdd = [
459
+ '.aura-security/.env',
460
+ '.aura-security/results/',
461
+ '.aura-security/*.log'
462
+ ];
463
+ const missingLines = linesToAdd.filter(line => !gitignore.includes(line));
464
+ if (missingLines.length > 0) {
465
+ const addition = '\n# aurasecurity\n' + missingLines.join('\n') + '\n';
466
+ const { appendFileSync } = await import('fs');
467
+ appendFileSync(gitignorePath, addition);
468
+ console.log(c('green', '✓') + ' Updated .gitignore');
469
+ }
470
+ }
471
+ console.log('');
472
+ console.log(c('green', '✓ Initialization complete!'));
473
+ console.log('');
474
+ console.log('Next steps:');
475
+ console.log(` 1. Edit ${c('cyan', configFile)} to customize settings`);
476
+ console.log(` 2. Copy ${c('cyan', envExamplePath)} to .env and add credentials`);
477
+ console.log(` 3. Run ${c('cyan', 'aura-security scan .')} to scan your project`);
478
+ console.log('');
479
+ }
480
+ // ============ SCAN COMMAND (Standalone - no server needed) ============
481
+ async function runScan(args) {
482
+ // Parse arguments
483
+ let targetPath = '.';
484
+ let outputFormat = 'console';
485
+ let outputFile;
486
+ let languages;
487
+ let scanners;
488
+ let failOn = ['critical']; // Default: fail on critical
489
+ for (let i = 0; i < args.length; i++) {
490
+ const arg = args[i];
491
+ if (arg === '--json' || arg === '-j') {
492
+ outputFormat = 'json';
493
+ }
494
+ else if (arg === '--format' || arg === '-f') {
495
+ const fmt = args[++i]?.toLowerCase();
496
+ if (['json', 'sarif', 'junit', 'gitlab-sast', 'gitlab-deps', 'summary', 'console'].includes(fmt)) {
497
+ outputFormat = fmt;
498
+ }
499
+ else {
500
+ console.error(c('red', `Unknown format: ${fmt}`));
501
+ console.log('Available formats: console, json, sarif, junit, gitlab-sast, gitlab-deps, summary');
502
+ process.exit(1);
503
+ }
504
+ }
505
+ else if (arg === '--output' || arg === '-o') {
506
+ outputFile = args[++i];
507
+ }
508
+ else if (arg === '--languages' || arg === '-l') {
509
+ languages = args[++i].split(',').map(l => l.trim());
510
+ }
511
+ else if (arg === '--scanners' || arg === '-S') {
512
+ scanners = args[++i].split(',').map(s => s.trim());
513
+ }
514
+ else if (arg === '--fail-on') {
515
+ failOn = args[++i].split(',').map(s => s.trim().toLowerCase());
516
+ }
517
+ else if (arg === '--no-fail') {
518
+ failOn = [];
519
+ }
520
+ else if (!arg.startsWith('-')) {
521
+ targetPath = arg;
522
+ }
523
+ }
524
+ // Resolve path
525
+ targetPath = resolve(targetPath);
526
+ if (!existsSync(targetPath)) {
527
+ console.error(c('red', `Error: Path does not exist: ${targetPath}`));
528
+ process.exit(1);
529
+ }
530
+ // Print header
531
+ console.log('');
532
+ console.log(c('cyan', '╔═══════════════════════════════════════════════════════════════╗'));
533
+ console.log(c('cyan', '║') + c('bold', ' AURASECURITY - Security Scanner ') + c('cyan', '║'));
534
+ console.log(c('cyan', '╚═══════════════════════════════════════════════════════════════╝'));
535
+ console.log('');
536
+ console.log(c('dim', `Target: ${targetPath}`));
537
+ console.log(c('dim', `Time: ${new Date().toISOString()}`));
538
+ if (languages)
539
+ console.log(c('dim', `Languages: ${languages.join(', ')}`));
540
+ if (scanners)
541
+ console.log(c('dim', `Scanners: ${scanners.join(', ')}`));
542
+ console.log('');
543
+ // Run the scan
544
+ console.log(c('yellow', '⏳ Starting security scan...'));
545
+ console.log('');
546
+ const startTime = Date.now();
547
+ const scanner = new LocalScanner({
548
+ targetPath,
549
+ languages,
550
+ scanners,
551
+ scanIaC: true,
552
+ scanDockerfiles: true
553
+ });
554
+ const result = await scanner.scan();
555
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
556
+ console.log('');
557
+ console.log(c('green', `✓ Scan completed in ${elapsed}s`));
558
+ console.log('');
559
+ // Generate output based on format
560
+ let outputContent;
561
+ let showConsole = true;
562
+ switch (outputFormat) {
563
+ case 'json':
564
+ outputContent = JSON.stringify(result, null, 2);
565
+ showConsole = false;
566
+ console.log(outputContent);
567
+ break;
568
+ case 'sarif':
569
+ outputContent = JSON.stringify(toSARIF(result, targetPath), null, 2);
570
+ showConsole = false;
571
+ if (!outputFile)
572
+ console.log(outputContent);
573
+ break;
574
+ case 'junit':
575
+ outputContent = toJUnit(result);
576
+ showConsole = false;
577
+ if (!outputFile)
578
+ console.log(outputContent);
579
+ break;
580
+ case 'gitlab-sast':
581
+ outputContent = JSON.stringify(toGitLabReport(result, 'sast'), null, 2);
582
+ showConsole = false;
583
+ if (!outputFile)
584
+ console.log(outputContent);
585
+ break;
586
+ case 'gitlab-deps':
587
+ outputContent = JSON.stringify(toGitLabReport(result, 'dependency_scanning'), null, 2);
588
+ showConsole = false;
589
+ if (!outputFile)
590
+ console.log(outputContent);
591
+ break;
592
+ case 'summary':
593
+ outputContent = JSON.stringify(toJSONSummary(result), null, 2);
594
+ showConsole = false;
595
+ console.log(outputContent);
596
+ break;
597
+ default:
598
+ outputContent = JSON.stringify(result, null, 2);
599
+ displayScanResults(result);
600
+ }
601
+ // Write to file if requested
602
+ if (outputFile) {
603
+ const outputPath = resolve(outputFile);
604
+ writeFileSync(outputPath, outputContent);
605
+ if (showConsole) {
606
+ console.log('');
607
+ }
608
+ console.log(c('green', `✓ Results saved to: ${outputPath}`));
609
+ }
610
+ // Exit with appropriate code based on --fail-on flag
611
+ const hasCritical = result.secrets.some(s => s.severity === 'critical') ||
612
+ result.packages.some(p => p.severity === 'critical') ||
613
+ result.iacFindings.some(i => i.severity === 'critical') ||
614
+ result.dockerfileFindings.some(d => d.severity === 'critical');
615
+ const hasHigh = result.secrets.some(s => s.severity === 'high') ||
616
+ result.packages.some(p => p.severity === 'high') ||
617
+ result.iacFindings.some(i => i.severity === 'high') ||
618
+ result.dockerfileFindings.some(d => d.severity === 'high');
619
+ const hasMedium = result.secrets.some(s => s.severity === 'medium') ||
620
+ result.packages.some(p => p.severity === 'medium') ||
621
+ result.iacFindings.some(i => i.severity === 'medium') ||
622
+ result.dockerfileFindings.some(d => d.severity === 'medium');
623
+ if (failOn.includes('critical') && hasCritical) {
624
+ process.exit(2); // Critical findings
625
+ }
626
+ else if (failOn.includes('high') && hasHigh) {
627
+ process.exit(1); // High findings
628
+ }
629
+ else if (failOn.includes('medium') && hasMedium) {
630
+ process.exit(1); // Medium findings
631
+ }
632
+ process.exit(0); // Clean
633
+ }
634
+ function displayScanResults(result) {
635
+ // Summary counts
636
+ const secretCount = result.secrets.length;
637
+ const packageCount = result.packages.length;
638
+ const sastCount = result.sastFindings.length;
639
+ const iacCount = result.iacFindings.length;
640
+ const dockerfileCount = result.dockerfileFindings.length;
641
+ const serviceCount = result.discoveredServices.length;
642
+ const moduleCount = result.discoveredModules.length;
643
+ const criticalCount = result.secrets.filter(s => s.severity === 'critical').length +
644
+ result.packages.filter(p => p.severity === 'critical').length +
645
+ result.iacFindings.filter(i => i.severity === 'critical').length +
646
+ result.dockerfileFindings.filter(d => d.severity === 'critical').length;
647
+ const highCount = result.secrets.filter(s => s.severity === 'high').length +
648
+ result.packages.filter(p => p.severity === 'high').length +
649
+ result.iacFindings.filter(i => i.severity === 'high').length +
650
+ result.dockerfileFindings.filter(d => d.severity === 'high').length;
651
+ const mediumCount = result.secrets.filter(s => s.severity === 'medium').length +
652
+ result.packages.filter(p => p.severity === 'medium').length +
653
+ result.iacFindings.filter(i => i.severity === 'medium').length +
654
+ result.dockerfileFindings.filter(d => d.severity === 'medium').length;
655
+ const lowCount = result.secrets.filter(s => s.severity === 'low').length +
656
+ result.packages.filter(p => p.severity === 'low').length +
657
+ result.iacFindings.filter(i => i.severity === 'low').length +
658
+ result.dockerfileFindings.filter(d => d.severity === 'low').length;
659
+ // Stats bar
660
+ console.log('┌─────────────────────────────────────────────────────────────────┐');
661
+ console.log(`│ ${c('bgRed', ` CRITICAL ${criticalCount} `)} ${c('red', `HIGH ${highCount}`)} ${c('yellow', `MEDIUM ${mediumCount}`)} ${c('dim', `LOW ${lowCount}`)} │`);
662
+ console.log('└─────────────────────────────────────────────────────────────────┘');
663
+ console.log('');
664
+ // Tools used
665
+ console.log(c('cyan', '► Tools Used:'));
666
+ console.log(` ${result.toolsUsed.join(', ') || 'regex-patterns (fallback)'}`);
667
+ console.log('');
668
+ // Secrets
669
+ if (secretCount > 0) {
670
+ console.log(c('red', `► Secrets Found (${secretCount}):`));
671
+ for (const s of result.secrets.slice(0, 10)) {
672
+ const sev = s.severity === 'critical' ? c('bgRed', ' CRIT ') :
673
+ s.severity === 'high' ? c('red', ' HIGH ') :
674
+ s.severity === 'medium' ? c('yellow', ' MED ') : c('dim', ' LOW ');
675
+ console.log(` ${sev} ${s.type}`);
676
+ console.log(` ${c('dim', s.file)}:${s.line}`);
677
+ }
678
+ if (secretCount > 10) {
679
+ console.log(c('dim', ` ... and ${secretCount - 10} more`));
680
+ }
681
+ console.log('');
682
+ }
683
+ // Package vulnerabilities
684
+ if (packageCount > 0) {
685
+ console.log(c('yellow', `► Package Vulnerabilities (${packageCount}):`));
686
+ for (const p of result.packages.slice(0, 10)) {
687
+ const sev = p.severity === 'critical' ? c('bgRed', ' CRIT ') :
688
+ p.severity === 'high' ? c('red', ' HIGH ') :
689
+ p.severity === 'medium' ? c('yellow', ' MED ') : c('dim', ' LOW ');
690
+ console.log(` ${sev} ${p.name}@${p.version}`);
691
+ if (p.vulnId) {
692
+ console.log(` ${c('dim', p.vulnId)}${p.title ? ': ' + p.title : ''}`);
693
+ }
694
+ if (p.fixedVersion) {
695
+ console.log(` ${c('green', 'Fix:')} Upgrade to ${p.fixedVersion}`);
696
+ }
697
+ }
698
+ if (packageCount > 10) {
699
+ console.log(c('dim', ` ... and ${packageCount - 10} more`));
700
+ }
701
+ console.log('');
702
+ }
703
+ // SAST findings
704
+ if (sastCount > 0) {
705
+ console.log(c('magenta', `► SAST Findings (${sastCount}):`));
706
+ for (const s of result.sastFindings.slice(0, 10)) {
707
+ console.log(` [${s.severity}] ${s.rule}`);
708
+ console.log(` ${c('dim', s.file)}:${s.line}`);
709
+ console.log(` ${s.message}`);
710
+ }
711
+ if (sastCount > 10) {
712
+ console.log(c('dim', ` ... and ${sastCount - 10} more`));
713
+ }
714
+ console.log('');
715
+ }
716
+ // IaC findings
717
+ if (iacCount > 0) {
718
+ console.log(c('cyan', `► IaC Security Issues (${iacCount}):`));
719
+ for (const i of result.iacFindings.slice(0, 10)) {
720
+ const sev = i.severity === 'critical' ? c('bgRed', ' CRIT ') :
721
+ i.severity === 'high' ? c('red', ' HIGH ') :
722
+ i.severity === 'medium' ? c('yellow', ' MED ') : c('dim', ' LOW ');
723
+ console.log(` ${sev} ${i.checkId}`);
724
+ console.log(` ${c('dim', i.file)} - ${i.resource}`);
725
+ if (i.guideline) {
726
+ console.log(` ${c('green', 'Guide:')} ${i.guideline}`);
727
+ }
728
+ }
729
+ if (iacCount > 10) {
730
+ console.log(c('dim', ` ... and ${iacCount - 10} more`));
731
+ }
732
+ console.log('');
733
+ }
734
+ // Dockerfile findings
735
+ if (dockerfileCount > 0) {
736
+ console.log(c('blue', `► Dockerfile Issues (${dockerfileCount}):`));
737
+ for (const d of result.dockerfileFindings.slice(0, 10)) {
738
+ const sev = d.severity === 'critical' ? c('bgRed', ' CRIT ') :
739
+ d.severity === 'high' ? c('red', ' HIGH ') :
740
+ d.severity === 'medium' ? c('yellow', ' MED ') : c('dim', ' LOW ');
741
+ console.log(` ${sev} ${d.code}`);
742
+ console.log(` ${c('dim', d.file)}:${d.line}`);
743
+ console.log(` ${d.message}`);
744
+ }
745
+ if (dockerfileCount > 10) {
746
+ console.log(c('dim', ` ... and ${dockerfileCount - 10} more`));
747
+ }
748
+ console.log('');
749
+ }
750
+ // Languages detected
751
+ if (result.languagesDetected && result.languagesDetected.length > 0) {
752
+ console.log(c('dim', '► Languages Detected:'));
753
+ console.log(` ${result.languagesDetected.join(', ')}`);
754
+ console.log('');
755
+ }
756
+ // Discovered services
757
+ if (serviceCount > 0) {
758
+ console.log(c('blue', `► Discovered Services (${serviceCount}):`));
759
+ for (const s of result.discoveredServices) {
760
+ const typeColor = s.type === 'database' ? 'blue' :
761
+ s.type === 'cloud' ? 'cyan' :
762
+ s.type === 'api' ? 'yellow' :
763
+ s.type === 'auth' ? 'green' : 'white';
764
+ console.log(` ${c(typeColor, `[${s.type.toUpperCase()}]`)} ${s.name}`);
765
+ console.log(` ${c('dim', 'Source:')} ${s.source}`);
766
+ }
767
+ console.log('');
768
+ }
769
+ // Discovered modules
770
+ if (moduleCount > 0) {
771
+ console.log(c('green', `► Codebase Structure (${moduleCount} modules):`));
772
+ for (const m of result.discoveredModules) {
773
+ console.log(` [${m.type}] ${m.name} (${m.fileCount} files)`);
774
+ }
775
+ console.log('');
776
+ }
777
+ // Git info
778
+ if (result.gitInfo) {
779
+ console.log(c('dim', '► Git Info:'));
780
+ console.log(` Branch: ${result.gitInfo.branch}`);
781
+ if (result.gitInfo.remoteUrl) {
782
+ console.log(` Remote: ${result.gitInfo.remoteUrl}`);
783
+ }
784
+ if (result.gitInfo.uncommittedChanges > 0) {
785
+ console.log(` ${c('yellow', `Uncommitted changes: ${result.gitInfo.uncommittedChanges}`)}`);
786
+ }
787
+ console.log('');
788
+ }
789
+ // Env files
790
+ if (result.envFiles.length > 0) {
791
+ console.log(c('dim', '► Environment Files:'));
792
+ for (const e of result.envFiles) {
793
+ const warning = e.hasSecrets ? c('yellow', ' (contains secrets)') : '';
794
+ console.log(` ${e.file}${warning} - ${e.variables.length} variables`);
795
+ }
796
+ console.log('');
797
+ }
798
+ // Summary
799
+ const totalIssues = criticalCount + highCount + mediumCount + lowCount;
800
+ if (totalIssues === 0) {
801
+ console.log(c('green', '✓ No security issues found!'));
802
+ }
803
+ else {
804
+ console.log('─'.repeat(67));
805
+ console.log(`Total issues: ${totalIssues}`);
806
+ if (criticalCount > 0) {
807
+ console.log(c('red', `⚠ ${criticalCount} critical issues require immediate attention!`));
808
+ }
809
+ }
810
+ }
811
+ // ============ AWS SCAN COMMAND ============
812
+ async function runAWSScan(args) {
813
+ // Parse arguments
814
+ let region = process.env.AWS_REGION || 'us-east-1';
815
+ let profile;
816
+ let services;
817
+ let outputFormat = 'console';
818
+ let outputFile;
819
+ for (let i = 0; i < args.length; i++) {
820
+ const arg = args[i];
821
+ if (arg === '--region' || arg === '-r') {
822
+ region = args[++i];
823
+ }
824
+ else if (arg === '--profile' || arg === '-p') {
825
+ profile = args[++i];
826
+ }
827
+ else if (arg === '--services' || arg === '-s') {
828
+ services = args[++i].split(',');
829
+ }
830
+ else if (arg === '--json' || arg === '-j') {
831
+ outputFormat = 'json';
832
+ }
833
+ else if (arg === '--output' || arg === '-o') {
834
+ outputFile = args[++i];
835
+ outputFormat = 'both';
836
+ }
837
+ }
838
+ // Print header
839
+ console.log('');
840
+ console.log(c('cyan', '╔═══════════════════════════════════════════════════════════════╗'));
841
+ console.log(c('cyan', '║') + c('bold', ' AURASECURITY - AWS Security Scan ') + c('cyan', '║'));
842
+ console.log(c('cyan', '╚═══════════════════════════════════════════════════════════════╝'));
843
+ console.log('');
844
+ console.log(c('dim', `Region: ${region}`));
845
+ if (profile)
846
+ console.log(c('dim', `Profile: ${profile}`));
847
+ console.log(c('dim', `Time: ${new Date().toISOString()}`));
848
+ console.log('');
849
+ // Check for AWS credentials
850
+ if (!process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_PROFILE && !profile) {
851
+ console.log(c('yellow', '⚠ No AWS credentials detected.'));
852
+ console.log('');
853
+ console.log('Set credentials using one of these methods:');
854
+ console.log(` 1. Environment variables: ${c('cyan', 'AWS_ACCESS_KEY_ID')} and ${c('cyan', 'AWS_SECRET_ACCESS_KEY')}`);
855
+ console.log(` 2. AWS profile: ${c('cyan', 'aura-security aws --profile <name>')}`);
856
+ console.log(` 3. AWS config file: ${c('cyan', '~/.aws/credentials')}`);
857
+ console.log('');
858
+ process.exit(1);
859
+ }
860
+ console.log(c('yellow', '⏳ Scanning AWS infrastructure...'));
861
+ console.log('');
862
+ const startTime = Date.now();
863
+ try {
864
+ const result = await scanAWS({ region, profile, services });
865
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
866
+ console.log('');
867
+ console.log(c('green', `✓ Scan completed in ${elapsed}s`));
868
+ console.log('');
869
+ // Display results
870
+ if (outputFormat === 'json') {
871
+ console.log(JSON.stringify(result, null, 2));
872
+ }
873
+ else {
874
+ displayAWSResults(result);
875
+ }
876
+ // Write to file if requested
877
+ if (outputFile) {
878
+ const outputPath = resolve(outputFile);
879
+ writeFileSync(outputPath, JSON.stringify(result, null, 2));
880
+ console.log('');
881
+ console.log(c('green', `✓ Results saved to: ${outputPath}`));
882
+ }
883
+ // Exit with appropriate code
884
+ if (result.summary.critical > 0) {
885
+ process.exit(2);
886
+ }
887
+ else if (result.summary.high > 0) {
888
+ process.exit(1);
889
+ }
890
+ process.exit(0);
891
+ }
892
+ catch (err) {
893
+ console.error(c('red', 'AWS scan failed:'), err.message || err);
894
+ console.log('');
895
+ console.log('Possible causes:');
896
+ console.log(' - Invalid credentials');
897
+ console.log(' - Insufficient IAM permissions');
898
+ console.log(' - Network connectivity issues');
899
+ console.log('');
900
+ process.exit(1);
901
+ }
902
+ }
903
+ // ============ SBOM COMMAND ============
904
+ async function runSBOM(args) {
905
+ let targetPath = '.';
906
+ let format = 'cyclonedx';
907
+ let outputFile;
908
+ let includeVulns = false;
909
+ let projectName;
910
+ for (let i = 0; i < args.length; i++) {
911
+ const arg = args[i];
912
+ if (arg === '--format' || arg === '-f') {
913
+ const fmt = args[++i]?.toLowerCase();
914
+ if (fmt === 'cyclonedx' || fmt === 'spdx') {
915
+ format = fmt;
916
+ }
917
+ else {
918
+ console.error(c('red', `Unknown SBOM format: ${fmt}`));
919
+ console.log('Available formats: cyclonedx, spdx');
920
+ process.exit(1);
921
+ }
922
+ }
923
+ else if (arg === '--output' || arg === '-o') {
924
+ outputFile = args[++i];
925
+ }
926
+ else if (arg === '--include-vulns' || arg === '--vulns') {
927
+ includeVulns = true;
928
+ }
929
+ else if (arg === '--name') {
930
+ projectName = args[++i];
931
+ }
932
+ else if (!arg.startsWith('-')) {
933
+ targetPath = arg;
934
+ }
935
+ }
936
+ targetPath = resolve(targetPath);
937
+ if (!existsSync(targetPath)) {
938
+ console.error(c('red', `Error: Path does not exist: ${targetPath}`));
939
+ process.exit(1);
940
+ }
941
+ console.log('');
942
+ console.log(c('cyan', '╔═══════════════════════════════════════════════════════════════╗'));
943
+ console.log(c('cyan', '║') + c('bold', ' AURASECURITY - SBOM Generator ') + c('cyan', '║'));
944
+ console.log(c('cyan', '╚═══════════════════════════════════════════════════════════════╝'));
945
+ console.log('');
946
+ console.log(c('dim', `Target: ${targetPath}`));
947
+ console.log(c('dim', `Format: ${format.toUpperCase()}`));
948
+ console.log('');
949
+ console.log(c('yellow', '⏳ Generating SBOM...'));
950
+ // Run a quick scan if including vulnerabilities
951
+ let scanResult;
952
+ if (includeVulns) {
953
+ console.log(c('dim', ' Including vulnerability information...'));
954
+ scanResult = await quickLocalScan(targetPath);
955
+ }
956
+ const sbom = generateSBOM(targetPath, scanResult, {
957
+ format,
958
+ includeVulnerabilities: includeVulns,
959
+ projectName: projectName || basename(targetPath)
960
+ });
961
+ const output = JSON.stringify(sbom, null, 2);
962
+ console.log('');
963
+ console.log(c('green', '✓ SBOM generated successfully'));
964
+ if (outputFile) {
965
+ const outputPath = resolve(outputFile);
966
+ writeFileSync(outputPath, output);
967
+ console.log(c('green', `✓ Saved to: ${outputPath}`));
968
+ }
969
+ else {
970
+ console.log('');
971
+ console.log(output);
972
+ }
973
+ // Summary
974
+ const componentCount = 'components' in sbom ? sbom.components.length : ('packages' in sbom ? sbom.packages.length : 0);
975
+ console.log('');
976
+ console.log(c('dim', `Components: ${componentCount}`));
977
+ if (includeVulns && scanResult) {
978
+ console.log(c('dim', `Vulnerabilities: ${scanResult.packages.length}`));
979
+ }
980
+ }
981
+ // ============ LICENSE CHECK COMMAND ============
982
+ async function runLicenseCheck(args) {
983
+ let targetPath = '.';
984
+ let policy = 'permissive';
985
+ let allowedLicenses = [];
986
+ let deniedLicenses = [];
987
+ let outputFile;
988
+ let outputJson = false;
989
+ for (let i = 0; i < args.length; i++) {
990
+ const arg = args[i];
991
+ if (arg === '--policy' || arg === '-p') {
992
+ policy = args[++i] || 'permissive';
993
+ if (!POLICY_NAMES.includes(policy)) {
994
+ console.error(c('red', `Unknown policy: ${policy}`));
995
+ console.log(`Available policies: ${POLICY_NAMES.join(', ')}`);
996
+ process.exit(1);
997
+ }
998
+ }
999
+ else if (arg === '--allow') {
1000
+ allowedLicenses = args[++i].split(',').map(s => s.trim());
1001
+ }
1002
+ else if (arg === '--deny') {
1003
+ deniedLicenses = args[++i].split(',').map(s => s.trim());
1004
+ }
1005
+ else if (arg === '--output' || arg === '-o') {
1006
+ outputFile = args[++i];
1007
+ }
1008
+ else if (arg === '--json') {
1009
+ outputJson = true;
1010
+ }
1011
+ else if (!arg.startsWith('-')) {
1012
+ targetPath = arg;
1013
+ }
1014
+ }
1015
+ targetPath = resolve(targetPath);
1016
+ if (!existsSync(targetPath)) {
1017
+ console.error(c('red', `Error: Path does not exist: ${targetPath}`));
1018
+ process.exit(1);
1019
+ }
1020
+ console.log('');
1021
+ console.log(c('cyan', '╔═══════════════════════════════════════════════════════════════╗'));
1022
+ console.log(c('cyan', '║') + c('bold', ' AURASECURITY - License Compliance ') + c('cyan', '║'));
1023
+ console.log(c('cyan', '╚═══════════════════════════════════════════════════════════════╝'));
1024
+ console.log('');
1025
+ console.log(c('dim', `Target: ${targetPath}`));
1026
+ console.log(c('dim', `Policy: ${policy}`));
1027
+ console.log('');
1028
+ console.log(c('yellow', '⏳ Checking licenses...'));
1029
+ console.log('');
1030
+ const result = checkLicenses(targetPath, {
1031
+ policy,
1032
+ allowedLicenses: allowedLicenses.length > 0 ? allowedLicenses : undefined,
1033
+ deniedLicenses: deniedLicenses.length > 0 ? deniedLicenses : undefined
1034
+ });
1035
+ if (outputJson) {
1036
+ const output = JSON.stringify(result, null, 2);
1037
+ if (outputFile) {
1038
+ writeFileSync(resolve(outputFile), output);
1039
+ console.log(c('green', `✓ Results saved to: ${outputFile}`));
1040
+ }
1041
+ else {
1042
+ console.log(output);
1043
+ }
1044
+ }
1045
+ else {
1046
+ // Display results
1047
+ console.log('┌─────────────────────────────────────────────────────────────────┐');
1048
+ console.log(`│ Total: ${result.summary.total} ${c('green', `Compliant: ${result.summary.compliant}`)} ${c('red', `Violations: ${result.summary.violations}`)} ${c('yellow', `Unknown: ${result.summary.unknown}`)} │`);
1049
+ console.log('└─────────────────────────────────────────────────────────────────┘');
1050
+ console.log('');
1051
+ if (result.violations.length > 0) {
1052
+ console.log(c('red', `► License Violations (${result.violations.length}):`));
1053
+ for (const v of result.violations.slice(0, 15)) {
1054
+ const sev = v.severity === 'high' ? c('red', ' HIGH ') :
1055
+ v.severity === 'medium' ? c('yellow', ' MED ') : c('dim', ' LOW ');
1056
+ console.log(` ${sev} ${v.package}@${v.version}`);
1057
+ console.log(` License: ${v.license}`);
1058
+ console.log(` ${c('dim', v.violation)}`);
1059
+ }
1060
+ if (result.violations.length > 15) {
1061
+ console.log(c('dim', ` ... and ${result.violations.length - 15} more`));
1062
+ }
1063
+ console.log('');
1064
+ }
1065
+ // Show some compliant licenses
1066
+ const compliant = result.licenses.filter(l => !result.violations.some(v => v.package === l.package && v.version === l.version));
1067
+ if (compliant.length > 0) {
1068
+ console.log(c('green', `► Compliant Packages (${compliant.length}):`));
1069
+ const licenseCounts = new Map();
1070
+ for (const l of compliant) {
1071
+ licenseCounts.set(l.license, (licenseCounts.get(l.license) || 0) + 1);
1072
+ }
1073
+ const sorted = [...licenseCounts.entries()].sort((a, b) => b[1] - a[1]);
1074
+ for (const [license, count] of sorted.slice(0, 10)) {
1075
+ console.log(` ${license}: ${count} packages`);
1076
+ }
1077
+ console.log('');
1078
+ }
1079
+ if (outputFile) {
1080
+ writeFileSync(resolve(outputFile), JSON.stringify(result, null, 2));
1081
+ console.log(c('green', `✓ Results saved to: ${outputFile}`));
1082
+ }
1083
+ }
1084
+ // Exit with error if violations found
1085
+ const highViolations = result.violations.filter(v => v.severity === 'high').length;
1086
+ if (highViolations > 0) {
1087
+ process.exit(1);
1088
+ }
1089
+ }
1090
+ function displayAWSResults(result) {
1091
+ const { summary, findings, scannedServices, errors } = result;
1092
+ // Stats bar
1093
+ console.log('┌─────────────────────────────────────────────────────────────────┐');
1094
+ console.log(`│ ${c('bgRed', ` CRITICAL ${summary.critical} `)} ${c('red', `HIGH ${summary.high}`)} ${c('yellow', `MEDIUM ${summary.medium}`)} ${c('dim', `LOW ${summary.low}`)} INFO ${summary.info} │`);
1095
+ console.log('└─────────────────────────────────────────────────────────────────┘');
1096
+ console.log('');
1097
+ // Scanned services
1098
+ console.log(c('cyan', '► Services Scanned:'));
1099
+ console.log(` ${scannedServices.join(', ') || 'None'}`);
1100
+ console.log('');
1101
+ // Errors
1102
+ if (errors.length > 0) {
1103
+ console.log(c('red', '► Scan Errors:'));
1104
+ for (const err of errors) {
1105
+ console.log(` [${err.service}] ${err.error}`);
1106
+ }
1107
+ console.log('');
1108
+ }
1109
+ // Group findings by service
1110
+ const byService = new Map();
1111
+ for (const finding of findings) {
1112
+ const existing = byService.get(finding.service) || [];
1113
+ existing.push(finding);
1114
+ byService.set(finding.service, existing);
1115
+ }
1116
+ // Display findings by service
1117
+ for (const [service, serviceFindings] of byService) {
1118
+ const serviceUpper = service.toUpperCase();
1119
+ const serviceColor = service === 'iam' ? 'green' :
1120
+ service === 's3' ? 'blue' :
1121
+ service === 'ec2' ? 'yellow' :
1122
+ service === 'lambda' ? 'magenta' :
1123
+ service === 'rds' ? 'cyan' : 'white';
1124
+ console.log(c(serviceColor, `► ${serviceUpper} Findings (${serviceFindings.length}):`));
1125
+ for (const f of serviceFindings.slice(0, 10)) {
1126
+ const sev = f.severity === 'critical' ? c('bgRed', ' CRIT ') :
1127
+ f.severity === 'high' ? c('red', ' HIGH ') :
1128
+ f.severity === 'medium' ? c('yellow', ' MED ') :
1129
+ f.severity === 'low' ? c('dim', ' LOW ') : c('dim', ' INFO ');
1130
+ console.log(` ${sev} ${f.title}`);
1131
+ console.log(` ${c('dim', f.resourceType)}: ${f.resourceId}`);
1132
+ if (f.remediation) {
1133
+ console.log(` ${c('green', 'Fix:')} ${f.remediation}`);
1134
+ }
1135
+ }
1136
+ if (serviceFindings.length > 10) {
1137
+ console.log(c('dim', ` ... and ${serviceFindings.length - 10} more`));
1138
+ }
1139
+ console.log('');
1140
+ }
1141
+ // Summary
1142
+ if (summary.total === 0) {
1143
+ console.log(c('green', '✓ No security issues found in AWS!'));
1144
+ }
1145
+ else {
1146
+ console.log('─'.repeat(67));
1147
+ console.log(`Total AWS findings: ${summary.total}`);
1148
+ if (summary.critical > 0) {
1149
+ console.log(c('red', `⚠ ${summary.critical} critical issues require immediate attention!`));
1150
+ }
1151
+ }
1152
+ }
1153
+ // ============ SERVER COMMANDS ============
1154
+ async function startServer() {
1155
+ console.log(c('cyan', '🚀 Starting aurasecurity server...'));
1156
+ console.log('');
1157
+ // Dynamically import index.js which auto-starts the server
1158
+ try {
1159
+ await import('./index.js');
1160
+ // The server runs until stopped via SIGINT/SIGTERM
1161
+ }
1162
+ catch (err) {
1163
+ console.error(c('red', 'Failed to start server:'), err);
1164
+ process.exit(1);
1165
+ }
1166
+ }
1167
+ async function startVisualizer() {
1168
+ console.log(c('cyan', '🎮 Starting 3D Visualizer...'));
1169
+ console.log('');
1170
+ try {
1171
+ // Dynamically import serve-visualizer.js which auto-starts
1172
+ await import('./serve-visualizer.js');
1173
+ }
1174
+ catch (err) {
1175
+ console.error(c('red', 'Failed to start visualizer:'), err);
1176
+ process.exit(1);
1177
+ }
1178
+ }
1179
+ async function showStatus() {
1180
+ try {
1181
+ const res = await fetch(`${AURA_URL}/info`);
1182
+ const info = await res.json();
1183
+ console.log('');
1184
+ console.log(c('cyan', '╔═══════════════════════════════════════╗'));
1185
+ console.log(c('cyan', '║') + c('bold', ' AURASECURITY STATUS ') + c('cyan', '║'));
1186
+ console.log(c('cyan', '╚═══════════════════════════════════════╝'));
1187
+ console.log('');
1188
+ console.log(` Server: ${c('green', AURA_URL)}`);
1189
+ console.log(` Name: ${info.name}`);
1190
+ console.log(` Version: ${info.version}`);
1191
+ console.log(` Tools: ${info.tools.join(', ')}`);
1192
+ console.log(` Endpoints: ${info.endpoints.join(', ')}`);
1193
+ console.log('');
1194
+ }
1195
+ catch (err) {
1196
+ console.error(c('red', '✗ Cannot connect to Aura server at'), AURA_URL);
1197
+ console.log('');
1198
+ console.log(' Is the server running? Start it with:');
1199
+ console.log(c('cyan', ' aura-security serve'));
1200
+ console.log('');
1201
+ process.exit(1);
1202
+ }
1203
+ }
1204
+ async function runAudit(args) {
1205
+ const inputFile = args[0];
1206
+ if (!inputFile) {
1207
+ // Run interactive demo
1208
+ const demoInput = {
1209
+ tool: 'audit',
1210
+ arguments: {
1211
+ change_event: {
1212
+ id: 'demo-' + Date.now(),
1213
+ type: 'pull_request',
1214
+ environment: 'staging',
1215
+ repo: 'demo/app',
1216
+ commit: 'a'.repeat(40),
1217
+ files_changed: ['src/auth/login.ts'],
1218
+ diff: '+const API_KEY = "secret123";'
1219
+ },
1220
+ evidence_bundle: {
1221
+ vuln_scan: 'critical: 1\nhigh: 2'
1222
+ },
1223
+ policy_context: {
1224
+ critical_assets: ['auth'],
1225
+ risk_tolerance: 'low'
1226
+ }
1227
+ }
1228
+ };
1229
+ console.log('');
1230
+ console.log(c('yellow', '🔍 Running demo audit...'));
1231
+ console.log('');
1232
+ try {
1233
+ const res = await fetch(`${AURA_URL}/tools`, {
1234
+ method: 'POST',
1235
+ headers: { 'Content-Type': 'application/json' },
1236
+ body: JSON.stringify(demoInput)
1237
+ });
1238
+ const data = await res.json();
1239
+ const output = data.result;
1240
+ console.log(visualize(output));
1241
+ console.log('');
1242
+ console.log(visualizeState(output.agent_state));
1243
+ console.log('');
1244
+ console.log(visualizeCompact(output));
1245
+ }
1246
+ catch (err) {
1247
+ console.error(c('red', '✗ Server not reachable. Start it with: aura-security serve'));
1248
+ process.exit(1);
1249
+ }
1250
+ }
1251
+ else {
1252
+ // Load from file
1253
+ const { readFileSync } = await import('fs');
1254
+ const input = JSON.parse(readFileSync(inputFile, 'utf-8'));
1255
+ const payload = input.tool ? input : { tool: 'audit', arguments: input };
1256
+ const res = await fetch(`${AURA_URL}/tools`, {
1257
+ method: 'POST',
1258
+ headers: { 'Content-Type': 'application/json' },
1259
+ body: JSON.stringify(payload)
1260
+ });
1261
+ const data = await res.json();
1262
+ const output = data.result;
1263
+ console.log(visualize(output));
1264
+ }
1265
+ }
1266
+ async function showLogs() {
1267
+ try {
1268
+ const res = await fetch(`${AURA_URL}/memory`);
1269
+ const data = await res.json();
1270
+ console.log('');
1271
+ console.log(c('cyan', '╔═══════════════════════════════════════╗'));
1272
+ console.log(c('cyan', '║') + c('bold', ' AUDIT LOG ENTRIES ') + c('cyan', '║'));
1273
+ console.log(c('cyan', '╚═══════════════════════════════════════╝'));
1274
+ console.log('');
1275
+ if (data.keys.length === 0) {
1276
+ console.log(' No audit logs found.');
1277
+ }
1278
+ else {
1279
+ for (const key of data.keys) {
1280
+ const parts = key.split(':');
1281
+ console.log(` • ${parts[1]} (${new Date(parseInt(parts[2])).toISOString()})`);
1282
+ }
1283
+ }
1284
+ console.log('');
1285
+ }
1286
+ catch {
1287
+ console.error(c('red', '✗ Server not reachable. Start it with: aura-security serve'));
1288
+ process.exit(1);
1289
+ }
1290
+ }
1291
+ async function watchMode() {
1292
+ console.log('');
1293
+ console.log(c('cyan', '👁️ aurasecurity Watch Mode'));
1294
+ console.log(c('dim', ' Monitoring for new audits... (Ctrl+C to exit)'));
1295
+ console.log('');
1296
+ let lastCount = 0;
1297
+ setInterval(async () => {
1298
+ try {
1299
+ const res = await fetch(`${AURA_URL}/memory`);
1300
+ const data = await res.json();
1301
+ if (data.keys.length > lastCount) {
1302
+ const newKeys = data.keys.slice(lastCount);
1303
+ for (const key of newKeys) {
1304
+ const entryRes = await fetch(`${AURA_URL}/memory?key=${encodeURIComponent(key)}`);
1305
+ const entry = await entryRes.json();
1306
+ if (entry.value) {
1307
+ console.log('');
1308
+ console.log(c('green', '━━━ NEW AUDIT ━━━'));
1309
+ console.log(visualizeCompact(entry.value));
1310
+ }
1311
+ }
1312
+ lastCount = data.keys.length;
1313
+ }
1314
+ }
1315
+ catch {
1316
+ // Ignore errors in watch mode
1317
+ }
1318
+ }, 2000);
1319
+ }
1320
+ function showHelp() {
1321
+ console.log(`
1322
+ ${c('cyan', 'aurasecurity')} - Security Scanner & Audit Pipeline
1323
+ ${c('dim', `Version ${VERSION}`)}
1324
+
1325
+ ${c('bold', 'USAGE:')}
1326
+ aura-security <command> [options]
1327
+
1328
+ ${c('bold', 'COMMANDS:')}
1329
+ ${c('green', 'doctor')} Check installed security tools and show install commands
1330
+ ${c('green', 'init')} [path] Initialize config in a project directory
1331
+ ${c('green', 'scan')} <path> Scan a directory for security issues (standalone)
1332
+ ${c('green', 'sbom')} <path> Generate Software Bill of Materials
1333
+ ${c('green', 'license-check')} <path> Check license compliance
1334
+ ${c('green', 'aws')} Scan AWS infrastructure for security issues
1335
+ ${c('green', 'serve')} Start the Aura server (port 3000)
1336
+ ${c('green', 'visualizer')} Start the 3D visualizer (port 8080)
1337
+ ${c('green', 'status')} Show server connection status
1338
+ ${c('green', 'audit')} [file] Run audit via server (demo if no file)
1339
+ ${c('green', 'logs')} Show audit log entries from server
1340
+ ${c('green', 'watch')} Watch for new audits in real-time
1341
+
1342
+ ${c('bold', 'SCAN OPTIONS:')}
1343
+ -j, --json Output results as JSON
1344
+ -f, --format Output format: console, json, sarif, junit, gitlab-sast, gitlab-deps, summary
1345
+ -o, --output Save results to file
1346
+ -l, --languages Filter by languages: js,py,go,rust,ruby,php,java
1347
+ -S, --scanners Use specific scanners: grype,trivy,gitleaks,semgrep,checkov,hadolint,pip-audit,govulncheck,cargo-audit,bundle-audit,composer
1348
+ --fail-on Exit with error on severity: critical,high,medium (default: critical)
1349
+ --no-fail Never exit with error code
1350
+
1351
+ ${c('bold', 'SBOM OPTIONS:')}
1352
+ -f, --format SBOM format: cyclonedx (default), spdx
1353
+ -o, --output Save SBOM to file
1354
+ --include-vulns Include vulnerability information
1355
+ --name Project name for SBOM metadata
1356
+
1357
+ ${c('bold', 'LICENSE OPTIONS:')}
1358
+ -p, --policy License policy: permissive (default), strict, copyleft
1359
+ --allow Additional licenses to allow (comma-separated)
1360
+ --deny Additional licenses to deny (comma-separated)
1361
+ --json Output as JSON
1362
+ -o, --output Save results to file
1363
+
1364
+ ${c('bold', 'AWS OPTIONS:')}
1365
+ -r, --region AWS region (default: us-east-1)
1366
+ -p, --profile AWS profile name
1367
+ -s, --services Services to scan: iam,s3,ec2,lambda,rds
1368
+
1369
+ ${c('bold', 'EXAMPLES:')}
1370
+ ${c('dim', '# Initialize config in current directory')}
1371
+ aura-security init
1372
+
1373
+ ${c('dim', '# Scan current directory')}
1374
+ aura-security scan .
1375
+
1376
+ ${c('dim', '# Scan with SARIF output (GitHub Code Scanning)')}
1377
+ aura-security scan . --format sarif -o results.sarif
1378
+
1379
+ ${c('dim', '# Scan with GitLab security report format')}
1380
+ aura-security scan . --format gitlab-sast -o gl-sast-report.json
1381
+
1382
+ ${c('dim', '# Scan only Python projects')}
1383
+ aura-security scan . --languages py
1384
+
1385
+ ${c('dim', '# Use specific scanners')}
1386
+ aura-security scan . --scanners grype,checkov,hadolint
1387
+
1388
+ ${c('dim', '# Generate CycloneDX SBOM')}
1389
+ aura-security sbom . --format cyclonedx -o sbom.json
1390
+
1391
+ ${c('dim', '# Generate SPDX SBOM with vulnerabilities')}
1392
+ aura-security sbom . --format spdx --include-vulns -o sbom-spdx.json
1393
+
1394
+ ${c('dim', '# Check license compliance')}
1395
+ aura-security license-check . --policy strict
1396
+
1397
+ ${c('dim', '# License check with custom allowed licenses')}
1398
+ aura-security license-check . --allow "MPL-2.0,LGPL-3.0"
1399
+
1400
+ ${c('dim', '# Scan AWS infrastructure')}
1401
+ aura-security aws --region us-west-2
1402
+
1403
+ ${c('dim', '# Start full stack (2 terminals)')}
1404
+ aura-security serve ${c('dim', '# Terminal 1')}
1405
+ aura-security visualizer ${c('dim', '# Terminal 2')}
1406
+
1407
+ ${c('bold', 'CI/CD INTEGRATION:')}
1408
+ ${c('dim', 'See templates/ci/ for GitHub Actions and GitLab CI examples')}
1409
+
1410
+ ${c('bold', 'OUTPUT FORMATS:')}
1411
+ console Interactive terminal output (default)
1412
+ json Full JSON results
1413
+ sarif SARIF 2.1.0 for GitHub Code Scanning, VS Code
1414
+ junit JUnit XML for CI test reporters
1415
+ gitlab-sast GitLab SAST security report
1416
+ gitlab-deps GitLab Dependency Scanning report
1417
+ summary Compact JSON summary
1418
+
1419
+ ${c('bold', 'ENVIRONMENT:')}
1420
+ AURA_URL Server URL (default: http://127.0.0.1:3000)
1421
+
1422
+ ${c('bold', 'EXIT CODES:')}
1423
+ 0 Clean - no issues found
1424
+ 1 High severity issues found
1425
+ 2 Critical severity issues found
1426
+
1427
+ ${c('dim', 'For more info: https://github.com/aurasecurity/aura-security')}
1428
+ `);
1429
+ }
1430
+ main().catch((err) => {
1431
+ console.error(c('red', 'Error:'), err.message || err);
1432
+ process.exit(1);
1433
+ });