agentspd 1.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.
- package/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/api.d.ts +198 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +171 -0
- package/dist/commands/agents.d.ts +3 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +277 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +181 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +226 -0
- package/dist/commands/config-cmd.d.ts +3 -0
- package/dist/commands/config-cmd.d.ts.map +1 -0
- package/dist/commands/config-cmd.js +111 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +8 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +275 -0
- package/dist/commands/policies.d.ts +3 -0
- package/dist/commands/policies.d.ts.map +1 -0
- package/dist/commands/policies.js +362 -0
- package/dist/commands/threats.d.ts +3 -0
- package/dist/commands/threats.d.ts.map +1 -0
- package/dist/commands/threats.js +161 -0
- package/dist/commands/webhooks.d.ts +3 -0
- package/dist/commands/webhooks.d.ts.map +1 -0
- package/dist/commands/webhooks.js +222 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +58 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +328 -0
- package/dist/output.d.ts +60 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +212 -0
- package/package.json +58 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import * as output from '../output.js';
|
|
7
|
+
const DEFAULT_POLICY_TEMPLATE = `# Emotos Security Policy
|
|
8
|
+
# Version 1.0
|
|
9
|
+
|
|
10
|
+
version: "1.0"
|
|
11
|
+
name: "my-policy"
|
|
12
|
+
description: "My security policy"
|
|
13
|
+
|
|
14
|
+
settings:
|
|
15
|
+
default_action: deny
|
|
16
|
+
require_identity: true
|
|
17
|
+
|
|
18
|
+
# Tool permissions
|
|
19
|
+
tools:
|
|
20
|
+
# Allow read operations
|
|
21
|
+
- pattern: "read_*"
|
|
22
|
+
action: allow
|
|
23
|
+
|
|
24
|
+
# Block dangerous operations
|
|
25
|
+
- pattern: "exec_*"
|
|
26
|
+
action: deny
|
|
27
|
+
reason: "Shell execution not permitted"
|
|
28
|
+
|
|
29
|
+
# Allow with rate limiting
|
|
30
|
+
- pattern: "web_*"
|
|
31
|
+
action: allow
|
|
32
|
+
rate_limit:
|
|
33
|
+
requests_per_minute: 10
|
|
34
|
+
|
|
35
|
+
# Prompt injection protection
|
|
36
|
+
prompt_injection:
|
|
37
|
+
enabled: true
|
|
38
|
+
action: block
|
|
39
|
+
|
|
40
|
+
# Data exfiltration prevention
|
|
41
|
+
exfiltration:
|
|
42
|
+
enabled: true
|
|
43
|
+
block_patterns:
|
|
44
|
+
- name: "aws_keys"
|
|
45
|
+
pattern: "AKIA[0-9A-Z]{16}"
|
|
46
|
+
- name: "private_keys"
|
|
47
|
+
pattern: "-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----"
|
|
48
|
+
`;
|
|
49
|
+
export function createPoliciesCommand() {
|
|
50
|
+
const policies = new Command('policies')
|
|
51
|
+
.alias('policy')
|
|
52
|
+
.description('Manage security policies');
|
|
53
|
+
policies
|
|
54
|
+
.command('create')
|
|
55
|
+
.description('Create a new security policy')
|
|
56
|
+
.option('-n, --name <name>', 'Policy name')
|
|
57
|
+
.option('-d, --description <desc>', 'Policy description')
|
|
58
|
+
.option('-f, --file <path>', 'Read policy content from file')
|
|
59
|
+
.option('-t, --template', 'Start with a template')
|
|
60
|
+
.option('--json', 'Output as JSON')
|
|
61
|
+
.action(async (options) => {
|
|
62
|
+
let name = options.name;
|
|
63
|
+
let description = options.description;
|
|
64
|
+
let content;
|
|
65
|
+
// Get policy content
|
|
66
|
+
if (options.file) {
|
|
67
|
+
if (!fs.existsSync(options.file)) {
|
|
68
|
+
output.error(`File not found: ${options.file}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
content = fs.readFileSync(options.file, 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
else if (options.template) {
|
|
74
|
+
content = DEFAULT_POLICY_TEMPLATE;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
output.info('Enter policy YAML (end with Ctrl+D on a new line):');
|
|
78
|
+
console.log();
|
|
79
|
+
const answers = await inquirer.prompt([
|
|
80
|
+
{
|
|
81
|
+
type: 'editor',
|
|
82
|
+
name: 'content',
|
|
83
|
+
message: 'Policy content:',
|
|
84
|
+
default: DEFAULT_POLICY_TEMPLATE,
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
content = answers.content;
|
|
88
|
+
}
|
|
89
|
+
// Get name and description if not provided
|
|
90
|
+
if (!name) {
|
|
91
|
+
const answers = await inquirer.prompt([
|
|
92
|
+
{
|
|
93
|
+
type: 'input',
|
|
94
|
+
name: 'name',
|
|
95
|
+
message: 'Policy name:',
|
|
96
|
+
validate: (input) => input.length > 0 || 'Name is required',
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
name = answers.name;
|
|
100
|
+
}
|
|
101
|
+
if (!description) {
|
|
102
|
+
const answers = await inquirer.prompt([
|
|
103
|
+
{
|
|
104
|
+
type: 'input',
|
|
105
|
+
name: 'description',
|
|
106
|
+
message: 'Description (optional):',
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
description = answers.description || undefined;
|
|
110
|
+
}
|
|
111
|
+
// Validate first
|
|
112
|
+
const validateSpinner = ora('Validating policy...').start();
|
|
113
|
+
const validateResult = await api.validatePolicy(content);
|
|
114
|
+
if (validateResult.error || (validateResult.data && !validateResult.data.valid)) {
|
|
115
|
+
validateSpinner.fail('Policy validation failed');
|
|
116
|
+
if (validateResult.data?.errors) {
|
|
117
|
+
for (const err of validateResult.data.errors) {
|
|
118
|
+
output.error(err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (validateResult.error) {
|
|
122
|
+
output.error(validateResult.error.message);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
validateSpinner.succeed('Policy validated');
|
|
127
|
+
// Create policy
|
|
128
|
+
const spinner = ora('Creating policy...').start();
|
|
129
|
+
const result = await api.createPolicy({ name, description, content });
|
|
130
|
+
if (result.error) {
|
|
131
|
+
spinner.fail('Failed to create policy');
|
|
132
|
+
output.error(result.error.message);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
spinner.succeed('Policy created successfully!');
|
|
136
|
+
if (options.json) {
|
|
137
|
+
output.printJson(result.data);
|
|
138
|
+
}
|
|
139
|
+
else if (result.data) {
|
|
140
|
+
console.log();
|
|
141
|
+
output.printKeyValue([
|
|
142
|
+
['Policy ID', result.data.id],
|
|
143
|
+
['Name', result.data.name],
|
|
144
|
+
['Version', result.data.version],
|
|
145
|
+
['Active', result.data.isActive ? 'Yes' : 'No'],
|
|
146
|
+
['Created', output.formatDate(result.data.createdAt)],
|
|
147
|
+
]);
|
|
148
|
+
console.log();
|
|
149
|
+
output.info('Next steps:');
|
|
150
|
+
console.log(` 1. Activate policy: ${output.highlight(`emotos policies activate ${result.data.id}`)}`);
|
|
151
|
+
console.log(` 2. Assign to agent: ${output.highlight(`emotos agents create --policy ${result.data.id}`)}`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
policies
|
|
155
|
+
.command('list')
|
|
156
|
+
.alias('ls')
|
|
157
|
+
.description('List all policies')
|
|
158
|
+
.option('--json', 'Output as JSON')
|
|
159
|
+
.action(async (options) => {
|
|
160
|
+
const spinner = ora('Fetching policies...').start();
|
|
161
|
+
const result = await api.listPolicies();
|
|
162
|
+
if (result.error) {
|
|
163
|
+
spinner.fail('Failed to fetch policies');
|
|
164
|
+
output.error(result.error.message);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
spinner.stop();
|
|
168
|
+
if (options.json) {
|
|
169
|
+
output.printJson(result.data);
|
|
170
|
+
}
|
|
171
|
+
else if (result.data) {
|
|
172
|
+
if (result.data.policies.length === 0) {
|
|
173
|
+
output.info('No policies found');
|
|
174
|
+
output.info('Create one with: emotos policies create');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
output.heading('Security Policies');
|
|
178
|
+
output.printPolicyTable(result.data.policies);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
policies
|
|
182
|
+
.command('get <policyId>')
|
|
183
|
+
.alias('show')
|
|
184
|
+
.description('Get policy details')
|
|
185
|
+
.option('--json', 'Output as JSON')
|
|
186
|
+
.option('--content', 'Show policy content only')
|
|
187
|
+
.action(async (policyId, options) => {
|
|
188
|
+
const spinner = ora('Fetching policy...').start();
|
|
189
|
+
const result = await api.getPolicy(policyId);
|
|
190
|
+
if (result.error) {
|
|
191
|
+
spinner.fail('Failed to fetch policy');
|
|
192
|
+
output.error(result.error.message);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
spinner.stop();
|
|
196
|
+
if (options.content && result.data) {
|
|
197
|
+
console.log(result.data.content);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (options.json) {
|
|
201
|
+
output.printJson(result.data);
|
|
202
|
+
}
|
|
203
|
+
else if (result.data) {
|
|
204
|
+
output.heading(`Policy: ${result.data.name}`);
|
|
205
|
+
output.printKeyValue([
|
|
206
|
+
['ID', result.data.id],
|
|
207
|
+
['Name', result.data.name],
|
|
208
|
+
['Description', result.data.description],
|
|
209
|
+
['Version', result.data.version],
|
|
210
|
+
['Active', result.data.isActive ? 'Yes' : 'No'],
|
|
211
|
+
['Created', output.formatDate(result.data.createdAt)],
|
|
212
|
+
['Updated', result.data.updatedAt ? output.formatDate(result.data.updatedAt) : undefined],
|
|
213
|
+
]);
|
|
214
|
+
console.log();
|
|
215
|
+
output.heading('Content');
|
|
216
|
+
console.log(result.data.content);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
policies
|
|
220
|
+
.command('update <policyId>')
|
|
221
|
+
.description('Update a policy')
|
|
222
|
+
.option('-f, --file <path>', 'Read policy content from file')
|
|
223
|
+
.option('--json', 'Output as JSON')
|
|
224
|
+
.action(async (policyId, options) => {
|
|
225
|
+
let content;
|
|
226
|
+
if (options.file) {
|
|
227
|
+
if (!fs.existsSync(options.file)) {
|
|
228
|
+
output.error(`File not found: ${options.file}`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
content = fs.readFileSync(options.file, 'utf-8');
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Fetch current content
|
|
235
|
+
const current = await api.getPolicy(policyId);
|
|
236
|
+
if (current.error) {
|
|
237
|
+
output.error(current.error.message);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const answers = await inquirer.prompt([
|
|
241
|
+
{
|
|
242
|
+
type: 'editor',
|
|
243
|
+
name: 'content',
|
|
244
|
+
message: 'Policy content:',
|
|
245
|
+
default: current.data?.content,
|
|
246
|
+
},
|
|
247
|
+
]);
|
|
248
|
+
content = answers.content;
|
|
249
|
+
}
|
|
250
|
+
// Validate first
|
|
251
|
+
const validateSpinner = ora('Validating policy...').start();
|
|
252
|
+
const validateResult = await api.validatePolicy(content);
|
|
253
|
+
if (validateResult.error || (validateResult.data && !validateResult.data.valid)) {
|
|
254
|
+
validateSpinner.fail('Policy validation failed');
|
|
255
|
+
if (validateResult.data?.errors) {
|
|
256
|
+
for (const err of validateResult.data.errors) {
|
|
257
|
+
output.error(err);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
validateSpinner.succeed('Policy validated');
|
|
263
|
+
// Update policy
|
|
264
|
+
const spinner = ora('Updating policy...').start();
|
|
265
|
+
const result = await api.updatePolicy(policyId, content);
|
|
266
|
+
if (result.error) {
|
|
267
|
+
spinner.fail('Failed to update policy');
|
|
268
|
+
output.error(result.error.message);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
spinner.succeed('Policy updated successfully!');
|
|
272
|
+
if (options.json) {
|
|
273
|
+
output.printJson(result.data);
|
|
274
|
+
}
|
|
275
|
+
else if (result.data) {
|
|
276
|
+
output.printKeyValue([
|
|
277
|
+
['Version', result.data.version],
|
|
278
|
+
['Updated', result.data.updatedAt ? output.formatDate(result.data.updatedAt) : 'Now'],
|
|
279
|
+
]);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
policies
|
|
283
|
+
.command('validate')
|
|
284
|
+
.description('Validate a policy file')
|
|
285
|
+
.argument('<file>', 'Policy file to validate')
|
|
286
|
+
.action(async (file) => {
|
|
287
|
+
if (!fs.existsSync(file)) {
|
|
288
|
+
output.error(`File not found: ${file}`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
292
|
+
const spinner = ora('Validating policy...').start();
|
|
293
|
+
const result = await api.validatePolicy(content);
|
|
294
|
+
if (result.error) {
|
|
295
|
+
spinner.fail('Validation failed');
|
|
296
|
+
output.error(result.error.message);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (result.data?.valid) {
|
|
300
|
+
spinner.succeed('Policy is valid!');
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
spinner.fail('Policy is invalid');
|
|
304
|
+
if (result.data?.errors) {
|
|
305
|
+
for (const err of result.data.errors) {
|
|
306
|
+
output.error(err);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
policies
|
|
312
|
+
.command('activate <policyId>')
|
|
313
|
+
.description('Activate a policy')
|
|
314
|
+
.action(async (policyId) => {
|
|
315
|
+
const spinner = ora('Activating policy...').start();
|
|
316
|
+
const result = await api.activatePolicy(policyId);
|
|
317
|
+
if (result.error) {
|
|
318
|
+
spinner.fail('Failed to activate policy');
|
|
319
|
+
output.error(result.error.message);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
spinner.succeed('Policy activated!');
|
|
323
|
+
});
|
|
324
|
+
policies
|
|
325
|
+
.command('deactivate <policyId>')
|
|
326
|
+
.description('Deactivate a policy')
|
|
327
|
+
.action(async (policyId) => {
|
|
328
|
+
const spinner = ora('Deactivating policy...').start();
|
|
329
|
+
const result = await api.deactivatePolicy(policyId);
|
|
330
|
+
if (result.error) {
|
|
331
|
+
spinner.fail('Failed to deactivate policy');
|
|
332
|
+
output.error(result.error.message);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
spinner.succeed('Policy deactivated');
|
|
336
|
+
});
|
|
337
|
+
policies
|
|
338
|
+
.command('init')
|
|
339
|
+
.description('Initialize a new policy file from template')
|
|
340
|
+
.option('-o, --output <path>', 'Output file path', 'emotos-policy.yaml')
|
|
341
|
+
.action(async (options) => {
|
|
342
|
+
if (fs.existsSync(options.output)) {
|
|
343
|
+
const answers = await inquirer.prompt([
|
|
344
|
+
{
|
|
345
|
+
type: 'confirm',
|
|
346
|
+
name: 'overwrite',
|
|
347
|
+
message: `File ${options.output} already exists. Overwrite?`,
|
|
348
|
+
default: false,
|
|
349
|
+
},
|
|
350
|
+
]);
|
|
351
|
+
if (!answers.overwrite) {
|
|
352
|
+
output.info('Operation cancelled');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
fs.writeFileSync(options.output, DEFAULT_POLICY_TEMPLATE);
|
|
357
|
+
output.success(`Policy template created: ${options.output}`);
|
|
358
|
+
output.info('Edit this file to customize your security policy');
|
|
359
|
+
output.info(`Then upload with: emotos policies create --file ${options.output}`);
|
|
360
|
+
});
|
|
361
|
+
return policies;
|
|
362
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threats.d.ts","sourceRoot":"","sources":["../../src/commands/threats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,oBAAoB,IAAI,OAAO,CAoL9C"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
import * as output from '../output.js';
|
|
6
|
+
export function createThreatsCommand() {
|
|
7
|
+
const threats = new Command('threats')
|
|
8
|
+
.alias('threat')
|
|
9
|
+
.description('Monitor and respond to security threats');
|
|
10
|
+
threats
|
|
11
|
+
.command('list')
|
|
12
|
+
.alias('ls')
|
|
13
|
+
.description('List detected threats')
|
|
14
|
+
.option('-a, --agent <agentId>', 'Filter by agent ID')
|
|
15
|
+
.option('-s, --severity <severity>', 'Filter by severity (low, medium, high, critical)')
|
|
16
|
+
.option('--status <status>', 'Filter by status (detected, blocked, escalated, resolved)')
|
|
17
|
+
.option('-l, --limit <n>', 'Limit results', '20')
|
|
18
|
+
.option('--json', 'Output as JSON')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const spinner = ora('Fetching threats...').start();
|
|
21
|
+
const result = await api.listThreats({
|
|
22
|
+
agentId: options.agent,
|
|
23
|
+
severity: options.severity,
|
|
24
|
+
status: options.status,
|
|
25
|
+
limit: Number.parseInt(options.limit, 10),
|
|
26
|
+
});
|
|
27
|
+
if (result.error) {
|
|
28
|
+
spinner.fail('Failed to fetch threats');
|
|
29
|
+
output.error(result.error.message);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
spinner.stop();
|
|
33
|
+
if (options.json) {
|
|
34
|
+
output.printJson(result.data);
|
|
35
|
+
}
|
|
36
|
+
else if (result.data) {
|
|
37
|
+
if (result.data.items.length === 0) {
|
|
38
|
+
output.success('No threats detected!');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
output.heading(`Security Threats (${result.data.total} total)`);
|
|
42
|
+
output.printThreatTable(result.data.items);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
threats
|
|
46
|
+
.command('resolve <threatId>')
|
|
47
|
+
.description('Mark a threat as resolved')
|
|
48
|
+
.option('-f, --force', 'Skip confirmation')
|
|
49
|
+
.action(async (threatId, options) => {
|
|
50
|
+
if (!options.force) {
|
|
51
|
+
const answers = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: 'confirm',
|
|
54
|
+
name: 'confirm',
|
|
55
|
+
message: `Mark threat ${threatId} as resolved?`,
|
|
56
|
+
default: true,
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
if (!answers.confirm) {
|
|
60
|
+
output.info('Operation cancelled');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const spinner = ora('Resolving threat...').start();
|
|
65
|
+
const result = await api.resolveThreat(threatId);
|
|
66
|
+
if (result.error) {
|
|
67
|
+
spinner.fail('Failed to resolve threat');
|
|
68
|
+
output.error(result.error.message);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
spinner.succeed('Threat resolved');
|
|
72
|
+
if (result.data) {
|
|
73
|
+
output.info(`Resolved at: ${output.formatDate(result.data.resolvedAt || new Date().toISOString())}`);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
threats
|
|
77
|
+
.command('watch')
|
|
78
|
+
.description('Watch for threats in real-time')
|
|
79
|
+
.option('-a, --agent <agentId>', 'Filter by agent ID')
|
|
80
|
+
.option('-s, --severity <severity>', 'Minimum severity to show')
|
|
81
|
+
.action(async (options) => {
|
|
82
|
+
output.heading('Threat Monitor');
|
|
83
|
+
output.info('Press Ctrl+C to stop');
|
|
84
|
+
console.log();
|
|
85
|
+
let lastSeen = null;
|
|
86
|
+
const fetchThreats = async () => {
|
|
87
|
+
const result = await api.listThreats({
|
|
88
|
+
agentId: options.agent,
|
|
89
|
+
severity: options.severity,
|
|
90
|
+
limit: 10,
|
|
91
|
+
});
|
|
92
|
+
if (result.data && result.data.items.length > 0) {
|
|
93
|
+
const newThreats = lastSeen
|
|
94
|
+
? result.data.items.filter(t => t.id !== lastSeen && new Date(t.createdAt) > new Date(lastSeen))
|
|
95
|
+
: result.data.items;
|
|
96
|
+
if (newThreats.length > 0) {
|
|
97
|
+
for (const threat of newThreats) {
|
|
98
|
+
const severityStr = output.formatSeverity(threat.severity);
|
|
99
|
+
const statusStr = output.formatStatus(threat.status);
|
|
100
|
+
console.log(`[${output.formatDate(threat.createdAt)}] ${severityStr} ${threat.threatType} - Agent: ${threat.agentId} - Status: ${statusStr}`);
|
|
101
|
+
}
|
|
102
|
+
lastSeen = result.data.items[0].id;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
// Initial fetch
|
|
107
|
+
await fetchThreats();
|
|
108
|
+
// Poll every 3 seconds
|
|
109
|
+
const interval = setInterval(fetchThreats, 3000);
|
|
110
|
+
// Handle Ctrl+C
|
|
111
|
+
process.on('SIGINT', () => {
|
|
112
|
+
clearInterval(interval);
|
|
113
|
+
console.log();
|
|
114
|
+
output.info('Monitoring stopped');
|
|
115
|
+
process.exit(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
threats
|
|
119
|
+
.command('stats')
|
|
120
|
+
.description('Show threat statistics')
|
|
121
|
+
.action(async () => {
|
|
122
|
+
const spinner = ora('Fetching threat statistics...').start();
|
|
123
|
+
const result = await api.listThreats({ limit: 1000 });
|
|
124
|
+
if (result.error) {
|
|
125
|
+
spinner.fail('Failed to fetch threats');
|
|
126
|
+
output.error(result.error.message);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
spinner.stop();
|
|
130
|
+
if (result.data) {
|
|
131
|
+
const threats = result.data.items;
|
|
132
|
+
const bySeverity = {};
|
|
133
|
+
const byStatus = {};
|
|
134
|
+
const byType = {};
|
|
135
|
+
for (const threat of threats) {
|
|
136
|
+
bySeverity[threat.severity] = (bySeverity[threat.severity] || 0) + 1;
|
|
137
|
+
byStatus[threat.status] = (byStatus[threat.status] || 0) + 1;
|
|
138
|
+
byType[threat.threatType] = (byType[threat.threatType] || 0) + 1;
|
|
139
|
+
}
|
|
140
|
+
output.heading('Threat Statistics');
|
|
141
|
+
output.printKeyValue([
|
|
142
|
+
['Total Threats', result.data.total],
|
|
143
|
+
['Blocked', byStatus['blocked'] || 0],
|
|
144
|
+
['Resolved', byStatus['resolved'] || 0],
|
|
145
|
+
]);
|
|
146
|
+
console.log();
|
|
147
|
+
output.heading('By Severity');
|
|
148
|
+
const severityRows = Object.entries(bySeverity)
|
|
149
|
+
.map(([sev, count]) => [output.formatSeverity(sev), String(count)]);
|
|
150
|
+
output.printTable(['Severity', 'Count'], severityRows);
|
|
151
|
+
console.log();
|
|
152
|
+
output.heading('By Type');
|
|
153
|
+
const typeRows = Object.entries(byType)
|
|
154
|
+
.sort((a, b) => b[1] - a[1])
|
|
155
|
+
.slice(0, 10)
|
|
156
|
+
.map(([type, count]) => [type, String(count)]);
|
|
157
|
+
output.printTable(['Threat Type', 'Count'], typeRows);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return threats;
|
|
161
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/commands/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBpC,wBAAgB,qBAAqB,IAAI,OAAO,CAkO/C"}
|