@grc-claw/soar 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,94 @@
1
+ export type PlaybookTrigger = 'policy_violation' | 'anomaly_detected' | 'threshold_breach' | 'agent_compromise' | 'credential_leak' | 'data_exfiltration' | 'unauthorized_access' | 'drift_detected' | 'manual';
2
+ export type StepAction = 'quarantine_agent' | 'revoke_did' | 'suspend_agent' | 'rollback_iac' | 'block_network' | 'generate_forensic_bundle' | 'notify_soc' | 'escalate_human' | 'snapshot_environment' | 'rotate_credentials' | 'update_firewall_rule' | 'log_evidence' | 'send_webhook' | 'custom_script';
3
+ export type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'awaiting_approval';
4
+ export type PlaybookStatus = 'idle' | 'running' | 'completed' | 'failed' | 'cancelled' | 'awaiting_approval';
5
+ export interface PlaybookStep {
6
+ id: string;
7
+ name: string;
8
+ action: StepAction;
9
+ params: Record<string, unknown>;
10
+ condition?: string;
11
+ requires_approval: boolean;
12
+ timeout_ms: number;
13
+ on_failure: 'continue' | 'abort' | 'escalate';
14
+ depends_on?: string[];
15
+ }
16
+ export interface Playbook {
17
+ id: string;
18
+ name: string;
19
+ description: string;
20
+ version: string;
21
+ trigger: PlaybookTrigger;
22
+ severity: 'low' | 'medium' | 'high' | 'critical';
23
+ steps: PlaybookStep[];
24
+ sla_seconds: number;
25
+ evidence_required: boolean;
26
+ tags: string[];
27
+ author: string;
28
+ created: string;
29
+ updated: string;
30
+ }
31
+ export interface StepResult {
32
+ stepId: string;
33
+ status: StepStatus;
34
+ startedAt: string;
35
+ completedAt?: string;
36
+ output: Record<string, unknown>;
37
+ error?: string;
38
+ approvedBy?: string;
39
+ durationMs: number;
40
+ }
41
+ export interface PlaybookExecution {
42
+ executionId: string;
43
+ playbookId: string;
44
+ status: PlaybookStatus;
45
+ trigger: PlaybookTrigger;
46
+ triggerContext: Record<string, unknown>;
47
+ stepResults: StepResult[];
48
+ startedAt: string;
49
+ completedAt?: string;
50
+ totalDurationMs: number;
51
+ slaBreached: boolean;
52
+ evidenceHashes: string[];
53
+ }
54
+ export interface IncidentReport {
55
+ incidentId: string;
56
+ executionId: string;
57
+ severity: string;
58
+ summary: string;
59
+ timeline: {
60
+ timestamp: string;
61
+ event: string;
62
+ details: string;
63
+ }[];
64
+ affectedAgents: string[];
65
+ remediationActions: string[];
66
+ evidenceBundle: string;
67
+ generatedAt: string;
68
+ }
69
+ export declare const BUILTIN_PLAYBOOKS: Playbook[];
70
+ export declare class SOAREngine {
71
+ private playbooks;
72
+ private executions;
73
+ constructor();
74
+ /** Register a custom playbook */
75
+ registerPlaybook(playbook: Playbook): void;
76
+ /** List all registered playbooks */
77
+ listPlaybooks(): Playbook[];
78
+ /** Get playbook by ID */
79
+ getPlaybook(id: string): Playbook | undefined;
80
+ /** Find playbooks matching a trigger */
81
+ findPlaybooksForTrigger(trigger: PlaybookTrigger): Playbook[];
82
+ /** Execute a playbook (simulated DAG execution) */
83
+ executePlaybook(playbookId: string, context: Record<string, unknown>): Promise<PlaybookExecution>;
84
+ /** Simulate a step execution */
85
+ private executeStep;
86
+ /** Simplified condition evaluation */
87
+ private evaluateCondition;
88
+ /** Generate an incident report from an execution */
89
+ generateIncidentReport(executionId: string): IncidentReport;
90
+ /** Get execution history */
91
+ getExecutionHistory(): PlaybookExecution[];
92
+ /** Get execution by ID */
93
+ getExecution(executionId: string): PlaybookExecution | undefined;
94
+ }
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ /**
2
+ * @grc-claw/soar
3
+ * Autonomous SOAR (Security Orchestration, Automation, and Response) Engine
4
+ *
5
+ * DAG-based playbook engine for autonomous incident response.
6
+ * Supports human-in-the-loop gates, conditional branching,
7
+ * SLA enforcement, and full evidence trail generation.
8
+ */
9
+ import * as crypto from 'crypto';
10
+ // ─── Built-in Playbooks ──────────────────────────────────────────────
11
+ export const BUILTIN_PLAYBOOKS = [
12
+ {
13
+ id: 'pb-agent-compromise',
14
+ name: 'Agent Compromise Response',
15
+ description: 'Automated response when an agent is detected as compromised. Quarantines the agent, revokes credentials, snapshots the environment, and generates a forensic bundle.',
16
+ version: '1.0.0',
17
+ trigger: 'agent_compromise',
18
+ severity: 'critical',
19
+ sla_seconds: 30,
20
+ evidence_required: true,
21
+ tags: ['security', 'incident-response', 'critical'],
22
+ author: 'grc-claw-core',
23
+ created: new Date().toISOString(),
24
+ updated: new Date().toISOString(),
25
+ steps: [
26
+ { id: 'step-1', name: 'Quarantine Agent', action: 'quarantine_agent', params: {}, condition: undefined, requires_approval: false, timeout_ms: 5000, on_failure: 'escalate' },
27
+ { id: 'step-2', name: 'Revoke Agent DID', action: 'revoke_did', params: {}, condition: undefined, requires_approval: false, timeout_ms: 3000, on_failure: 'escalate', depends_on: ['step-1'] },
28
+ { id: 'step-3', name: 'Snapshot Environment', action: 'snapshot_environment', params: { includeNetworkState: true }, condition: undefined, requires_approval: false, timeout_ms: 10000, on_failure: 'continue', depends_on: ['step-1'] },
29
+ { id: 'step-4', name: 'Block Network Access', action: 'block_network', params: { scope: 'agent-subnet' }, condition: undefined, requires_approval: false, timeout_ms: 5000, on_failure: 'continue', depends_on: ['step-1'] },
30
+ { id: 'step-5', name: 'Generate Forensic Bundle', action: 'generate_forensic_bundle', params: {}, condition: undefined, requires_approval: false, timeout_ms: 15000, on_failure: 'continue', depends_on: ['step-2', 'step-3'] },
31
+ { id: 'step-6', name: 'Notify SOC Team', action: 'notify_soc', params: { channel: 'critical-alerts' }, condition: undefined, requires_approval: false, timeout_ms: 3000, on_failure: 'continue', depends_on: ['step-5'] },
32
+ ],
33
+ },
34
+ {
35
+ id: 'pb-policy-violation',
36
+ name: 'Policy Violation Response',
37
+ description: 'Responds to agent policy violations. Suspends the agent, logs evidence, and escalates if severity is high.',
38
+ version: '1.0.0',
39
+ trigger: 'policy_violation',
40
+ severity: 'high',
41
+ sla_seconds: 60,
42
+ evidence_required: true,
43
+ tags: ['compliance', 'policy', 'governance'],
44
+ author: 'grc-claw-core',
45
+ created: new Date().toISOString(),
46
+ updated: new Date().toISOString(),
47
+ steps: [
48
+ { id: 'step-1', name: 'Suspend Agent', action: 'suspend_agent', params: {}, condition: undefined, requires_approval: false, timeout_ms: 3000, on_failure: 'escalate' },
49
+ { id: 'step-2', name: 'Log Evidence', action: 'log_evidence', params: { evidenceType: 'policy_violation' }, condition: undefined, requires_approval: false, timeout_ms: 5000, on_failure: 'continue', depends_on: ['step-1'] },
50
+ { id: 'step-3', name: 'Notify SOC', action: 'notify_soc', params: { channel: 'compliance-alerts' }, condition: undefined, requires_approval: false, timeout_ms: 3000, on_failure: 'continue', depends_on: ['step-2'] },
51
+ { id: 'step-4', name: 'Escalate to Human', action: 'escalate_human', params: { reason: 'Policy violation requires human review' }, condition: 'severity == "critical"', requires_approval: false, timeout_ms: 5000, on_failure: 'continue', depends_on: ['step-2'] },
52
+ ],
53
+ },
54
+ {
55
+ id: 'pb-drift-correction',
56
+ name: 'Infrastructure Drift Correction',
57
+ description: 'Detects and corrects infrastructure drift. Snapshots current state, generates IaC diff, and applies correction after approval.',
58
+ version: '1.0.0',
59
+ trigger: 'drift_detected',
60
+ severity: 'medium',
61
+ sla_seconds: 300,
62
+ evidence_required: true,
63
+ tags: ['infrastructure', 'compliance', 'iac'],
64
+ author: 'grc-claw-core',
65
+ created: new Date().toISOString(),
66
+ updated: new Date().toISOString(),
67
+ steps: [
68
+ { id: 'step-1', name: 'Snapshot Current State', action: 'snapshot_environment', params: { type: 'infrastructure' }, condition: undefined, requires_approval: false, timeout_ms: 10000, on_failure: 'abort' },
69
+ { id: 'step-2', name: 'Log Drift Evidence', action: 'log_evidence', params: { evidenceType: 'infrastructure_drift' }, condition: undefined, requires_approval: false, timeout_ms: 5000, on_failure: 'continue', depends_on: ['step-1'] },
70
+ { id: 'step-3', name: 'Apply IaC Correction', action: 'rollback_iac', params: { strategy: 'revert-to-baseline' }, condition: undefined, requires_approval: true, timeout_ms: 30000, on_failure: 'escalate', depends_on: ['step-2'] },
71
+ { id: 'step-4', name: 'Notify SOC', action: 'notify_soc', params: { channel: 'infrastructure-alerts' }, condition: undefined, requires_approval: false, timeout_ms: 3000, on_failure: 'continue', depends_on: ['step-3'] },
72
+ ],
73
+ },
74
+ {
75
+ id: 'pb-credential-rotation',
76
+ name: 'Emergency Credential Rotation',
77
+ description: 'Rotates compromised credentials, revokes existing sessions, and generates audit evidence.',
78
+ version: '1.0.0',
79
+ trigger: 'credential_leak',
80
+ severity: 'critical',
81
+ sla_seconds: 60,
82
+ evidence_required: true,
83
+ tags: ['security', 'identity', 'critical'],
84
+ author: 'grc-claw-core',
85
+ created: new Date().toISOString(),
86
+ updated: new Date().toISOString(),
87
+ steps: [
88
+ { id: 'step-1', name: 'Rotate Credentials', action: 'rotate_credentials', params: { scope: 'affected' }, condition: undefined, requires_approval: false, timeout_ms: 5000, on_failure: 'escalate' },
89
+ { id: 'step-2', name: 'Revoke Active Sessions', action: 'revoke_did', params: { scope: 'sessions' }, condition: undefined, requires_approval: false, timeout_ms: 3000, on_failure: 'continue', depends_on: ['step-1'] },
90
+ { id: 'step-3', name: 'Log Evidence', action: 'log_evidence', params: { evidenceType: 'credential_rotation' }, condition: undefined, requires_approval: false, timeout_ms: 5000, on_failure: 'continue', depends_on: ['step-2'] },
91
+ { id: 'step-4', name: 'Generate Forensic Bundle', action: 'generate_forensic_bundle', params: {}, condition: undefined, requires_approval: false, timeout_ms: 10000, on_failure: 'continue', depends_on: ['step-3'] },
92
+ { id: 'step-5', name: 'Notify SOC', action: 'notify_soc', params: { channel: 'critical-alerts' }, condition: undefined, requires_approval: false, timeout_ms: 3000, on_failure: 'continue', depends_on: ['step-4'] },
93
+ ],
94
+ },
95
+ ];
96
+ // ─── SOAR Engine ─────────────────────────────────────────────────────
97
+ export class SOAREngine {
98
+ playbooks = new Map();
99
+ executions = new Map();
100
+ constructor() {
101
+ // Register built-in playbooks
102
+ for (const pb of BUILTIN_PLAYBOOKS) {
103
+ this.playbooks.set(pb.id, pb);
104
+ }
105
+ }
106
+ /** Register a custom playbook */
107
+ registerPlaybook(playbook) {
108
+ this.playbooks.set(playbook.id, playbook);
109
+ }
110
+ /** List all registered playbooks */
111
+ listPlaybooks() {
112
+ return Array.from(this.playbooks.values());
113
+ }
114
+ /** Get playbook by ID */
115
+ getPlaybook(id) {
116
+ return this.playbooks.get(id);
117
+ }
118
+ /** Find playbooks matching a trigger */
119
+ findPlaybooksForTrigger(trigger) {
120
+ return Array.from(this.playbooks.values()).filter((p) => p.trigger === trigger);
121
+ }
122
+ /** Execute a playbook (simulated DAG execution) */
123
+ async executePlaybook(playbookId, context) {
124
+ const playbook = this.playbooks.get(playbookId);
125
+ if (!playbook)
126
+ throw new Error(`Playbook not found: ${playbookId}`);
127
+ const executionId = `exec_${crypto.randomUUID().substring(0, 12)}`;
128
+ const startedAt = new Date();
129
+ const execution = {
130
+ executionId,
131
+ playbookId,
132
+ status: 'running',
133
+ trigger: playbook.trigger,
134
+ triggerContext: context,
135
+ stepResults: [],
136
+ startedAt: startedAt.toISOString(),
137
+ totalDurationMs: 0,
138
+ slaBreached: false,
139
+ evidenceHashes: [],
140
+ };
141
+ this.executions.set(executionId, execution);
142
+ // Execute steps in dependency order
143
+ const completedSteps = new Set();
144
+ for (const step of playbook.steps) {
145
+ // Check dependencies
146
+ if (step.depends_on) {
147
+ const allDepsComplete = step.depends_on.every((dep) => completedSteps.has(dep));
148
+ if (!allDepsComplete) {
149
+ execution.stepResults.push({
150
+ stepId: step.id,
151
+ status: 'skipped',
152
+ startedAt: new Date().toISOString(),
153
+ output: { reason: 'dependency_not_met' },
154
+ durationMs: 0,
155
+ });
156
+ continue;
157
+ }
158
+ }
159
+ // Check condition (simplified evaluation)
160
+ if (step.condition) {
161
+ const conditionMet = this.evaluateCondition(step.condition, context);
162
+ if (!conditionMet) {
163
+ execution.stepResults.push({
164
+ stepId: step.id,
165
+ status: 'skipped',
166
+ startedAt: new Date().toISOString(),
167
+ output: { reason: 'condition_not_met', condition: step.condition },
168
+ durationMs: 0,
169
+ });
170
+ continue;
171
+ }
172
+ }
173
+ // Check if approval required
174
+ if (step.requires_approval) {
175
+ execution.stepResults.push({
176
+ stepId: step.id,
177
+ status: 'awaiting_approval',
178
+ startedAt: new Date().toISOString(),
179
+ output: { action: step.action, params: step.params, awaitingApproval: true },
180
+ durationMs: 0,
181
+ });
182
+ // In production, this would pause execution
183
+ // For simulation, we auto-approve
184
+ }
185
+ // Execute step (simulated)
186
+ const stepStart = Date.now();
187
+ const result = this.executeStep(step, context);
188
+ const stepDuration = Date.now() - stepStart;
189
+ const stepResult = {
190
+ stepId: step.id,
191
+ status: 'completed',
192
+ startedAt: new Date(stepStart).toISOString(),
193
+ completedAt: new Date().toISOString(),
194
+ output: result,
195
+ durationMs: stepDuration,
196
+ };
197
+ execution.stepResults.push(stepResult);
198
+ completedSteps.add(step.id);
199
+ // Generate evidence hash
200
+ if (playbook.evidence_required) {
201
+ const evidencePayload = JSON.stringify({ step: step.id, result, context });
202
+ const hash = crypto.createHash('sha256').update(evidencePayload).digest('hex');
203
+ execution.evidenceHashes.push(`sha256:${hash.substring(0, 16)}`);
204
+ }
205
+ }
206
+ const endTime = Date.now();
207
+ execution.totalDurationMs = endTime - startedAt.getTime();
208
+ execution.completedAt = new Date().toISOString();
209
+ execution.slaBreached = execution.totalDurationMs > playbook.sla_seconds * 1000;
210
+ const failedSteps = execution.stepResults.filter((s) => s.status === 'failed');
211
+ execution.status = failedSteps.length > 0 ? 'failed' : 'completed';
212
+ return execution;
213
+ }
214
+ /** Simulate a step execution */
215
+ executeStep(step, context) {
216
+ const agentDid = String(context.agentDid ?? 'unknown');
217
+ switch (step.action) {
218
+ case 'quarantine_agent':
219
+ return { quarantined: true, agentDid, isolatedAt: new Date().toISOString(), networkAccess: 'blocked', containerState: 'frozen' };
220
+ case 'revoke_did':
221
+ return { revoked: true, agentDid, revokedAt: new Date().toISOString() };
222
+ case 'suspend_agent':
223
+ return { suspended: true, agentDid, suspendedAt: new Date().toISOString() };
224
+ case 'rollback_iac':
225
+ return { rolledBack: true, strategy: step.params.strategy, baseline: 'restored', appliedAt: new Date().toISOString() };
226
+ case 'block_network':
227
+ return { blocked: true, scope: step.params.scope, firewallRuleId: `fw_${crypto.randomUUID().substring(0, 8)}` };
228
+ case 'generate_forensic_bundle':
229
+ return { bundleGenerated: true, bundleId: `forensic_${crypto.randomUUID().substring(0, 8)}`, artifactsCount: 12 };
230
+ case 'notify_soc':
231
+ return { notified: true, channel: step.params.channel, timestamp: new Date().toISOString() };
232
+ case 'escalate_human':
233
+ return { escalated: true, reason: step.params.reason, ticketId: `ESC-${Date.now()}` };
234
+ case 'snapshot_environment':
235
+ return { snapshotId: `snap_${crypto.randomUUID().substring(0, 8)}`, type: step.params.type ?? 'full' };
236
+ case 'rotate_credentials':
237
+ return { rotated: true, scope: step.params.scope, newCredentialId: `cred_${crypto.randomUUID().substring(0, 8)}` };
238
+ case 'update_firewall_rule':
239
+ return { updated: true, ruleId: step.params.ruleId };
240
+ case 'log_evidence':
241
+ return { logged: true, evidenceType: step.params.evidenceType };
242
+ case 'send_webhook':
243
+ return { sent: true, url: step.params.url };
244
+ case 'custom_script':
245
+ return { executed: true, script: step.params.script };
246
+ default:
247
+ return { executed: true, action: step.action };
248
+ }
249
+ }
250
+ /** Simplified condition evaluation */
251
+ evaluateCondition(condition, context) {
252
+ // Simple key == "value" evaluation
253
+ const match = condition.match(/(\w+)\s*==\s*"([^"]+)"/);
254
+ if (match) {
255
+ const [, key, value] = match;
256
+ return String(context[key ?? '']) === value;
257
+ }
258
+ return true; // Default to true for unrecognized conditions
259
+ }
260
+ /** Generate an incident report from an execution */
261
+ generateIncidentReport(executionId) {
262
+ const execution = this.executions.get(executionId);
263
+ if (!execution)
264
+ throw new Error(`Execution not found: ${executionId}`);
265
+ const playbook = this.playbooks.get(execution.playbookId);
266
+ const timeline = execution.stepResults.map((sr) => ({
267
+ timestamp: sr.startedAt,
268
+ event: sr.stepId,
269
+ details: `Status: ${sr.status}, Duration: ${sr.durationMs}ms`,
270
+ }));
271
+ const remediationActions = execution.stepResults
272
+ .filter((sr) => sr.status === 'completed')
273
+ .map((sr) => `${sr.stepId}: ${JSON.stringify(sr.output)}`);
274
+ const evidencePayload = JSON.stringify(execution);
275
+ const evidenceHash = crypto.createHash('sha256').update(evidencePayload).digest('hex');
276
+ return {
277
+ incidentId: `INC-${Date.now()}`,
278
+ executionId,
279
+ severity: playbook?.severity ?? 'unknown',
280
+ summary: `${playbook?.name ?? 'Unknown Playbook'} executed with ${execution.stepResults.length} steps. Status: ${execution.status}. SLA ${execution.slaBreached ? 'BREACHED' : 'met'}.`,
281
+ timeline,
282
+ affectedAgents: [String(execution.triggerContext.agentDid ?? 'unknown')],
283
+ remediationActions,
284
+ evidenceBundle: `sha256:${evidenceHash.substring(0, 32)}`,
285
+ generatedAt: new Date().toISOString(),
286
+ };
287
+ }
288
+ /** Get execution history */
289
+ getExecutionHistory() {
290
+ return Array.from(this.executions.values());
291
+ }
292
+ /** Get execution by ID */
293
+ getExecution(executionId) {
294
+ return this.executions.get(executionId);
295
+ }
296
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@grc-claw/soar",
3
+ "version": "2.0.0",
4
+ "description": "Autonomous SOAR playbook engine for agentic incident response",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "test": "node --test dist/**/*.test.js 2>/dev/null || true"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ }
25
+ }