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.
- package/LICENSE +21 -0
- package/README.md +446 -0
- package/deploy/AWS-DEPLOYMENT.md +358 -0
- package/deploy/terraform/main.tf +362 -0
- package/deploy/terraform/terraform.tfvars.example +6 -0
- package/dist/agents/base.d.ts +44 -0
- package/dist/agents/base.js +96 -0
- package/dist/agents/index.d.ts +14 -0
- package/dist/agents/index.js +17 -0
- package/dist/agents/policy/evaluator.d.ts +15 -0
- package/dist/agents/policy/evaluator.js +183 -0
- package/dist/agents/policy/index.d.ts +12 -0
- package/dist/agents/policy/index.js +15 -0
- package/dist/agents/policy/validator.d.ts +15 -0
- package/dist/agents/policy/validator.js +182 -0
- package/dist/agents/scanners/gitleaks.d.ts +14 -0
- package/dist/agents/scanners/gitleaks.js +155 -0
- package/dist/agents/scanners/grype.d.ts +14 -0
- package/dist/agents/scanners/grype.js +109 -0
- package/dist/agents/scanners/index.d.ts +15 -0
- package/dist/agents/scanners/index.js +27 -0
- package/dist/agents/scanners/npm-audit.d.ts +13 -0
- package/dist/agents/scanners/npm-audit.js +129 -0
- package/dist/agents/scanners/semgrep.d.ts +14 -0
- package/dist/agents/scanners/semgrep.js +131 -0
- package/dist/agents/scanners/trivy.d.ts +14 -0
- package/dist/agents/scanners/trivy.js +122 -0
- package/dist/agents/types.d.ts +137 -0
- package/dist/agents/types.js +91 -0
- package/dist/auditor/index.d.ts +3 -0
- package/dist/auditor/index.js +2 -0
- package/dist/auditor/pipeline.d.ts +19 -0
- package/dist/auditor/pipeline.js +240 -0
- package/dist/auditor/validator.d.ts +17 -0
- package/dist/auditor/validator.js +58 -0
- package/dist/aura/client.d.ts +29 -0
- package/dist/aura/client.js +125 -0
- package/dist/aura/index.d.ts +4 -0
- package/dist/aura/index.js +2 -0
- package/dist/aura/server.d.ts +45 -0
- package/dist/aura/server.js +343 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +1433 -0
- package/dist/client/index.d.ts +41 -0
- package/dist/client/index.js +170 -0
- package/dist/compliance/index.d.ts +40 -0
- package/dist/compliance/index.js +292 -0
- package/dist/database/index.d.ts +77 -0
- package/dist/database/index.js +395 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +762 -0
- package/dist/integrations/aura-scanner.d.ts +69 -0
- package/dist/integrations/aura-scanner.js +155 -0
- package/dist/integrations/aws-scanner.d.ts +63 -0
- package/dist/integrations/aws-scanner.js +624 -0
- package/dist/integrations/config.d.ts +69 -0
- package/dist/integrations/config.js +212 -0
- package/dist/integrations/github.d.ts +45 -0
- package/dist/integrations/github.js +201 -0
- package/dist/integrations/gitlab.d.ts +36 -0
- package/dist/integrations/gitlab.js +110 -0
- package/dist/integrations/index.d.ts +11 -0
- package/dist/integrations/index.js +11 -0
- package/dist/integrations/local-scanner.d.ts +146 -0
- package/dist/integrations/local-scanner.js +1654 -0
- package/dist/integrations/notifications.d.ts +99 -0
- package/dist/integrations/notifications.js +305 -0
- package/dist/integrations/scanners.d.ts +57 -0
- package/dist/integrations/scanners.js +217 -0
- package/dist/integrations/slop-scanner.d.ts +69 -0
- package/dist/integrations/slop-scanner.js +155 -0
- package/dist/integrations/webhook.d.ts +37 -0
- package/dist/integrations/webhook.js +256 -0
- package/dist/orchestrator/index.d.ts +72 -0
- package/dist/orchestrator/index.js +187 -0
- package/dist/output/index.d.ts +152 -0
- package/dist/output/index.js +399 -0
- package/dist/pipeline/index.d.ts +72 -0
- package/dist/pipeline/index.js +313 -0
- package/dist/sbom/index.d.ts +94 -0
- package/dist/sbom/index.js +298 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/input.schema.d.ts +87 -0
- package/dist/schemas/input.schema.js +44 -0
- package/dist/schemas/output.schema.d.ts +115 -0
- package/dist/schemas/output.schema.js +64 -0
- package/dist/serve-visualizer.d.ts +2 -0
- package/dist/serve-visualizer.js +78 -0
- package/dist/slop/client.d.ts +29 -0
- package/dist/slop/client.js +125 -0
- package/dist/slop/index.d.ts +4 -0
- package/dist/slop/index.js +2 -0
- package/dist/slop/server.d.ts +45 -0
- package/dist/slop/server.js +343 -0
- package/dist/types/events.d.ts +62 -0
- package/dist/types/events.js +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/visualizer/index.d.ts +4 -0
- package/dist/visualizer/index.js +181 -0
- package/dist/websocket/index.d.ts +88 -0
- package/dist/websocket/index.js +195 -0
- package/dist/zones/index.d.ts +7 -0
- package/dist/zones/index.js +7 -0
- package/dist/zones/manager.d.ts +101 -0
- package/dist/zones/manager.js +304 -0
- package/dist/zones/types.d.ts +78 -0
- package/dist/zones/types.js +33 -0
- package/package.json +84 -0
- package/visualizer/app.js +0 -0
- package/visualizer/index-minimal.html +1771 -0
- package/visualizer/index.html +2933 -0
- package/visualizer/landing.html +1328 -0
- package/visualizer/styles.css +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Schema Validator - Strict validation with fail-closed behavior
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import Ajv from 'ajv';
|
|
4
|
+
import addFormats from 'ajv-formats';
|
|
5
|
+
import { auditorInputSchema } from '../schemas/input.schema.js';
|
|
6
|
+
import { auditorOutputSchema } from '../schemas/output.schema.js';
|
|
7
|
+
export class ValidationError extends Error {
|
|
8
|
+
errors;
|
|
9
|
+
constructor(message, errors) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ValidationError';
|
|
12
|
+
this.errors = errors;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class SchemaValidator {
|
|
16
|
+
ajv;
|
|
17
|
+
validateInput;
|
|
18
|
+
validateOutput;
|
|
19
|
+
constructor() {
|
|
20
|
+
// Handle ESM/CJS interop
|
|
21
|
+
const AjvClass = Ajv.default ?? Ajv;
|
|
22
|
+
const addFormatsFunc = addFormats.default ?? addFormats;
|
|
23
|
+
this.ajv = new AjvClass({
|
|
24
|
+
strict: true,
|
|
25
|
+
allErrors: true,
|
|
26
|
+
verbose: true
|
|
27
|
+
});
|
|
28
|
+
addFormatsFunc(this.ajv);
|
|
29
|
+
this.validateInput = this.ajv.compile(auditorInputSchema);
|
|
30
|
+
this.validateOutput = this.ajv.compile(auditorOutputSchema);
|
|
31
|
+
}
|
|
32
|
+
// Fail-closed: throws on invalid input
|
|
33
|
+
assertValidInput(data) {
|
|
34
|
+
if (!this.validateInput(data)) {
|
|
35
|
+
throw new ValidationError('Input validation failed', this.validateInput.errors ?? []);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Fail-closed: throws on invalid output
|
|
39
|
+
assertValidOutput(data) {
|
|
40
|
+
if (!this.validateOutput(data)) {
|
|
41
|
+
throw new ValidationError('Output validation failed', this.validateOutput.errors ?? []);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
isValidInput(data) {
|
|
45
|
+
return this.validateInput(data);
|
|
46
|
+
}
|
|
47
|
+
isValidOutput(data) {
|
|
48
|
+
return this.validateOutput(data);
|
|
49
|
+
}
|
|
50
|
+
getInputErrors(data) {
|
|
51
|
+
this.validateInput(data);
|
|
52
|
+
return this.validateInput.errors ?? [];
|
|
53
|
+
}
|
|
54
|
+
getOutputErrors(data) {
|
|
55
|
+
this.validateOutput(data);
|
|
56
|
+
return this.validateOutput.errors ?? [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface AuraClientConfig {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface AuraToolCall {
|
|
7
|
+
tool: string;
|
|
8
|
+
arguments: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface AuraMemoryEntry {
|
|
11
|
+
key: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export declare class AuraConnectionError extends Error {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class AuraClient {
|
|
19
|
+
private config;
|
|
20
|
+
private _connected;
|
|
21
|
+
constructor(config: AuraClientConfig);
|
|
22
|
+
get connected(): boolean;
|
|
23
|
+
private headers;
|
|
24
|
+
connect(): Promise<void>;
|
|
25
|
+
disconnect(): Promise<void>;
|
|
26
|
+
publishToMemory(entry: AuraMemoryEntry): Promise<void>;
|
|
27
|
+
callTool(call: AuraToolCall): Promise<unknown>;
|
|
28
|
+
healthCheck(): Promise<boolean>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Aura Client - Publish-only client for auditor pipeline
|
|
2
|
+
// Implements: /tools, /memory endpoints per Aura spec
|
|
3
|
+
export class AuraConnectionError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'AuraConnectionError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class AuraClient {
|
|
10
|
+
config;
|
|
11
|
+
_connected = false;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = {
|
|
14
|
+
baseUrl: config.baseUrl.replace(/\/$/, ''),
|
|
15
|
+
apiKey: config.apiKey ?? '',
|
|
16
|
+
timeout: config.timeout ?? 30000
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
get connected() {
|
|
20
|
+
return this._connected;
|
|
21
|
+
}
|
|
22
|
+
headers() {
|
|
23
|
+
const h = {
|
|
24
|
+
'Content-Type': 'application/json'
|
|
25
|
+
};
|
|
26
|
+
if (this.config.apiKey) {
|
|
27
|
+
h['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
28
|
+
}
|
|
29
|
+
return h;
|
|
30
|
+
}
|
|
31
|
+
async connect() {
|
|
32
|
+
// Verify Aura server is available via /info
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(`${this.config.baseUrl}/info`, {
|
|
37
|
+
method: 'GET',
|
|
38
|
+
headers: this.headers(),
|
|
39
|
+
signal: controller.signal
|
|
40
|
+
});
|
|
41
|
+
clearTimeout(timeoutId);
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new AuraConnectionError(`Aura server returned ${res.status}`);
|
|
44
|
+
}
|
|
45
|
+
this._connected = true;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
clearTimeout(timeoutId);
|
|
49
|
+
if (err instanceof AuraConnectionError)
|
|
50
|
+
throw err;
|
|
51
|
+
throw new AuraConnectionError(`Failed to connect to Aura server: ${err}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async disconnect() {
|
|
55
|
+
this._connected = false;
|
|
56
|
+
}
|
|
57
|
+
// Fail-closed: throws on any error
|
|
58
|
+
async publishToMemory(entry) {
|
|
59
|
+
if (!this._connected) {
|
|
60
|
+
throw new AuraConnectionError('Not connected to Aura server');
|
|
61
|
+
}
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(`${this.config.baseUrl}/memory`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: this.headers(),
|
|
68
|
+
body: JSON.stringify(entry),
|
|
69
|
+
signal: controller.signal
|
|
70
|
+
});
|
|
71
|
+
clearTimeout(timeoutId);
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
throw new AuraConnectionError(`Memory publish failed: ${res.status}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
if (err instanceof AuraConnectionError)
|
|
79
|
+
throw err;
|
|
80
|
+
throw new AuraConnectionError(`Memory publish error: ${err}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async callTool(call) {
|
|
84
|
+
if (!this._connected) {
|
|
85
|
+
throw new AuraConnectionError('Not connected to Aura server');
|
|
86
|
+
}
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(`${this.config.baseUrl}/tools`, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: this.headers(),
|
|
93
|
+
body: JSON.stringify(call),
|
|
94
|
+
signal: controller.signal
|
|
95
|
+
});
|
|
96
|
+
clearTimeout(timeoutId);
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
throw new AuraConnectionError(`Tool call failed: ${res.status}`);
|
|
99
|
+
}
|
|
100
|
+
return await res.json();
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
clearTimeout(timeoutId);
|
|
104
|
+
if (err instanceof AuraConnectionError)
|
|
105
|
+
throw err;
|
|
106
|
+
throw new AuraConnectionError(`Tool call error: ${err}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async healthCheck() {
|
|
110
|
+
try {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
113
|
+
const res = await fetch(`${this.config.baseUrl}/info`, {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
headers: this.headers(),
|
|
116
|
+
signal: controller.signal
|
|
117
|
+
});
|
|
118
|
+
clearTimeout(timeoutId);
|
|
119
|
+
return res.ok;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type AuditorDatabase } from '../database/index.js';
|
|
2
|
+
import { NotificationService } from '../integrations/notifications.js';
|
|
3
|
+
export interface AuraTool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: Record<string, unknown>;
|
|
7
|
+
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface AuraServerConfig {
|
|
10
|
+
port: number;
|
|
11
|
+
host?: string;
|
|
12
|
+
dbPath?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class AuraServer {
|
|
15
|
+
private server;
|
|
16
|
+
private tools;
|
|
17
|
+
private memory;
|
|
18
|
+
private config;
|
|
19
|
+
private db;
|
|
20
|
+
private notificationService;
|
|
21
|
+
constructor(config: AuraServerConfig);
|
|
22
|
+
getNotificationService(): NotificationService;
|
|
23
|
+
reloadNotifications(): void;
|
|
24
|
+
registerTool(tool: AuraTool): void;
|
|
25
|
+
getDatabase(): AuditorDatabase;
|
|
26
|
+
private handleRequest;
|
|
27
|
+
private handleInfo;
|
|
28
|
+
private handleListTools;
|
|
29
|
+
private handleCallTool;
|
|
30
|
+
private handleMemoryWrite;
|
|
31
|
+
private handleMemoryRead;
|
|
32
|
+
private handleGetSettings;
|
|
33
|
+
private handleSaveSettings;
|
|
34
|
+
private handleGetAudits;
|
|
35
|
+
private handleGetAudit;
|
|
36
|
+
private handleDeleteAudit;
|
|
37
|
+
private handleGetStats;
|
|
38
|
+
private handleGetNotifications;
|
|
39
|
+
private handleTestNotification;
|
|
40
|
+
private handleSendNotification;
|
|
41
|
+
private readBody;
|
|
42
|
+
start(): Promise<void>;
|
|
43
|
+
stop(): Promise<void>;
|
|
44
|
+
getMemorySnapshot(): Map<string, unknown>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// Aura Server - Minimal implementation for auditor pipeline
|
|
2
|
+
// Exposes /tools, /memory, /info, /settings, /audits, /stats endpoints
|
|
3
|
+
import { createServer } from 'http';
|
|
4
|
+
import { getDatabase } from '../database/index.js';
|
|
5
|
+
import { NotificationService, createNotificationFromAudit } from '../integrations/notifications.js';
|
|
6
|
+
export class AuraServer {
|
|
7
|
+
server = null;
|
|
8
|
+
tools = new Map();
|
|
9
|
+
memory = new Map();
|
|
10
|
+
config;
|
|
11
|
+
db;
|
|
12
|
+
notificationService;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = {
|
|
15
|
+
port: config.port,
|
|
16
|
+
host: config.host ?? '127.0.0.1',
|
|
17
|
+
dbPath: config.dbPath ?? process.cwd()
|
|
18
|
+
};
|
|
19
|
+
// Initialize database
|
|
20
|
+
this.db = getDatabase(this.config.dbPath);
|
|
21
|
+
// Initialize notification service
|
|
22
|
+
this.notificationService = new NotificationService({}, this.config.dbPath);
|
|
23
|
+
this.notificationService.loadFromDatabase();
|
|
24
|
+
}
|
|
25
|
+
getNotificationService() {
|
|
26
|
+
return this.notificationService;
|
|
27
|
+
}
|
|
28
|
+
reloadNotifications() {
|
|
29
|
+
this.notificationService.loadFromDatabase();
|
|
30
|
+
}
|
|
31
|
+
registerTool(tool) {
|
|
32
|
+
this.tools.set(tool.name, tool);
|
|
33
|
+
}
|
|
34
|
+
getDatabase() {
|
|
35
|
+
return this.db;
|
|
36
|
+
}
|
|
37
|
+
async handleRequest(req, res) {
|
|
38
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
|
|
39
|
+
const path = url.pathname;
|
|
40
|
+
// CORS headers for visualizer access
|
|
41
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
42
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
43
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
44
|
+
res.setHeader('Content-Type', 'application/json');
|
|
45
|
+
// Handle preflight
|
|
46
|
+
if (req.method === 'OPTIONS') {
|
|
47
|
+
res.statusCode = 204;
|
|
48
|
+
res.end();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
// Core Aura endpoints
|
|
53
|
+
if (path === '/info' && req.method === 'GET') {
|
|
54
|
+
await this.handleInfo(res);
|
|
55
|
+
}
|
|
56
|
+
else if (path === '/tools' && req.method === 'GET') {
|
|
57
|
+
await this.handleListTools(res);
|
|
58
|
+
}
|
|
59
|
+
else if (path === '/tools' && req.method === 'POST') {
|
|
60
|
+
await this.handleCallTool(req, res);
|
|
61
|
+
}
|
|
62
|
+
else if (path === '/memory' && req.method === 'POST') {
|
|
63
|
+
await this.handleMemoryWrite(req, res);
|
|
64
|
+
}
|
|
65
|
+
else if (path === '/memory' && req.method === 'GET') {
|
|
66
|
+
await this.handleMemoryRead(url, res);
|
|
67
|
+
}
|
|
68
|
+
// Settings endpoints
|
|
69
|
+
else if (path === '/settings' && req.method === 'GET') {
|
|
70
|
+
await this.handleGetSettings(url, res);
|
|
71
|
+
}
|
|
72
|
+
else if (path === '/settings' && req.method === 'POST') {
|
|
73
|
+
await this.handleSaveSettings(req, res);
|
|
74
|
+
}
|
|
75
|
+
// Audit history endpoints
|
|
76
|
+
else if (path === '/audits' && req.method === 'GET') {
|
|
77
|
+
await this.handleGetAudits(url, res);
|
|
78
|
+
}
|
|
79
|
+
else if (path.startsWith('/audits/') && req.method === 'GET') {
|
|
80
|
+
const id = path.slice(8);
|
|
81
|
+
await this.handleGetAudit(id, res);
|
|
82
|
+
}
|
|
83
|
+
else if (path.startsWith('/audits/') && req.method === 'DELETE') {
|
|
84
|
+
const id = path.slice(8);
|
|
85
|
+
await this.handleDeleteAudit(id, res);
|
|
86
|
+
}
|
|
87
|
+
// Stats endpoint
|
|
88
|
+
else if (path === '/stats' && req.method === 'GET') {
|
|
89
|
+
await this.handleGetStats(res);
|
|
90
|
+
}
|
|
91
|
+
// Notifications endpoints
|
|
92
|
+
else if (path === '/notifications' && req.method === 'GET') {
|
|
93
|
+
await this.handleGetNotifications(url, res);
|
|
94
|
+
}
|
|
95
|
+
else if (path === '/notifications/test' && req.method === 'POST') {
|
|
96
|
+
await this.handleTestNotification(req, res);
|
|
97
|
+
}
|
|
98
|
+
else if (path === '/notifications/send' && req.method === 'POST') {
|
|
99
|
+
await this.handleSendNotification(req, res);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
res.statusCode = 404;
|
|
103
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error('[SERVER] Error:', err);
|
|
108
|
+
// Fail-closed: return 500 on any error
|
|
109
|
+
res.statusCode = 500;
|
|
110
|
+
res.end(JSON.stringify({
|
|
111
|
+
error: 'Internal server error',
|
|
112
|
+
message: err instanceof Error ? err.message : 'Unknown error',
|
|
113
|
+
blocked: true
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async handleInfo(res) {
|
|
118
|
+
res.statusCode = 200;
|
|
119
|
+
res.end(JSON.stringify({
|
|
120
|
+
name: 'aura-security',
|
|
121
|
+
version: '0.2.0',
|
|
122
|
+
endpoints: ['/info', '/tools', '/memory', '/settings', '/audits', '/stats', '/notifications'],
|
|
123
|
+
tools: Array.from(this.tools.keys()),
|
|
124
|
+
database: true
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
async handleListTools(res) {
|
|
128
|
+
const toolList = Array.from(this.tools.values()).map(t => ({
|
|
129
|
+
name: t.name,
|
|
130
|
+
description: t.description,
|
|
131
|
+
parameters: t.parameters
|
|
132
|
+
}));
|
|
133
|
+
res.statusCode = 200;
|
|
134
|
+
res.end(JSON.stringify({ tools: toolList }));
|
|
135
|
+
}
|
|
136
|
+
async handleCallTool(req, res) {
|
|
137
|
+
const body = await this.readBody(req);
|
|
138
|
+
const { tool, arguments: args } = JSON.parse(body);
|
|
139
|
+
const toolDef = this.tools.get(tool);
|
|
140
|
+
if (!toolDef) {
|
|
141
|
+
res.statusCode = 404;
|
|
142
|
+
res.end(JSON.stringify({ error: `Tool not found: ${tool}` }));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const result = await toolDef.handler(args ?? {});
|
|
146
|
+
res.statusCode = 200;
|
|
147
|
+
res.end(JSON.stringify({ result }));
|
|
148
|
+
}
|
|
149
|
+
async handleMemoryWrite(req, res) {
|
|
150
|
+
const body = await this.readBody(req);
|
|
151
|
+
const { key, value, metadata } = JSON.parse(body);
|
|
152
|
+
this.memory.set(key, { value, metadata, timestamp: new Date().toISOString() });
|
|
153
|
+
res.statusCode = 201;
|
|
154
|
+
res.end(JSON.stringify({ status: 'stored', key }));
|
|
155
|
+
}
|
|
156
|
+
async handleMemoryRead(url, res) {
|
|
157
|
+
const key = url.searchParams.get('key');
|
|
158
|
+
if (key) {
|
|
159
|
+
const entry = this.memory.get(key);
|
|
160
|
+
if (entry) {
|
|
161
|
+
res.statusCode = 200;
|
|
162
|
+
res.end(JSON.stringify(entry));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
res.statusCode = 404;
|
|
166
|
+
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
res.statusCode = 200;
|
|
171
|
+
res.end(JSON.stringify({ keys: Array.from(this.memory.keys()) }));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ============ SETTINGS ENDPOINTS ============
|
|
175
|
+
async handleGetSettings(url, res) {
|
|
176
|
+
const prefix = url.searchParams.get('prefix');
|
|
177
|
+
let settings;
|
|
178
|
+
if (prefix) {
|
|
179
|
+
settings = this.db.getSettings(prefix);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
settings = this.db.getAllSettings();
|
|
183
|
+
}
|
|
184
|
+
res.statusCode = 200;
|
|
185
|
+
res.end(JSON.stringify({ settings }));
|
|
186
|
+
}
|
|
187
|
+
async handleSaveSettings(req, res) {
|
|
188
|
+
const body = await this.readBody(req);
|
|
189
|
+
const { settings } = JSON.parse(body);
|
|
190
|
+
if (!settings || typeof settings !== 'object') {
|
|
191
|
+
res.statusCode = 400;
|
|
192
|
+
res.end(JSON.stringify({ error: 'Invalid settings object' }));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.db.setSettings(settings);
|
|
196
|
+
res.statusCode = 200;
|
|
197
|
+
res.end(JSON.stringify({ status: 'saved', count: Object.keys(settings).length }));
|
|
198
|
+
}
|
|
199
|
+
// ============ AUDIT HISTORY ENDPOINTS ============
|
|
200
|
+
async handleGetAudits(url, res) {
|
|
201
|
+
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
202
|
+
const offset = parseInt(url.searchParams.get('offset') || '0', 10);
|
|
203
|
+
const type = url.searchParams.get('type') || undefined;
|
|
204
|
+
const audits = this.db.getAudits(limit, offset, type);
|
|
205
|
+
const total = this.db.getAuditCount(type);
|
|
206
|
+
// Return without full data for list view (lighter response)
|
|
207
|
+
const auditList = audits.map(a => ({
|
|
208
|
+
id: a.id,
|
|
209
|
+
type: a.type,
|
|
210
|
+
timestamp: a.timestamp,
|
|
211
|
+
target: a.target,
|
|
212
|
+
summary: a.summary
|
|
213
|
+
}));
|
|
214
|
+
res.statusCode = 200;
|
|
215
|
+
res.end(JSON.stringify({ audits: auditList, total, limit, offset }));
|
|
216
|
+
}
|
|
217
|
+
async handleGetAudit(id, res) {
|
|
218
|
+
const audit = this.db.getAudit(id);
|
|
219
|
+
if (!audit) {
|
|
220
|
+
res.statusCode = 404;
|
|
221
|
+
res.end(JSON.stringify({ error: 'Audit not found' }));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Parse the stored JSON data
|
|
225
|
+
let data;
|
|
226
|
+
try {
|
|
227
|
+
data = JSON.parse(audit.data);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
data = audit.data;
|
|
231
|
+
}
|
|
232
|
+
res.statusCode = 200;
|
|
233
|
+
res.end(JSON.stringify({
|
|
234
|
+
id: audit.id,
|
|
235
|
+
type: audit.type,
|
|
236
|
+
timestamp: audit.timestamp,
|
|
237
|
+
target: audit.target,
|
|
238
|
+
summary: audit.summary,
|
|
239
|
+
data
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
async handleDeleteAudit(id, res) {
|
|
243
|
+
const deleted = this.db.deleteAudit(id);
|
|
244
|
+
if (!deleted) {
|
|
245
|
+
res.statusCode = 404;
|
|
246
|
+
res.end(JSON.stringify({ error: 'Audit not found' }));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
res.statusCode = 200;
|
|
250
|
+
res.end(JSON.stringify({ status: 'deleted', id }));
|
|
251
|
+
}
|
|
252
|
+
// ============ STATS ENDPOINT ============
|
|
253
|
+
async handleGetStats(res) {
|
|
254
|
+
const stats = this.db.getStats();
|
|
255
|
+
res.statusCode = 200;
|
|
256
|
+
res.end(JSON.stringify(stats));
|
|
257
|
+
}
|
|
258
|
+
// ============ NOTIFICATIONS ENDPOINT ============
|
|
259
|
+
async handleGetNotifications(url, res) {
|
|
260
|
+
const auditId = url.searchParams.get('audit_id') || undefined;
|
|
261
|
+
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
262
|
+
const notifications = this.db.getNotifications(auditId, limit);
|
|
263
|
+
res.statusCode = 200;
|
|
264
|
+
res.end(JSON.stringify({ notifications }));
|
|
265
|
+
}
|
|
266
|
+
async handleTestNotification(req, res) {
|
|
267
|
+
const body = await this.readBody(req);
|
|
268
|
+
const { channel } = JSON.parse(body);
|
|
269
|
+
if (!channel || !['slack', 'discord', 'webhook'].includes(channel)) {
|
|
270
|
+
res.statusCode = 400;
|
|
271
|
+
res.end(JSON.stringify({ error: 'Invalid channel. Must be: slack, discord, or webhook' }));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
// Reload settings before testing
|
|
275
|
+
this.notificationService.loadFromDatabase();
|
|
276
|
+
const result = await this.notificationService.testChannel(channel);
|
|
277
|
+
res.statusCode = result.success ? 200 : 400;
|
|
278
|
+
res.end(JSON.stringify(result));
|
|
279
|
+
}
|
|
280
|
+
async handleSendNotification(req, res) {
|
|
281
|
+
const body = await this.readBody(req);
|
|
282
|
+
const { auditId, title, message, severity } = JSON.parse(body);
|
|
283
|
+
// If auditId provided, create notification from audit data
|
|
284
|
+
let payload;
|
|
285
|
+
if (auditId) {
|
|
286
|
+
const audit = this.db.getAudit(auditId);
|
|
287
|
+
if (!audit) {
|
|
288
|
+
res.statusCode = 404;
|
|
289
|
+
res.end(JSON.stringify({ error: 'Audit not found' }));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
payload = createNotificationFromAudit(audit.id, audit.type, audit.target, audit.summary);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// Manual notification
|
|
296
|
+
payload = {
|
|
297
|
+
title: title || 'Manual Notification',
|
|
298
|
+
message: message || 'Test notification from Aura Auditor',
|
|
299
|
+
severity: severity || 'low'
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// Reload settings and send
|
|
303
|
+
this.notificationService.loadFromDatabase();
|
|
304
|
+
const result = await this.notificationService.notify(payload);
|
|
305
|
+
res.statusCode = 200;
|
|
306
|
+
res.end(JSON.stringify(result));
|
|
307
|
+
}
|
|
308
|
+
readBody(req) {
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
const chunks = [];
|
|
311
|
+
req.on('data', chunk => chunks.push(chunk));
|
|
312
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
313
|
+
req.on('error', reject);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
async start() {
|
|
317
|
+
return new Promise((resolve, reject) => {
|
|
318
|
+
this.server = createServer((req, res) => {
|
|
319
|
+
this.handleRequest(req, res).catch(() => {
|
|
320
|
+
res.statusCode = 500;
|
|
321
|
+
res.end(JSON.stringify({ error: 'Internal error', blocked: true }));
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
this.server.on('error', reject);
|
|
325
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
326
|
+
resolve();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async stop() {
|
|
331
|
+
return new Promise((resolve) => {
|
|
332
|
+
if (this.server) {
|
|
333
|
+
this.server.close(() => resolve());
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
resolve();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
getMemorySnapshot() {
|
|
341
|
+
return new Map(this.memory);
|
|
342
|
+
}
|
|
343
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
export {};
|