opena2a-cli 0.1.2 → 0.2.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/README.md +225 -1
- package/dist/commands/guard-hooks.d.ts +27 -0
- package/dist/commands/guard-hooks.d.ts.map +1 -0
- package/dist/commands/guard-hooks.js +207 -0
- package/dist/commands/guard-hooks.js.map +1 -0
- package/dist/commands/guard-policy.d.ts +54 -0
- package/dist/commands/guard-policy.d.ts.map +1 -0
- package/dist/commands/guard-policy.js +251 -0
- package/dist/commands/guard-policy.js.map +1 -0
- package/dist/commands/guard-signing.d.ts +52 -0
- package/dist/commands/guard-signing.d.ts.map +1 -0
- package/dist/commands/guard-signing.js +185 -0
- package/dist/commands/guard-signing.js.map +1 -0
- package/dist/commands/guard-snapshots.d.ts +54 -0
- package/dist/commands/guard-snapshots.d.ts.map +1 -0
- package/dist/commands/guard-snapshots.js +346 -0
- package/dist/commands/guard-snapshots.js.map +1 -0
- package/dist/commands/guard.d.ts +60 -4
- package/dist/commands/guard.d.ts.map +1 -1
- package/dist/commands/guard.js +475 -95
- package/dist/commands/guard.js.map +1 -1
- package/dist/commands/init.js +3 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/shield.d.ts +3 -0
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +458 -30
- package/dist/commands/shield.js.map +1 -1
- package/dist/index.js +15 -6
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1 -0
- package/dist/router.js.map +1 -1
- package/dist/shield/arp-bridge.d.ts +62 -0
- package/dist/shield/arp-bridge.d.ts.map +1 -0
- package/dist/shield/arp-bridge.js +198 -0
- package/dist/shield/arp-bridge.js.map +1 -0
- package/dist/shield/baselines.d.ts +58 -0
- package/dist/shield/baselines.d.ts.map +1 -0
- package/dist/shield/baselines.js +371 -0
- package/dist/shield/baselines.js.map +1 -0
- package/dist/shield/findings.d.ts +52 -0
- package/dist/shield/findings.d.ts.map +1 -0
- package/dist/shield/findings.js +336 -0
- package/dist/shield/findings.js.map +1 -0
- package/dist/shield/integrity.d.ts.map +1 -1
- package/dist/shield/integrity.js +6 -2
- package/dist/shield/integrity.js.map +1 -1
- package/dist/shield/report-html.d.ts +29 -0
- package/dist/shield/report-html.d.ts.map +1 -0
- package/dist/shield/report-html.js +596 -0
- package/dist/shield/report-html.js.map +1 -0
- package/dist/shield/sarif.d.ts +65 -0
- package/dist/shield/sarif.d.ts.map +1 -0
- package/dist/shield/sarif.js +108 -0
- package/dist/shield/sarif.js.map +1 -0
- package/dist/shield/status.d.ts.map +1 -1
- package/dist/shield/status.js +6 -6
- package/dist/shield/status.js.map +1 -1
- package/dist/shield/types.d.ts +19 -1
- package/dist/shield/types.d.ts.map +1 -1
- package/dist/shield/types.js +2 -1
- package/dist/shield/types.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/shield.js
CHANGED
|
@@ -12,12 +12,48 @@
|
|
|
12
12
|
* - recover: Exit lockdown mode
|
|
13
13
|
* - report: Generate a security posture report
|
|
14
14
|
* - session: Show current AI coding assistant session identity
|
|
15
|
+
* - baseline: View adaptive enforcement baselines for agents
|
|
15
16
|
* - suggest: LLM-powered policy suggestions from observed behavior
|
|
16
17
|
* - explain: LLM-powered anomaly explanations for events
|
|
17
18
|
* - triage: LLM-powered incident classification and response
|
|
18
19
|
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
19
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
54
|
exports.shield = shield;
|
|
55
|
+
const fs = __importStar(require("node:fs"));
|
|
56
|
+
const path = __importStar(require("node:path"));
|
|
21
57
|
const colors_js_1 = require("../util/colors.js");
|
|
22
58
|
// --- Core dispatcher ---
|
|
23
59
|
async function shield(options) {
|
|
@@ -41,15 +77,19 @@ async function shield(options) {
|
|
|
41
77
|
return handleReport(options);
|
|
42
78
|
case 'session':
|
|
43
79
|
return handleSession(options);
|
|
80
|
+
case 'baseline':
|
|
81
|
+
return handleBaseline(options);
|
|
44
82
|
case 'suggest':
|
|
45
83
|
return handleSuggest(options);
|
|
46
84
|
case 'explain':
|
|
47
85
|
return handleExplain(options);
|
|
48
86
|
case 'triage':
|
|
49
87
|
return handleTriage(options);
|
|
88
|
+
case 'monitor':
|
|
89
|
+
return handleMonitor(options);
|
|
50
90
|
default:
|
|
51
91
|
process.stderr.write((0, colors_js_1.red)(`Unknown subcommand: ${options.subcommand}\n`));
|
|
52
|
-
process.stderr.write('Usage: opena2a shield <init|status|log|selfcheck|policy|evaluate|recover|report|session|suggest|explain|triage>\n');
|
|
92
|
+
process.stderr.write('Usage: opena2a shield <init|status|log|selfcheck|policy|evaluate|recover|report|monitor|session|baseline|suggest|explain|triage>\n');
|
|
53
93
|
return 1;
|
|
54
94
|
}
|
|
55
95
|
}
|
|
@@ -164,6 +204,7 @@ async function handlePolicy(options) {
|
|
|
164
204
|
}
|
|
165
205
|
async function handleEvaluate(options) {
|
|
166
206
|
const { loadPolicy, evaluatePolicy } = await import('../shield/policy.js');
|
|
207
|
+
const { writeEvent } = await import('../shield/events.js');
|
|
167
208
|
const isJson = options.format === 'json';
|
|
168
209
|
const policy = loadPolicy(options.dir);
|
|
169
210
|
if (!policy) {
|
|
@@ -175,17 +216,68 @@ async function handleEvaluate(options) {
|
|
|
175
216
|
}
|
|
176
217
|
return 1;
|
|
177
218
|
}
|
|
178
|
-
|
|
219
|
+
// Determine the command string from positional args or fall back to category-based evaluation.
|
|
220
|
+
const commandString = options.args && options.args.length > 0
|
|
221
|
+
? options.args.join(' ')
|
|
222
|
+
: null;
|
|
179
223
|
const agent = options.agent ?? null;
|
|
180
|
-
|
|
224
|
+
let category;
|
|
225
|
+
let target;
|
|
226
|
+
if (commandString) {
|
|
227
|
+
// Shell hook mode: parse the command to extract the binary name (first word).
|
|
228
|
+
// Handle pipes, subshells, and quoted strings by taking the first token.
|
|
229
|
+
const trimmed = commandString.trim();
|
|
230
|
+
const firstWord = trimmed.split(/[\s|;&]/)[0] ?? trimmed;
|
|
231
|
+
category = 'processes';
|
|
232
|
+
target = firstWord;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Explicit category mode (used by direct API calls)
|
|
236
|
+
category = options.category ?? 'processes';
|
|
237
|
+
target = '';
|
|
238
|
+
}
|
|
181
239
|
const decision = evaluatePolicy(policy, agent, category, target);
|
|
240
|
+
// Write enforcement events for non-allowed decisions
|
|
241
|
+
if (decision.outcome === 'blocked') {
|
|
242
|
+
writeEvent({
|
|
243
|
+
source: 'shield',
|
|
244
|
+
category: 'enforcement',
|
|
245
|
+
severity: 'high',
|
|
246
|
+
agent,
|
|
247
|
+
sessionId: null,
|
|
248
|
+
action: 'command.blocked',
|
|
249
|
+
target: commandString ?? target,
|
|
250
|
+
outcome: 'blocked',
|
|
251
|
+
detail: { rule: decision.rule, mode: policy.mode },
|
|
252
|
+
orgId: null,
|
|
253
|
+
managed: false,
|
|
254
|
+
agentId: null,
|
|
255
|
+
});
|
|
256
|
+
process.stderr.write(`[shield] blocked: ${commandString ?? target} (rule: ${decision.rule})\n`);
|
|
257
|
+
}
|
|
258
|
+
else if (decision.outcome === 'monitored') {
|
|
259
|
+
writeEvent({
|
|
260
|
+
source: 'shield',
|
|
261
|
+
category: 'enforcement',
|
|
262
|
+
severity: 'medium',
|
|
263
|
+
agent,
|
|
264
|
+
sessionId: null,
|
|
265
|
+
action: 'command.monitored',
|
|
266
|
+
target: commandString ?? target,
|
|
267
|
+
outcome: 'monitored',
|
|
268
|
+
detail: { rule: decision.rule, mode: policy.mode },
|
|
269
|
+
orgId: null,
|
|
270
|
+
managed: false,
|
|
271
|
+
agentId: null,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
182
274
|
if (isJson) {
|
|
183
275
|
process.stdout.write(JSON.stringify(decision, null, 2) + '\n');
|
|
184
276
|
}
|
|
185
|
-
else {
|
|
277
|
+
else if (decision.outcome !== 'blocked') {
|
|
278
|
+
// Only print non-blocked outcomes to stdout (blocked warning goes to stderr above)
|
|
186
279
|
const outcomeLabel = decision.outcome === 'allowed' ? (0, colors_js_1.green)('ALLOWED')
|
|
187
|
-
:
|
|
188
|
-
: (0, colors_js_1.yellow)('MONITORED');
|
|
280
|
+
: (0, colors_js_1.yellow)('MONITORED');
|
|
189
281
|
process.stdout.write(`${outcomeLabel} rule=${decision.rule}\n`);
|
|
190
282
|
}
|
|
191
283
|
return decision.outcome === 'blocked' ? 1 : 0;
|
|
@@ -272,6 +364,56 @@ async function handleReport(options) {
|
|
|
272
364
|
.map(([name, count]) => ({ name, count }));
|
|
273
365
|
const topAgents = topN(byAgent, 10);
|
|
274
366
|
const topActions = topN(byAction, 10);
|
|
367
|
+
// --- Classify events into findings ---
|
|
368
|
+
const { classifyEvents, classifyViolation } = await import('../shield/findings.js');
|
|
369
|
+
const classifiedFindings = classifyEvents(events);
|
|
370
|
+
// --- SARIF output ---
|
|
371
|
+
if (options.format === 'sarif') {
|
|
372
|
+
const { toSarif } = await import('../shield/sarif.js');
|
|
373
|
+
const { getVersion } = await import('../util/version.js');
|
|
374
|
+
const sarif = toSarif(classifiedFindings, getVersion());
|
|
375
|
+
const sarifJson = JSON.stringify(sarif, null, 2);
|
|
376
|
+
if (options.report) {
|
|
377
|
+
const reportPath = path.resolve(options.report);
|
|
378
|
+
fs.writeFileSync(reportPath, sarifJson, 'utf-8');
|
|
379
|
+
process.stdout.write(`SARIF report written to ${reportPath}\n`);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
process.stdout.write(sarifJson + '\n');
|
|
383
|
+
}
|
|
384
|
+
return 0;
|
|
385
|
+
}
|
|
386
|
+
// --- HTML report output ---
|
|
387
|
+
if (options.report) {
|
|
388
|
+
const weeklyReport = await buildWeeklyReport(events, since, bySeverity, byOutcome, byAgent, topActions);
|
|
389
|
+
// Enrich violations with finding data
|
|
390
|
+
for (const v of weeklyReport.policyEvaluation.topViolations) {
|
|
391
|
+
const finding = classifyViolation(v);
|
|
392
|
+
if (finding) {
|
|
393
|
+
v.findingId = finding.id;
|
|
394
|
+
v.remediationCommand = finding.remediation;
|
|
395
|
+
v.compliance = [finding.owaspAgentic, finding.mitreAtlas];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Compute trend from snapshot history
|
|
399
|
+
const trendData = await computeTrend(weeklyReport);
|
|
400
|
+
if (trendData) {
|
|
401
|
+
weeklyReport.posture.trend = trendData;
|
|
402
|
+
}
|
|
403
|
+
// Save snapshot for future trend comparisons
|
|
404
|
+
await saveReportSnapshot(weeklyReport, classifiedFindings);
|
|
405
|
+
let narrative = null;
|
|
406
|
+
if (options.analyze) {
|
|
407
|
+
const { generateNarrative } = await import('../shield/llm.js');
|
|
408
|
+
narrative = await generateNarrative(weeklyReport);
|
|
409
|
+
}
|
|
410
|
+
const { generateShieldHtmlReport } = await import('../shield/report-html.js');
|
|
411
|
+
const html = generateShieldHtmlReport(weeklyReport, narrative, classifiedFindings, trendData);
|
|
412
|
+
const reportPath = path.resolve(options.report);
|
|
413
|
+
fs.writeFileSync(reportPath, html, 'utf-8');
|
|
414
|
+
process.stdout.write(`Report written to ${reportPath}\n`);
|
|
415
|
+
return 0;
|
|
416
|
+
}
|
|
275
417
|
if (isJson) {
|
|
276
418
|
const data = {
|
|
277
419
|
periodSince: since,
|
|
@@ -369,12 +511,80 @@ async function handleReport(options) {
|
|
|
369
511
|
process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
|
|
370
512
|
return 0;
|
|
371
513
|
}
|
|
514
|
+
// --- Snapshot Persistence for Trend Analysis ---
|
|
515
|
+
async function saveReportSnapshot(report, findings) {
|
|
516
|
+
const { getShieldDir } = await import('../shield/events.js');
|
|
517
|
+
const { SHIELD_SNAPSHOTS_FILE } = await import('../shield/types.js');
|
|
518
|
+
const findingCounts = {};
|
|
519
|
+
for (const f of findings) {
|
|
520
|
+
const sev = f.finding.severity;
|
|
521
|
+
findingCounts[sev] = (findingCounts[sev] ?? 0) + f.count;
|
|
522
|
+
}
|
|
523
|
+
const snapshot = {
|
|
524
|
+
timestamp: report.generatedAt,
|
|
525
|
+
score: report.posture.score,
|
|
526
|
+
grade: report.posture.grade,
|
|
527
|
+
findingCounts,
|
|
528
|
+
totalFindings: findings.reduce((sum, f) => sum + f.count, 0),
|
|
529
|
+
};
|
|
530
|
+
const dir = getShieldDir();
|
|
531
|
+
const filePath = path.join(dir, SHIELD_SNAPSHOTS_FILE);
|
|
532
|
+
fs.appendFileSync(filePath, JSON.stringify(snapshot) + '\n', 'utf-8');
|
|
533
|
+
}
|
|
534
|
+
async function loadPreviousSnapshot() {
|
|
535
|
+
const { getShieldDir } = await import('../shield/events.js');
|
|
536
|
+
const { SHIELD_SNAPSHOTS_FILE } = await import('../shield/types.js');
|
|
537
|
+
const dir = getShieldDir();
|
|
538
|
+
const filePath = path.join(dir, SHIELD_SNAPSHOTS_FILE);
|
|
539
|
+
if (!fs.existsSync(filePath))
|
|
540
|
+
return null;
|
|
541
|
+
let content;
|
|
542
|
+
try {
|
|
543
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
const lines = content.split('\n').filter(l => l.trim().length > 0);
|
|
549
|
+
if (lines.length === 0)
|
|
550
|
+
return null;
|
|
551
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
552
|
+
try {
|
|
553
|
+
return JSON.parse(lines[i]);
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
async function computeTrend(report) {
|
|
562
|
+
const previous = await loadPreviousSnapshot();
|
|
563
|
+
if (!previous)
|
|
564
|
+
return null;
|
|
565
|
+
const delta = report.posture.score - previous.score;
|
|
566
|
+
const periodMs = new Date(report.generatedAt).getTime() - new Date(previous.timestamp).getTime();
|
|
567
|
+
const periodDays = Math.max(1, Math.round(periodMs / (24 * 60 * 60 * 1000)));
|
|
568
|
+
let direction;
|
|
569
|
+
if (delta > 3)
|
|
570
|
+
direction = 'improving';
|
|
571
|
+
else if (delta < -3)
|
|
572
|
+
direction = 'declining';
|
|
573
|
+
else
|
|
574
|
+
direction = 'stable';
|
|
575
|
+
return {
|
|
576
|
+
previousScore: previous.score,
|
|
577
|
+
previousGrade: previous.grade,
|
|
578
|
+
delta,
|
|
579
|
+
direction,
|
|
580
|
+
periodDays,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
372
583
|
/**
|
|
373
|
-
* Build a WeeklyReport from aggregated event data
|
|
374
|
-
* Returns the narrative or null if LLM is unavailable.
|
|
584
|
+
* Build a WeeklyReport from aggregated event data.
|
|
375
585
|
*/
|
|
376
|
-
async function
|
|
377
|
-
const {
|
|
586
|
+
async function buildWeeklyReport(events, since, bySeverity, byOutcome, byAgent, topActions) {
|
|
587
|
+
const { getARPStats } = await import('../shield/arp-bridge.js');
|
|
378
588
|
const { hostname } = await import('node:os');
|
|
379
589
|
const now = new Date();
|
|
380
590
|
const sinceMatch = since.match(/^(\d+)([dwm])$/);
|
|
@@ -484,17 +694,32 @@ async function buildNarrative(events, since, bySeverity, byOutcome, byAgent, top
|
|
|
484
694
|
blockedInstalls += 1;
|
|
485
695
|
}
|
|
486
696
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const
|
|
697
|
+
// Posture score: only count external threat events, not Shield's own diagnostic scans.
|
|
698
|
+
// Shield events (posture-assessment, credential-finding, shield.init) are informational.
|
|
699
|
+
const threatEvents = events.filter(e => e.source !== 'shield');
|
|
700
|
+
const threatSeverity = {};
|
|
701
|
+
for (const e of threatEvents) {
|
|
702
|
+
threatSeverity[e.severity] = (threatSeverity[e.severity] ?? 0) + 1;
|
|
703
|
+
}
|
|
704
|
+
const criticalCount = threatSeverity['critical'] ?? 0;
|
|
705
|
+
const highCount = threatSeverity['high'] ?? 0;
|
|
706
|
+
const mediumCount = threatSeverity['medium'] ?? 0;
|
|
490
707
|
const blockedCount = byOutcome['blocked'] ?? 0;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
708
|
+
const agentCount = Object.keys(byAgent).length;
|
|
709
|
+
// Weighted factor scoring: severity (50%), enforcement (25%), coverage (25%).
|
|
710
|
+
// Severity uses capped penalties so a few events don't destroy the entire score.
|
|
711
|
+
const severityPenalty = Math.min(60, criticalCount * 12 + highCount * 5 + mediumCount * 2);
|
|
712
|
+
const severityScore = 100 - severityPenalty;
|
|
713
|
+
// Enforcement: actively blocking threats is a strong positive signal.
|
|
714
|
+
const enforcementScore = blockedCount > 0
|
|
715
|
+
? Math.min(100, 60 + Math.min(40, blockedCount * 5))
|
|
716
|
+
: (threatEvents.length > 0 ? 30 : 50);
|
|
717
|
+
// Coverage: more agents monitored = better visibility.
|
|
718
|
+
const coverageScore = agentCount >= 3 ? 80 : agentCount > 0 ? 60 : 30;
|
|
719
|
+
let score = Math.round(severityScore * 0.5 + enforcementScore * 0.25 + coverageScore * 0.25);
|
|
496
720
|
score = Math.max(0, Math.min(100, score));
|
|
497
721
|
const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
|
|
722
|
+
const arpStats = getARPStats(since);
|
|
498
723
|
const report = {
|
|
499
724
|
version: 1,
|
|
500
725
|
generatedAt: now.toISOString(),
|
|
@@ -524,31 +749,114 @@ async function buildNarrative(events, since, bySeverity, byOutcome, byAgent, top
|
|
|
524
749
|
blockedInstalls,
|
|
525
750
|
lowTrustPackages: [],
|
|
526
751
|
},
|
|
527
|
-
configIntegrity: {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
752
|
+
configIntegrity: await (async () => {
|
|
753
|
+
try {
|
|
754
|
+
const mod = await import('./guard.js');
|
|
755
|
+
const fn = mod.verifyConfigIntegrity ?? mod.default?.verifyConfigIntegrity;
|
|
756
|
+
if (fn)
|
|
757
|
+
return fn();
|
|
758
|
+
return { filesMonitored: 0, tamperedFiles: [], signatureStatus: 'unsigned' };
|
|
759
|
+
}
|
|
760
|
+
catch {
|
|
761
|
+
return { filesMonitored: 0, tamperedFiles: [], signatureStatus: 'unsigned' };
|
|
762
|
+
}
|
|
763
|
+
})(),
|
|
532
764
|
runtimeProtection: {
|
|
533
|
-
arpActive:
|
|
534
|
-
processesSpawned:
|
|
535
|
-
networkConnections:
|
|
536
|
-
anomalies:
|
|
765
|
+
arpActive: arpStats.totalEvents > 0,
|
|
766
|
+
processesSpawned: arpStats.processEvents,
|
|
767
|
+
networkConnections: arpStats.networkEvents,
|
|
768
|
+
anomalies: arpStats.anomalies + arpStats.violations + arpStats.threats,
|
|
537
769
|
},
|
|
538
770
|
posture: {
|
|
539
771
|
score,
|
|
540
772
|
grade,
|
|
541
773
|
factors: [
|
|
542
|
-
{ name: 'severity', score:
|
|
543
|
-
{ name: 'enforcement', score:
|
|
544
|
-
{ name: 'coverage', score:
|
|
774
|
+
{ name: 'severity', score: severityScore, weight: 0.5, detail: `${criticalCount} critical, ${highCount} high, ${mediumCount} medium` },
|
|
775
|
+
{ name: 'enforcement', score: enforcementScore, weight: 0.25, detail: `${blockedCount} blocked` },
|
|
776
|
+
{ name: 'coverage', score: coverageScore, weight: 0.25, detail: `${agentCount} agents monitored` },
|
|
545
777
|
],
|
|
546
778
|
trend: null,
|
|
547
779
|
comparative: null,
|
|
548
780
|
},
|
|
549
781
|
};
|
|
782
|
+
return report;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Build a WeeklyReport and call generateNarrative().
|
|
786
|
+
* Returns the narrative or null if LLM is unavailable.
|
|
787
|
+
*/
|
|
788
|
+
async function buildNarrative(events, since, bySeverity, byOutcome, byAgent, topActions) {
|
|
789
|
+
const { generateNarrative } = await import('../shield/llm.js');
|
|
790
|
+
const report = await buildWeeklyReport(events, since, bySeverity, byOutcome, byAgent, topActions);
|
|
550
791
|
return generateNarrative(report);
|
|
551
792
|
}
|
|
793
|
+
// --- Monitor ---
|
|
794
|
+
async function handleMonitor(options) {
|
|
795
|
+
const { importARPEvents, getARPStats } = await import('../shield/arp-bridge.js');
|
|
796
|
+
const isJson = options.format === 'json';
|
|
797
|
+
const targetDir = options.dir ? path.resolve(options.dir) : process.cwd();
|
|
798
|
+
// Step 1: Import any existing ARP events into Shield's hash chain
|
|
799
|
+
const result = importARPEvents(targetDir, options.agent);
|
|
800
|
+
// Step 2: Get ARP stats from Shield's unified log
|
|
801
|
+
const stats = getARPStats(options.since ?? '7d');
|
|
802
|
+
if (isJson) {
|
|
803
|
+
process.stdout.write(JSON.stringify({
|
|
804
|
+
import: result,
|
|
805
|
+
stats,
|
|
806
|
+
}, null, 2) + '\n');
|
|
807
|
+
return 0;
|
|
808
|
+
}
|
|
809
|
+
process.stdout.write((0, colors_js_1.bold)('Shield ARP Monitor') + '\n');
|
|
810
|
+
process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
|
|
811
|
+
// Import results
|
|
812
|
+
if (result.total > 0) {
|
|
813
|
+
process.stdout.write((0, colors_js_1.bold)(' Event Import') + '\n');
|
|
814
|
+
if (result.imported > 0) {
|
|
815
|
+
process.stdout.write(` ${(0, colors_js_1.green)(`${result.imported} new events`)} imported into Shield log\n`);
|
|
816
|
+
}
|
|
817
|
+
if (result.skipped > 0) {
|
|
818
|
+
process.stdout.write(` ${(0, colors_js_1.dim)(`${result.skipped} already imported`)}\n`);
|
|
819
|
+
}
|
|
820
|
+
if (result.errors > 0) {
|
|
821
|
+
process.stdout.write(` ${(0, colors_js_1.yellow)(`${result.errors} parse errors`)}\n`);
|
|
822
|
+
}
|
|
823
|
+
process.stdout.write('\n');
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
process.stdout.write((0, colors_js_1.dim)(' No ARP events found.') + '\n');
|
|
827
|
+
process.stdout.write((0, colors_js_1.dim)(' Start ARP monitoring: opena2a runtime start') + '\n\n');
|
|
828
|
+
}
|
|
829
|
+
// ARP stats from Shield's unified log
|
|
830
|
+
if (stats.totalEvents > 0) {
|
|
831
|
+
process.stdout.write((0, colors_js_1.bold)(' Runtime Protection Summary') + '\n');
|
|
832
|
+
process.stdout.write(` ${(0, colors_js_1.dim)('Total events')} ${stats.totalEvents}\n`);
|
|
833
|
+
process.stdout.write(` ${(0, colors_js_1.dim)('Process events')} ${stats.processEvents}\n`);
|
|
834
|
+
process.stdout.write(` ${(0, colors_js_1.dim)('Network events')} ${stats.networkEvents}\n`);
|
|
835
|
+
process.stdout.write(` ${(0, colors_js_1.dim)('Filesystem events')} ${stats.filesystemEvents}\n`);
|
|
836
|
+
process.stdout.write(` ${(0, colors_js_1.dim)('Prompt events')} ${stats.promptEvents}\n`);
|
|
837
|
+
if (stats.anomalies > 0 || stats.violations > 0 || stats.threats > 0) {
|
|
838
|
+
process.stdout.write('\n');
|
|
839
|
+
process.stdout.write((0, colors_js_1.bold)(' Detections') + '\n');
|
|
840
|
+
if (stats.anomalies > 0) {
|
|
841
|
+
process.stdout.write(` ${(0, colors_js_1.yellow)(`${stats.anomalies} anomalies`)}\n`);
|
|
842
|
+
}
|
|
843
|
+
if (stats.violations > 0) {
|
|
844
|
+
process.stdout.write(` ${(0, colors_js_1.red)(`${stats.violations} violations`)}\n`);
|
|
845
|
+
}
|
|
846
|
+
if (stats.threats > 0) {
|
|
847
|
+
process.stdout.write(` ${(0, colors_js_1.bold)((0, colors_js_1.red)(`${stats.threats} threats`))}\n`);
|
|
848
|
+
}
|
|
849
|
+
if (stats.enforcements > 0) {
|
|
850
|
+
process.stdout.write(` ${(0, colors_js_1.cyan)(`${stats.enforcements} enforcements`)}\n`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
process.stdout.write(` ${(0, colors_js_1.green)('No anomalies or threats detected')}\n`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
|
|
858
|
+
return 0;
|
|
859
|
+
}
|
|
552
860
|
// --- Session ---
|
|
553
861
|
async function handleSession(options) {
|
|
554
862
|
const { identifySession, collectSignals } = await import('../shield/session.js');
|
|
@@ -589,6 +897,126 @@ async function handleSession(options) {
|
|
|
589
897
|
process.stdout.write((0, colors_js_1.gray)('-'.repeat(40)) + '\n');
|
|
590
898
|
return 0;
|
|
591
899
|
}
|
|
900
|
+
// --- Baseline ---
|
|
901
|
+
async function handleBaseline(options) {
|
|
902
|
+
const { listBaselines, getBaseline, computeStability, checkPhaseTransition } = await import('../shield/baselines.js');
|
|
903
|
+
const isJson = options.format === 'json';
|
|
904
|
+
if (options.agent) {
|
|
905
|
+
// Detailed view for a single agent
|
|
906
|
+
const baseline = getBaseline(options.agent);
|
|
907
|
+
const stability = computeStability(baseline);
|
|
908
|
+
const transition = checkPhaseTransition(baseline);
|
|
909
|
+
if (isJson) {
|
|
910
|
+
process.stdout.write(JSON.stringify({
|
|
911
|
+
...baseline,
|
|
912
|
+
stabilityScore: stability,
|
|
913
|
+
transition,
|
|
914
|
+
}, null, 2) + '\n');
|
|
915
|
+
return 0;
|
|
916
|
+
}
|
|
917
|
+
process.stdout.write((0, colors_js_1.bold)('Agent Baseline') + '\n');
|
|
918
|
+
process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
|
|
919
|
+
process.stdout.write(` Agent: ${(0, colors_js_1.cyan)(baseline.agent)}\n`);
|
|
920
|
+
process.stdout.write(` Phase: ${phaseColor(baseline.phase)}\n`);
|
|
921
|
+
process.stdout.write(` Stability: ${stabilityBar(stability)}\n`);
|
|
922
|
+
process.stdout.write(` Total actions: ${String(baseline.totalActions)}\n`);
|
|
923
|
+
process.stdout.write(` Total sessions: ${String(baseline.totalSessions)}\n`);
|
|
924
|
+
process.stdout.write(` Observed since: ${(0, colors_js_1.dim)(baseline.observationStart)}\n`);
|
|
925
|
+
process.stdout.write(` Last activity: ${(0, colors_js_1.dim)(baseline.observationEnd)}\n`);
|
|
926
|
+
if (baseline.lastNewBehaviorAt) {
|
|
927
|
+
process.stdout.write(` Last new behavior: ${(0, colors_js_1.dim)(baseline.lastNewBehaviorAt)}\n`);
|
|
928
|
+
}
|
|
929
|
+
process.stdout.write('\n');
|
|
930
|
+
process.stdout.write((0, colors_js_1.bold)(' Observed Behavior') + '\n');
|
|
931
|
+
const buckets = [
|
|
932
|
+
['Processes', baseline.observed.processes],
|
|
933
|
+
['Credentials', baseline.observed.credentials],
|
|
934
|
+
['Filesystem', baseline.observed.filesystemPaths],
|
|
935
|
+
['Network', baseline.observed.networkHosts],
|
|
936
|
+
['MCP Servers', baseline.observed.mcpServers],
|
|
937
|
+
];
|
|
938
|
+
for (const [label, entries] of buckets) {
|
|
939
|
+
const keys = Object.keys(entries);
|
|
940
|
+
if (keys.length === 0)
|
|
941
|
+
continue;
|
|
942
|
+
process.stdout.write(` ${label} (${keys.length}):\n`);
|
|
943
|
+
const sorted = Object.entries(entries).sort((a, b) => b[1] - a[1]);
|
|
944
|
+
for (const [name, count] of sorted.slice(0, 10)) {
|
|
945
|
+
process.stdout.write(` ${name.padEnd(40)} ${(0, colors_js_1.dim)(String(count) + 'x')}\n`);
|
|
946
|
+
}
|
|
947
|
+
if (sorted.length > 10) {
|
|
948
|
+
process.stdout.write((0, colors_js_1.dim)(` ... and ${sorted.length - 10} more\n`));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
process.stdout.write('\n');
|
|
952
|
+
process.stdout.write(` Transition: ${(0, colors_js_1.dim)(transition.reason)}\n`);
|
|
953
|
+
if (baseline.recommended) {
|
|
954
|
+
process.stdout.write('\n');
|
|
955
|
+
process.stdout.write((0, colors_js_1.bold)(' Recommended Policy') + '\n');
|
|
956
|
+
if (baseline.recommended.processes?.allow?.length) {
|
|
957
|
+
process.stdout.write(` Allow processes: ${baseline.recommended.processes.allow.length}\n`);
|
|
958
|
+
}
|
|
959
|
+
if (baseline.recommended.credentials?.allow?.length) {
|
|
960
|
+
process.stdout.write(` Allow credentials: ${baseline.recommended.credentials.allow.length}\n`);
|
|
961
|
+
}
|
|
962
|
+
if (baseline.recommended.network?.allow?.length) {
|
|
963
|
+
process.stdout.write(` Allow network: ${baseline.recommended.network.allow.length}\n`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
|
|
967
|
+
return 0;
|
|
968
|
+
}
|
|
969
|
+
// List all baselines
|
|
970
|
+
const baselines = listBaselines();
|
|
971
|
+
if (baselines.length === 0) {
|
|
972
|
+
if (isJson) {
|
|
973
|
+
process.stdout.write(JSON.stringify([], null, 2) + '\n');
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
process.stdout.write((0, colors_js_1.dim)('No baselines found. Shield will create baselines as agent activity is observed.\n'));
|
|
977
|
+
}
|
|
978
|
+
return 0;
|
|
979
|
+
}
|
|
980
|
+
if (isJson) {
|
|
981
|
+
process.stdout.write(JSON.stringify(baselines, null, 2) + '\n');
|
|
982
|
+
return 0;
|
|
983
|
+
}
|
|
984
|
+
process.stdout.write((0, colors_js_1.bold)('Agent Baselines') + '\n');
|
|
985
|
+
process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
|
|
986
|
+
process.stdout.write(` ${'Agent'.padEnd(20)} ${'Phase'.padEnd(10)} ${'Stability'.padEnd(12)} ${'Actions'.padEnd(10)} Sessions\n`);
|
|
987
|
+
process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
|
|
988
|
+
for (const bl of baselines) {
|
|
989
|
+
process.stdout.write(` ${bl.agent.padEnd(20)} ${phaseColor(bl.phase).padEnd(10 + colorPadding(bl.phase))} ` +
|
|
990
|
+
`${bl.stabilityScore.toFixed(2).padEnd(12)} ` +
|
|
991
|
+
`${String(bl.totalActions).padEnd(10)} ` +
|
|
992
|
+
`${String(bl.totalSessions)}\n`);
|
|
993
|
+
}
|
|
994
|
+
process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
|
|
995
|
+
return 0;
|
|
996
|
+
}
|
|
997
|
+
function phaseColor(phase) {
|
|
998
|
+
switch (phase) {
|
|
999
|
+
case 'learn': return (0, colors_js_1.cyan)(phase);
|
|
1000
|
+
case 'suggest': return (0, colors_js_1.yellow)(phase);
|
|
1001
|
+
case 'protect': return (0, colors_js_1.green)(phase);
|
|
1002
|
+
default: return (0, colors_js_1.dim)(phase);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/** ANSI codes add invisible characters; compute extra length for padding. */
|
|
1006
|
+
function colorPadding(phase) {
|
|
1007
|
+
return phaseColor(phase).length - phase.length;
|
|
1008
|
+
}
|
|
1009
|
+
function stabilityBar(score) {
|
|
1010
|
+
const filled = Math.round(score * 10);
|
|
1011
|
+
const empty = 10 - filled;
|
|
1012
|
+
const bar = '#'.repeat(filled) + '-'.repeat(empty);
|
|
1013
|
+
const label = (score * 100).toFixed(0) + '%';
|
|
1014
|
+
if (score >= 0.8)
|
|
1015
|
+
return (0, colors_js_1.green)(`[${bar}] ${label}`);
|
|
1016
|
+
if (score >= 0.5)
|
|
1017
|
+
return (0, colors_js_1.yellow)(`[${bar}] ${label}`);
|
|
1018
|
+
return (0, colors_js_1.dim)(`[${bar}] ${label}`);
|
|
1019
|
+
}
|
|
592
1020
|
// --- LLM intelligence handlers ---
|
|
593
1021
|
async function handleSuggest(options) {
|
|
594
1022
|
const { checkLlmAvailable, suggestPolicy } = await import('../shield/llm.js');
|