mergen-server 1.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 +57 -0
- package/README.md +96 -0
- package/dist/__stubs__/calibration.js +471 -0
- package/dist/__stubs__/causal.js +601 -0
- package/dist/__stubs__/closed-source.js +150 -0
- package/dist/__stubs__/license.js +35 -0
- package/dist/__stubs__/plans.js +197 -0
- package/dist/app.js +467 -0
- package/dist/cli.js +250 -0
- package/dist/commands/agent-identity.js +79 -0
- package/dist/commands/gate.js +107 -0
- package/dist/commands/github.js +632 -0
- package/dist/commands/incident.js +644 -0
- package/dist/commands/policy.js +710 -0
- package/dist/commands/scan.js +243 -0
- package/dist/commands/setup.js +1218 -0
- package/dist/commands/shared.js +109 -0
- package/dist/commands/team.js +809 -0
- package/dist/datadog/blame-attribution.js +228 -0
- package/dist/datadog/client.js +159 -0
- package/dist/datadog/compactor.js +169 -0
- package/dist/datadog/fingerprinter.js +34 -0
- package/dist/datadog/incident-state.js +21 -0
- package/dist/datadog/line-matcher.js +56 -0
- package/dist/datadog/memory-store.js +399 -0
- package/dist/datadog/otel-trace.js +67 -0
- package/dist/index.js +505 -0
- package/dist/intelligence/action-risk.js +132 -0
- package/dist/intelligence/activity-feed.js +40 -0
- package/dist/intelligence/agent-identity.js +115 -0
- package/dist/intelligence/agent-pipeline.js +329 -0
- package/dist/intelligence/agent-profiles.js +81 -0
- package/dist/intelligence/ai-commit.js +16 -0
- package/dist/intelligence/approval-analytics.js +42 -0
- package/dist/intelligence/approval-events.js +5 -0
- package/dist/intelligence/arch-boundaries.js +94 -0
- package/dist/intelligence/arch-graph.js +117 -0
- package/dist/intelligence/autonomy.js +397 -0
- package/dist/intelligence/azure-broker.js +57 -0
- package/dist/intelligence/azure-proxy-sessions.js +49 -0
- package/dist/intelligence/baseline.js +1 -0
- package/dist/intelligence/behavior-baseline.js +134 -0
- package/dist/intelligence/billing.js +1 -0
- package/dist/intelligence/blast-radius.js +224 -0
- package/dist/intelligence/bypass.js +157 -0
- package/dist/intelligence/calibration-classifier.js +77 -0
- package/dist/intelligence/calibration-git-sync.js +74 -0
- package/dist/intelligence/calibration.js +1 -0
- package/dist/intelligence/cascade-detector.js +58 -0
- package/dist/intelligence/case-study-generator.js +126 -0
- package/dist/intelligence/causal-graph.js +126 -0
- package/dist/intelligence/causal.js +1 -0
- package/dist/intelligence/change-risk.js +102 -0
- package/dist/intelligence/compliance-report.js +134 -0
- package/dist/intelligence/confidence-report.js +85 -0
- package/dist/intelligence/corpus-to-policy.js +140 -0
- package/dist/intelligence/credential-broker.js +112 -0
- package/dist/intelligence/credential-misuse.js +39 -0
- package/dist/intelligence/debug-sessions.js +108 -0
- package/dist/intelligence/decision-escalation.js +36 -0
- package/dist/intelligence/decision-memory.js +47 -0
- package/dist/intelligence/decision-similarity.js +85 -0
- package/dist/intelligence/default-safety-tests.js +53 -0
- package/dist/intelligence/degradation-watcher.js +108 -0
- package/dist/intelligence/detector-plugins.js +70 -0
- package/dist/intelligence/detectors.js +1 -0
- package/dist/intelligence/device-auth.js +138 -0
- package/dist/intelligence/diff-size.js +42 -0
- package/dist/intelligence/ebpf-verify.js +45 -0
- package/dist/intelligence/enterprise-policy-engine.js +1122 -0
- package/dist/intelligence/error-fingerprint.js +1 -0
- package/dist/intelligence/execution-gate.js +100 -0
- package/dist/intelligence/execution-mode.js +22 -0
- package/dist/intelligence/gate-analytics.js +256 -0
- package/dist/intelligence/gate-decision.js +64 -0
- package/dist/intelligence/gcp-broker.js +50 -0
- package/dist/intelligence/git-adr-sync.js +207 -0
- package/dist/intelligence/hitl-hold.js +460 -0
- package/dist/intelligence/hypothesis-history.js +93 -0
- package/dist/intelligence/idp-identity.js +83 -0
- package/dist/intelligence/impl-critic.js +216 -0
- package/dist/intelligence/incident-autopilot.js +622 -0
- package/dist/intelligence/incident-replay.js +162 -0
- package/dist/intelligence/incident-result-cache.js +33 -0
- package/dist/intelligence/incident-similarity.js +58 -0
- package/dist/intelligence/infra-detectors.js +312 -0
- package/dist/intelligence/license.js +185 -0
- package/dist/intelligence/llm-spokesperson.js +62 -0
- package/dist/intelligence/mcp-prompts.js +320 -0
- package/dist/intelligence/mcp-resources.js +171 -0
- package/dist/intelligence/normalize.js +108 -0
- package/dist/intelligence/notifications.js +83 -0
- package/dist/intelligence/oidc-issuer.js +87 -0
- package/dist/intelligence/override-corpus.js +597 -0
- package/dist/intelligence/planning-gate.js +84 -0
- package/dist/intelligence/plans.js +223 -0
- package/dist/intelligence/platt-scaling.js +164 -0
- package/dist/intelligence/policy-proposals.js +84 -0
- package/dist/intelligence/policy-suggester.js +161 -0
- package/dist/intelligence/policy-sync.js +103 -0
- package/dist/intelligence/policy-test-runner.js +35 -0
- package/dist/intelligence/postmortem-parser.js +181 -0
- package/dist/intelligence/postmortem-retrieval.js +224 -0
- package/dist/intelligence/postmortem-store.js +596 -0
- package/dist/intelligence/pr-commenter.js +81 -0
- package/dist/intelligence/pr-policy-gate.js +38 -0
- package/dist/intelligence/pr-shadow-analyzer.js +229 -0
- package/dist/intelligence/process-tree.js +108 -0
- package/dist/intelligence/prompts.js +1 -0
- package/dist/intelligence/replay.js +38 -0
- package/dist/intelligence/repro-steps.js +1 -0
- package/dist/intelligence/resource-extractors.js +0 -0
- package/dist/intelligence/resource-file-context.js +138 -0
- package/dist/intelligence/risk-score.js +22 -0
- package/dist/intelligence/rollback.js +137 -0
- package/dist/intelligence/runbook-updater.js +143 -0
- package/dist/intelligence/sandbox.js +194 -0
- package/dist/intelligence/script-trust.js +104 -0
- package/dist/intelligence/session-metrics.js +67 -0
- package/dist/intelligence/session-threat-tracker.js +231 -0
- package/dist/intelligence/shadow-digest-cron.js +81 -0
- package/dist/intelligence/shadow-log.js +284 -0
- package/dist/intelligence/shell-ast.js +145 -0
- package/dist/intelligence/siem-forward.js +104 -0
- package/dist/intelligence/slack-digest.js +151 -0
- package/dist/intelligence/slack-override-loop.js +217 -0
- package/dist/intelligence/slack-routing.js +67 -0
- package/dist/intelligence/slack.js +900 -0
- package/dist/intelligence/sql-ast.js +31 -0
- package/dist/intelligence/team.js +1 -0
- package/dist/intelligence/telemetry.js +76 -0
- package/dist/intelligence/threshold-optimizer.js +75 -0
- package/dist/intelligence/token-budget.js +66 -0
- package/dist/intelligence/tool-guard.js +708 -0
- package/dist/intelligence/tool-manifest 2.js +74 -0
- package/dist/intelligence/tool-manifest.js +125 -0
- package/dist/intelligence/tools-analysis.js +857 -0
- package/dist/intelligence/tools-arch.js +152 -0
- package/dist/intelligence/tools-autonomy.js +495 -0
- package/dist/intelligence/tools-blast-radius.js +140 -0
- package/dist/intelligence/tools-browser.js +431 -0
- package/dist/intelligence/tools-change-timeline.js +154 -0
- package/dist/intelligence/tools-credentials.js +117 -0
- package/dist/intelligence/tools-datadog.js +233 -0
- package/dist/intelligence/tools-debug-sessions.js +209 -0
- package/dist/intelligence/tools-discovery.js +284 -0
- package/dist/intelligence/tools-infra.js +612 -0
- package/dist/intelligence/tools-intent.js +128 -0
- package/dist/intelligence/tools-memory.js +514 -0
- package/dist/intelligence/tools-runbook.js +951 -0
- package/dist/intelligence/tools-sessions.js +107 -0
- package/dist/intelligence/tools-state.js +232 -0
- package/dist/intelligence/tools-utility.js +430 -0
- package/dist/intelligence/tools-validate.js +215 -0
- package/dist/intelligence/tools.js +1 -0
- package/dist/intelligence/trace-context.js +11 -0
- package/dist/intelligence/unclassified-clusters.js +87 -0
- package/dist/intelligence/usage.js +195 -0
- package/dist/routes/active-authors.js +63 -0
- package/dist/routes/activity-feed.js +21 -0
- package/dist/routes/adr.js +117 -0
- package/dist/routes/agent-activity.js +184 -0
- package/dist/routes/agent-blunders.js +163 -0
- package/dist/routes/agents.js +175 -0
- package/dist/routes/api-keys.js +65 -0
- package/dist/routes/arch.js +75 -0
- package/dist/routes/audit-export.js +93 -0
- package/dist/routes/billing-dashboard.js +158 -0
- package/dist/routes/billing-outcome.js +77 -0
- package/dist/routes/calibration.js +305 -0
- package/dist/routes/ci-gate-history.js +20 -0
- package/dist/routes/ci-gate.js +232 -0
- package/dist/routes/ci.js +293 -0
- package/dist/routes/compliance-report.js +24 -0
- package/dist/routes/confidence.js +30 -0
- package/dist/routes/credentials.js +119 -0
- package/dist/routes/dashboard.js +1407 -0
- package/dist/routes/decision-memory.js +42 -0
- package/dist/routes/demo.js +844 -0
- package/dist/routes/device-auth-stub.js +173 -0
- package/dist/routes/explain-why.js +39 -0
- package/dist/routes/gate-analytics.js +213 -0
- package/dist/routes/gate.js +28 -0
- package/dist/routes/github-webhook.js +364 -0
- package/dist/routes/habituation.js +30 -0
- package/dist/routes/health-integrations.js +243 -0
- package/dist/routes/heartbeats.js +45 -0
- package/dist/routes/hitl.js +364 -0
- package/dist/routes/impact-report.js +645 -0
- package/dist/routes/incident-webhook.js +63 -0
- package/dist/routes/incidents.js +366 -0
- package/dist/routes/layers.js +62 -0
- package/dist/routes/license.js +187 -0
- package/dist/routes/oidc-issuer.js +30 -0
- package/dist/routes/onboarding.js +170 -0
- package/dist/routes/otel.js +67 -0
- package/dist/routes/otlp-receiver.js +179 -0
- package/dist/routes/overrides.js +241 -0
- package/dist/routes/pagerduty.js +258 -0
- package/dist/routes/policies.js +809 -0
- package/dist/routes/policy-nl.js +169 -0
- package/dist/routes/postmortem.js +102 -0
- package/dist/routes/pr-shadow.js +47 -0
- package/dist/routes/rbac.js +61 -0
- package/dist/routes/replay.js +17 -0
- package/dist/routes/risk-report.js +164 -0
- package/dist/routes/runbooks.js +125 -0
- package/dist/routes/safety-test.js +174 -0
- package/dist/routes/sdk.js +222 -0
- package/dist/routes/sensor.js +819 -0
- package/dist/routes/sentry.js +140 -0
- package/dist/routes/sessions.js +43 -0
- package/dist/routes/setup-ui.js +594 -0
- package/dist/routes/shadow-report.js +107 -0
- package/dist/routes/slack-routing.js +171 -0
- package/dist/routes/team-usage.js +71 -0
- package/dist/routes/telemetry.js +28 -0
- package/dist/routes/tenants.js +199 -0
- package/dist/routes/tickets.js +167 -0
- package/dist/routes/validate.js +13 -0
- package/dist/routes/war-room.js +113 -0
- package/dist/scripts/check-arch.js +22 -0
- package/dist/seeds/community-corpus.js +176 -0
- package/dist/seeds/corpus.js +1224 -0
- package/dist/sensor/action-ledger.js +327 -0
- package/dist/sensor/adr-store.js +140 -0
- package/dist/sensor/agent-blunder-store.js +370 -0
- package/dist/sensor/agent-context-store.js +225 -0
- package/dist/sensor/agent-memory-store.js +231 -0
- package/dist/sensor/audit-fetch.js +23 -0
- package/dist/sensor/audit-log.js +273 -0
- package/dist/sensor/buffer-schemas.js +225 -0
- package/dist/sensor/buffer.js +599 -0
- package/dist/sensor/bypass-tracker.js +93 -0
- package/dist/sensor/ci-gate-history.js +70 -0
- package/dist/sensor/cloud-auth.js +183 -0
- package/dist/sensor/commit-context-store.js +235 -0
- package/dist/sensor/docker-log-stream.js +164 -0
- package/dist/sensor/docker-monitor.js +122 -0
- package/dist/sensor/extended-buffer.js +46 -0
- package/dist/sensor/feedback-token.js +41 -0
- package/dist/sensor/file-lock.js +6 -0
- package/dist/sensor/fs-watcher.js +106 -0
- package/dist/sensor/gate-heartbeat.js +122 -0
- package/dist/sensor/git-suspect.js +136 -0
- package/dist/sensor/habituation-store.js +77 -0
- package/dist/sensor/heartbeat-monitor.js +133 -0
- package/dist/sensor/incident-store.js +340 -0
- package/dist/sensor/infra-normalizer.js +513 -0
- package/dist/sensor/ingest.js +339 -0
- package/dist/sensor/jest-reporter.js +52 -0
- package/dist/sensor/k8s-events.js +132 -0
- package/dist/sensor/layer2-store.js +111 -0
- package/dist/sensor/layer3-store.js +230 -0
- package/dist/sensor/layer4-store.js +147 -0
- package/dist/sensor/logger.js +13 -0
- package/dist/sensor/otel-exporter.js +161 -0
- package/dist/sensor/paths.js +71 -0
- package/dist/sensor/policy-history.js +147 -0
- package/dist/sensor/pr-shadow-store.js +83 -0
- package/dist/sensor/process-watcher.js +162 -0
- package/dist/sensor/rbac.js +92 -0
- package/dist/sensor/redact.js +114 -0
- package/dist/sensor/redis-store.js +191 -0
- package/dist/sensor/route-reachability.js +64 -0
- package/dist/sensor/security-utils.js +18 -0
- package/dist/sensor/service-graph.js +170 -0
- package/dist/sensor/service-topology.js +227 -0
- package/dist/sensor/session-history.js +74 -0
- package/dist/sensor/session-persist.js +38 -0
- package/dist/sensor/shadow-promote.js +122 -0
- package/dist/sensor/sourcemap.js +281 -0
- package/dist/sensor/sqlite-store.js +205 -0
- package/dist/sensor/sso.js +164 -0
- package/dist/sensor/vitest-reporter.js +65 -0
- package/dist/sensor/watcher.js +48 -0
- package/dist/storage/interfaces.js +0 -0
- package/dist/storage/pg/pg-action-ledger.js +138 -0
- package/dist/storage/pg/pg-approval-store.js +107 -0
- package/dist/storage/pg/pg-blunder-store.js +193 -0
- package/dist/storage/pg/pg-ci-gate-history.js +83 -0
- package/dist/storage/pg/pg-client.js +24 -0
- package/dist/storage/pg/pg-event-store.js +63 -0
- package/dist/storage/pg/pg-incident-store.js +190 -0
- package/dist/storage/pg/pg-migrations.js +26 -0
- package/dist/storage/pg/pg-override-corpus.js +447 -0
- package/dist/storage/pg/pg-shadow-log.js +129 -0
- package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
- package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
- package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
- package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
- package/dist/storage/sqlite/sqlite-event-store.js +29 -0
- package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
- package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
- package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
- package/dist/storage/store-factory.js +55 -0
- package/dist/storage/store-registry.js +31 -0
- package/dist/update-checker.js +107 -0
- package/dist/workers/autopilot-worker.js +28 -0
- package/dist/workers/notification-worker.js +28 -0
- package/dist/workers/queues.js +58 -0
- package/dist/workers/validation-worker.js +24 -0
- package/dist/workers/worker-registry.js +18 -0
- package/package.json +123 -0
- package/sdk/mergen-inject.js +260 -0
- package/sdk/node.js +313 -0
- package/sdk/vite-plugin.ts +57 -0
- package/sdk/webpack-plugin.js +106 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createHmac } from "crypto";
|
|
2
|
+
import { Router } from "express";
|
|
3
|
+
import { getStores } from "../storage/store-registry.js";
|
|
4
|
+
import { getRefinementCandidates, getBypassStats } from "../sensor/bypass-tracker.js";
|
|
5
|
+
import { getGateCoverageSummary } from "../intelligence/enterprise-policy-engine.js";
|
|
6
|
+
import { buildReport, renderMarkdown } from "../intelligence/case-study-generator.js";
|
|
7
|
+
import { loadEnterprisePolicy } from "../intelligence/enterprise-policy-engine.js";
|
|
8
|
+
import { computeBlastRadius } from "../intelligence/blast-radius.js";
|
|
9
|
+
function createAgentBlundersRouter(localSecret) {
|
|
10
|
+
const router = Router();
|
|
11
|
+
router.get("/agent-blunders", async (req, res) => {
|
|
12
|
+
const limit = Math.min(100, Math.max(1, Number(req.query.limit ?? 20)));
|
|
13
|
+
const agentId = typeof req.query.agentId === "string" ? req.query.agentId : void 0;
|
|
14
|
+
const store = getStores().blunders;
|
|
15
|
+
const [stats, all] = await Promise.all([store.getStats(), store.list()]);
|
|
16
|
+
const filtered = agentId ? all.filter((b) => b.agentId === agentId) : all;
|
|
17
|
+
const recent = filtered.slice(-limit).reverse();
|
|
18
|
+
const policyRefinementCandidates = getRefinementCandidates();
|
|
19
|
+
const bypassStats = getBypassStats();
|
|
20
|
+
const gateCovers = getGateCoverageSummary();
|
|
21
|
+
res.json({
|
|
22
|
+
ok: true,
|
|
23
|
+
prevented: stats.total,
|
|
24
|
+
...stats,
|
|
25
|
+
gateCovers,
|
|
26
|
+
recentBlunders: recent,
|
|
27
|
+
bypassStats,
|
|
28
|
+
policyRefinementCandidates
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
router.get("/agent-blunders/verify", async (_req, res) => {
|
|
32
|
+
const result = await getStores().blunders.verifyChain();
|
|
33
|
+
let checkpoints;
|
|
34
|
+
try {
|
|
35
|
+
const { verifyCheckpoints } = await import("../sensor/agent-blunder-store.js");
|
|
36
|
+
checkpoints = verifyCheckpoints();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
res.json({ ok: true, ...result, ...checkpoints ? { checkpoints } : {} });
|
|
40
|
+
});
|
|
41
|
+
router.get("/agent-blunders/clusters", async (req, res) => {
|
|
42
|
+
const windowDays = Math.min(365, Math.max(1, Number(req.query.windowDays ?? 30)));
|
|
43
|
+
const minCount = Math.min(100, Math.max(1, Number(req.query.minCount ?? 2)));
|
|
44
|
+
const store = getStores().blunders;
|
|
45
|
+
const all = await store.list();
|
|
46
|
+
const cutoff = Date.now() - windowDays * 24 * 60 * 60 * 1e3;
|
|
47
|
+
const recent = all.filter((b) => b.recordedAt >= cutoff);
|
|
48
|
+
const clusterMap = /* @__PURE__ */ new Map();
|
|
49
|
+
for (const b of recent) {
|
|
50
|
+
const template = _commandTemplate(b.command ?? b.blockReason);
|
|
51
|
+
const key = `${b.blunderType}::${template}`;
|
|
52
|
+
let cluster = clusterMap.get(key);
|
|
53
|
+
if (!cluster) {
|
|
54
|
+
cluster = {
|
|
55
|
+
key,
|
|
56
|
+
blunderType: b.blunderType,
|
|
57
|
+
template,
|
|
58
|
+
count: 0,
|
|
59
|
+
agents: /* @__PURE__ */ new Set(),
|
|
60
|
+
services: /* @__PURE__ */ new Set(),
|
|
61
|
+
firstSeen: b.recordedAt,
|
|
62
|
+
lastSeen: b.recordedAt,
|
|
63
|
+
examples: [],
|
|
64
|
+
triggeredRules: []
|
|
65
|
+
};
|
|
66
|
+
clusterMap.set(key, cluster);
|
|
67
|
+
}
|
|
68
|
+
cluster.count++;
|
|
69
|
+
if (b.agentId) cluster.agents.add(b.agentId);
|
|
70
|
+
if (b.actor) cluster.agents.add(b.actor);
|
|
71
|
+
if (b.service) cluster.services.add(b.service);
|
|
72
|
+
if (b.recordedAt < cluster.firstSeen) cluster.firstSeen = b.recordedAt;
|
|
73
|
+
if (b.recordedAt > cluster.lastSeen) cluster.lastSeen = b.recordedAt;
|
|
74
|
+
if (cluster.examples.length < 3 && b.command) cluster.examples.push(b.command.slice(0, 120));
|
|
75
|
+
for (const r of b.triggeredRules ?? []) {
|
|
76
|
+
if (!cluster.triggeredRules.includes(r)) cluster.triggeredRules.push(r);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const clusters = [...clusterMap.values()].filter((c) => c.count >= minCount).sort((a, b) => b.count - a.count).map((c) => ({
|
|
80
|
+
key: c.key,
|
|
81
|
+
blunderType: c.blunderType,
|
|
82
|
+
template: c.template,
|
|
83
|
+
count: c.count,
|
|
84
|
+
uniqueAgents: c.agents.size,
|
|
85
|
+
uniqueServices: c.services.size,
|
|
86
|
+
firstSeen: c.firstSeen,
|
|
87
|
+
lastSeen: c.lastSeen,
|
|
88
|
+
examples: c.examples,
|
|
89
|
+
triggeredRules: c.triggeredRules,
|
|
90
|
+
threatSignal: _threatSignal(c.count, c.agents.size, windowDays)
|
|
91
|
+
}));
|
|
92
|
+
res.json({
|
|
93
|
+
ok: true,
|
|
94
|
+
windowDays,
|
|
95
|
+
totalBlunders: recent.length,
|
|
96
|
+
clustersFound: clusters.length,
|
|
97
|
+
clusters
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
router.get("/agent-blunders/evidence-pack", async (req, res) => {
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
const from = Number(req.query.from ?? now - 30 * 24 * 60 * 60 * 1e3);
|
|
103
|
+
const to = Number(req.query.to ?? now);
|
|
104
|
+
const agentId = typeof req.query.agentId === "string" ? req.query.agentId : void 0;
|
|
105
|
+
const sessionId = typeof req.query.sessionId === "string" ? req.query.sessionId : void 0;
|
|
106
|
+
const store = getStores().blunders;
|
|
107
|
+
const all = await store.list();
|
|
108
|
+
let entries = all.filter((b) => b.recordedAt >= from && b.recordedAt <= to);
|
|
109
|
+
if (agentId) entries = entries.filter((b) => b.agentId === agentId);
|
|
110
|
+
if (sessionId) entries = entries.filter((b) => b.sessionId === sessionId);
|
|
111
|
+
const policy = loadEnterprisePolicy();
|
|
112
|
+
const blastRadiusScores = entries.map((b) => ({
|
|
113
|
+
id: b.id,
|
|
114
|
+
blastRadius: b.command ? computeBlastRadius(b.command) : null
|
|
115
|
+
}));
|
|
116
|
+
const chainVerification = await store.verifyChain();
|
|
117
|
+
const pack = {
|
|
118
|
+
generatedAt: now,
|
|
119
|
+
rangeFrom: from,
|
|
120
|
+
rangeTo: to,
|
|
121
|
+
filters: { agentId: agentId ?? null, sessionId: sessionId ?? null },
|
|
122
|
+
entryCount: entries.length,
|
|
123
|
+
entries,
|
|
124
|
+
policySnapshot: policy,
|
|
125
|
+
blastRadiusScores,
|
|
126
|
+
chainVerification
|
|
127
|
+
};
|
|
128
|
+
const packJson = JSON.stringify(pack);
|
|
129
|
+
const hmacKey = process.env.MERGEN_SECRET || localSecret;
|
|
130
|
+
const signature = createHmac("sha256", hmacKey).update(packJson).digest("hex");
|
|
131
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
132
|
+
res.setHeader("Content-Disposition", `attachment; filename="mergen-evidence-pack-${now}.json"`);
|
|
133
|
+
res.json({ ok: true, pack, signature });
|
|
134
|
+
});
|
|
135
|
+
router.get("/agent-blunders/case-study-export", async (req, res) => {
|
|
136
|
+
const format = req.query.format === "md" ? "md" : "json";
|
|
137
|
+
const limit = Math.min(100, Math.max(1, Number(req.query.limit ?? 20)));
|
|
138
|
+
const blunders = await getStores().blunders.list();
|
|
139
|
+
const report = buildReport(blunders);
|
|
140
|
+
report.cases = report.cases.slice(0, limit);
|
|
141
|
+
if (format === "md") {
|
|
142
|
+
res.setHeader("Content-Type", "text/markdown; charset=utf-8");
|
|
143
|
+
res.setHeader("Content-Disposition", 'attachment; filename="mergen-case-studies.md"');
|
|
144
|
+
res.send(renderMarkdown(report));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
res.json({ ok: true, ...report });
|
|
148
|
+
});
|
|
149
|
+
return router;
|
|
150
|
+
}
|
|
151
|
+
function _commandTemplate(raw) {
|
|
152
|
+
return raw.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<uuid>").replace(/\b[0-9a-f]{32,64}\b/gi, "<hash>").replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, "<ip>").replace(/:\d{2,5}\b/g, ":<port>").replace(/\/[^\s"']{5,}/g, "/<path>").replace(/\b\d+\b/g, "<n>").replace(/\s+/g, " ").slice(0, 200).trim();
|
|
153
|
+
}
|
|
154
|
+
function _threatSignal(count, uniqueAgents, windowDays) {
|
|
155
|
+
const dailyRate = count / windowDays;
|
|
156
|
+
if (dailyRate >= 5 || uniqueAgents >= 4) return "critical";
|
|
157
|
+
if (dailyRate >= 2 || uniqueAgents >= 2) return "high";
|
|
158
|
+
if (count >= 3) return "medium";
|
|
159
|
+
return "low";
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
createAgentBlundersRouter
|
|
163
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
listProfiles,
|
|
5
|
+
getProfile,
|
|
6
|
+
saveProfile,
|
|
7
|
+
deleteProfile
|
|
8
|
+
} from "../intelligence/agent-profiles.js";
|
|
9
|
+
import { detectCredentialMisuse } from "../intelligence/credential-misuse.js";
|
|
10
|
+
import { getBaseline } from "../intelligence/behavior-baseline.js";
|
|
11
|
+
import { getAgentScrutinyTier } from "../intelligence/session-threat-tracker.js";
|
|
12
|
+
import { getStores } from "../storage/store-registry.js";
|
|
13
|
+
const ProfileSchema = z.object({
|
|
14
|
+
id: z.string().min(1).max(60).regex(/^[a-z0-9_-]+$/, "id must be lowercase alphanumeric, hyphens, underscores"),
|
|
15
|
+
name: z.string().min(1).max(80),
|
|
16
|
+
description: z.string().max(300).default(""),
|
|
17
|
+
allowedTools: z.array(z.string()).default([]),
|
|
18
|
+
blockedTools: z.array(z.string()).default([]),
|
|
19
|
+
allowedServices: z.array(z.string()).default([]),
|
|
20
|
+
maxRiskTier: z.enum(["read", "restart", "deploy", "full"]).default("restart")
|
|
21
|
+
});
|
|
22
|
+
function createAgentsRouter() {
|
|
23
|
+
const router = Router();
|
|
24
|
+
router.get("/agents", (_req, res) => {
|
|
25
|
+
const profiles = listProfiles();
|
|
26
|
+
const activeId = process.env.MERGEN_AGENT_ID ?? null;
|
|
27
|
+
res.json({ ok: true, profiles, activeId });
|
|
28
|
+
});
|
|
29
|
+
router.get("/agents/active", (_req, res) => {
|
|
30
|
+
const agentId = process.env.MERGEN_AGENT_ID;
|
|
31
|
+
if (!agentId) {
|
|
32
|
+
res.json({ ok: true, active: null, note: "MERGEN_AGENT_ID not set \u2014 no agent profile active" });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const profile = getProfile(agentId);
|
|
36
|
+
res.json({ ok: true, activeId: agentId, active: profile ?? null, registered: !!profile });
|
|
37
|
+
});
|
|
38
|
+
router.get("/agents/:id", (req, res) => {
|
|
39
|
+
const profile = getProfile(req.params.id);
|
|
40
|
+
if (!profile) {
|
|
41
|
+
res.status(404).json({ error: "Agent profile not found" });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
res.json({ ok: true, profile });
|
|
45
|
+
});
|
|
46
|
+
router.post("/agents", (req, res) => {
|
|
47
|
+
const parsed = ProfileSchema.safeParse(req.body);
|
|
48
|
+
if (!parsed.success) {
|
|
49
|
+
res.status(400).json({ error: parsed.error.issues });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (getProfile(parsed.data.id)) {
|
|
53
|
+
res.status(409).json({ error: `Agent '${parsed.data.id}' already exists` });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const profile = { ...parsed.data, createdAt: Date.now() };
|
|
57
|
+
saveProfile(profile);
|
|
58
|
+
res.status(201).json({ ok: true, profile });
|
|
59
|
+
});
|
|
60
|
+
router.patch("/agents/:id", (req, res) => {
|
|
61
|
+
const existing = getProfile(req.params.id);
|
|
62
|
+
if (!existing) {
|
|
63
|
+
res.status(404).json({ error: "Agent profile not found" });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const UpdateSchema = ProfileSchema.partial().omit({ id: true });
|
|
67
|
+
const parsed = UpdateSchema.safeParse(req.body);
|
|
68
|
+
if (!parsed.success) {
|
|
69
|
+
res.status(400).json({ error: parsed.error.issues });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const updated = { ...existing, ...parsed.data };
|
|
73
|
+
saveProfile(updated);
|
|
74
|
+
res.json({ ok: true, profile: updated });
|
|
75
|
+
});
|
|
76
|
+
router.delete("/agents/:id", (req, res) => {
|
|
77
|
+
if (!deleteProfile(req.params.id)) {
|
|
78
|
+
res.status(404).json({ error: "Agent profile not found" });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
res.json({ ok: true });
|
|
82
|
+
});
|
|
83
|
+
router.get("/agents/:id/timeline", async (req, res) => {
|
|
84
|
+
const agentId = req.params.id;
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
const from = Number(req.query.from ?? now - 24 * 60 * 60 * 1e3);
|
|
87
|
+
const to = Number(req.query.to ?? now);
|
|
88
|
+
const limit = Math.min(500, Math.max(1, Number(req.query.limit ?? 100)));
|
|
89
|
+
const gateEntries = (await getStores().actionLedger.query({ agentId, from, to, limit })).map((e) => ({
|
|
90
|
+
type: "gate",
|
|
91
|
+
ts: e.ts,
|
|
92
|
+
toolName: e.tool,
|
|
93
|
+
command: e.command,
|
|
94
|
+
verdict: e.verdict,
|
|
95
|
+
triggeredRules: e.policyMatched,
|
|
96
|
+
guidedAlternative: e.reason,
|
|
97
|
+
resource: e.resource,
|
|
98
|
+
credentialIssued: e.credentialIssued,
|
|
99
|
+
service: "mcp",
|
|
100
|
+
environment: e.environment
|
|
101
|
+
}));
|
|
102
|
+
const blunderEntries = (await getStores().blunders.list()).filter((b) => b.actor === agentId && b.recordedAt >= from && b.recordedAt <= to).map((b) => ({
|
|
103
|
+
type: "blunder",
|
|
104
|
+
ts: b.recordedAt,
|
|
105
|
+
toolName: null,
|
|
106
|
+
command: b.command,
|
|
107
|
+
verdict: "block",
|
|
108
|
+
blunderType: b.blunderType,
|
|
109
|
+
blockReason: b.blockReason,
|
|
110
|
+
service: b.service,
|
|
111
|
+
hash: b.hash
|
|
112
|
+
}));
|
|
113
|
+
const timeline = [...gateEntries, ...blunderEntries].sort((a, b) => b.ts - a.ts).slice(0, limit);
|
|
114
|
+
const profile = getProfile(agentId);
|
|
115
|
+
res.json({
|
|
116
|
+
ok: true,
|
|
117
|
+
agentId,
|
|
118
|
+
profile: profile ?? null,
|
|
119
|
+
from,
|
|
120
|
+
to,
|
|
121
|
+
total: timeline.length,
|
|
122
|
+
timeline
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
router.get("/agents/:id/ledger", async (req, res) => {
|
|
126
|
+
const agentId = req.params.id;
|
|
127
|
+
const verdict = req.query.verdict;
|
|
128
|
+
const tool = req.query.tool;
|
|
129
|
+
const from = req.query.from ? Number(req.query.from) : void 0;
|
|
130
|
+
const to = req.query.to ? Number(req.query.to) : void 0;
|
|
131
|
+
const limit = Math.min(1e3, Math.max(1, Number(req.query.limit ?? 100)));
|
|
132
|
+
const records = await getStores().actionLedger.query({ agentId, verdict, tool, from, to, limit });
|
|
133
|
+
res.json({ ok: true, agentId, total: records.length, records });
|
|
134
|
+
});
|
|
135
|
+
router.get("/agents/:id/report", async (req, res) => {
|
|
136
|
+
const agentId = req.params.id;
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const windowMs = Math.max(1, Number(req.query.windowDays ?? 30)) * 24 * 60 * 60 * 1e3;
|
|
139
|
+
const from = now - windowMs;
|
|
140
|
+
const records = await getStores().actionLedger.query({ agentId, from, to: now, limit: 5e3 });
|
|
141
|
+
const byVerdict = { pass: 0, hold: 0, block: 0 };
|
|
142
|
+
const byTool = {};
|
|
143
|
+
const byResource = {};
|
|
144
|
+
for (const r of records) {
|
|
145
|
+
byVerdict[r.verdict] = (byVerdict[r.verdict] ?? 0) + 1;
|
|
146
|
+
byTool[r.tool] = (byTool[r.tool] ?? 0) + 1;
|
|
147
|
+
if (r.resource) byResource[r.resource] = (byResource[r.resource] ?? 0) + 1;
|
|
148
|
+
}
|
|
149
|
+
const topResources = Object.entries(byResource).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([resource, count]) => ({ resource, count }));
|
|
150
|
+
const credRows = await getStores().actionLedger.queryCredentialUsage({ agentId, from, to: now });
|
|
151
|
+
const credentialUsage = {};
|
|
152
|
+
for (const c of credRows) {
|
|
153
|
+
const env = c.environment ?? "unknown";
|
|
154
|
+
const scope = c.credentialScope ?? "unknown";
|
|
155
|
+
(credentialUsage[scope] ??= []).includes(env) || credentialUsage[scope].push(env);
|
|
156
|
+
}
|
|
157
|
+
res.json({
|
|
158
|
+
ok: true,
|
|
159
|
+
agentId,
|
|
160
|
+
windowDays: windowMs / 864e5,
|
|
161
|
+
totalActions: records.length,
|
|
162
|
+
byVerdict,
|
|
163
|
+
topTools: Object.entries(byTool).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([tool, count]) => ({ tool, count })),
|
|
164
|
+
topResources,
|
|
165
|
+
credentialUsage,
|
|
166
|
+
credentialMisuse: await detectCredentialMisuse({ agentId, windowMs }),
|
|
167
|
+
anomalyBaseline: getBaseline(agentId),
|
|
168
|
+
scrutinyTier: getAgentScrutinyTier(agentId)
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
return router;
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
createAgentsRouter
|
|
175
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { createApiKey, listApiKeys, revokeApiKey, CLOUD_MODE } from "../sensor/cloud-auth.js";
|
|
3
|
+
function resolveCaller(req) {
|
|
4
|
+
return req.headers["x-mergen-member"] ?? (req.tenantId ?? "api-admin");
|
|
5
|
+
}
|
|
6
|
+
function createApiKeysRouter() {
|
|
7
|
+
const router = Router();
|
|
8
|
+
router.post("/api-keys", (req, res) => {
|
|
9
|
+
if (!CLOUD_MODE) {
|
|
10
|
+
res.status(404).json({ error: "cloud mode not enabled", fix: "set MERGEN_CLOUD_MODE=true" });
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const { tenantId, label, rateLimit, scope, expiresAt, createdBy } = req.body ?? {};
|
|
14
|
+
if (!tenantId || typeof tenantId !== "string") {
|
|
15
|
+
res.status(400).json({ ok: false, error: "tenantId (string) is required" });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!label || typeof label !== "string") {
|
|
19
|
+
res.status(400).json({ ok: false, error: "label (string) is required" });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (rateLimit !== void 0 && (typeof rateLimit !== "number" || rateLimit < 1)) {
|
|
23
|
+
res.status(400).json({ ok: false, error: "rateLimit must be a positive number" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (scope !== void 0 && (!Array.isArray(scope) || scope.some((s) => typeof s !== "string"))) {
|
|
27
|
+
res.status(400).json({ ok: false, error: 'scope must be an array of strings (e.g. ["ingest", "read"])' });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (expiresAt !== void 0 && isNaN(Date.parse(expiresAt))) {
|
|
31
|
+
res.status(400).json({ ok: false, error: "expiresAt must be a valid ISO 8601 date string" });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const caller = createdBy ?? resolveCaller(req);
|
|
35
|
+
const created = createApiKey({ tenantId, label, rateLimit, scope, expiresAt, createdBy: caller });
|
|
36
|
+
res.status(201).json({
|
|
37
|
+
ok: true,
|
|
38
|
+
...created,
|
|
39
|
+
note: "Store this key securely \u2014 it will not be shown again."
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
router.get("/api-keys", (_req, res) => {
|
|
43
|
+
if (!CLOUD_MODE) {
|
|
44
|
+
res.status(404).json({ error: "cloud mode not enabled" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
res.json({ ok: true, keys: listApiKeys() });
|
|
48
|
+
});
|
|
49
|
+
router.delete("/api-keys/:id", (req, res) => {
|
|
50
|
+
if (!CLOUD_MODE) {
|
|
51
|
+
res.status(404).json({ error: "cloud mode not enabled" });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const deleted = revokeApiKey(req.params.id);
|
|
55
|
+
if (!deleted) {
|
|
56
|
+
res.status(404).json({ ok: false, error: `no key with id: ${req.params.id}` });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
res.json({ ok: true });
|
|
60
|
+
});
|
|
61
|
+
return router;
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
createApiKeysRouter
|
|
65
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { checkBoundaries } from "../intelligence/arch-boundaries.js";
|
|
6
|
+
import { scoreChangeRisk } from "../intelligence/change-risk.js";
|
|
7
|
+
import { buildGraph, getZone } from "../intelligence/arch-graph.js";
|
|
8
|
+
import { critiqueImplementation } from "../intelligence/impl-critic.js";
|
|
9
|
+
function defaultSrcDir() {
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const candidates = [path.resolve(cwd, "src"), path.resolve(cwd, "server/src")];
|
|
12
|
+
for (const c of candidates) if (existsSync(c)) return c;
|
|
13
|
+
return path.resolve(cwd, "src");
|
|
14
|
+
}
|
|
15
|
+
const RiskBodySchema = z.object({
|
|
16
|
+
files: z.array(z.string()).min(1),
|
|
17
|
+
srcDir: z.string().optional()
|
|
18
|
+
});
|
|
19
|
+
const CritiqueBodySchema = z.object({
|
|
20
|
+
files: z.array(z.string()).min(1),
|
|
21
|
+
srcDir: z.string().optional()
|
|
22
|
+
});
|
|
23
|
+
function createArchRouter() {
|
|
24
|
+
const router = Router();
|
|
25
|
+
router.get("/arch/violations", (req, res) => {
|
|
26
|
+
const srcDir = typeof req.query.srcDir === "string" ? req.query.srcDir : defaultSrcDir();
|
|
27
|
+
const result = checkBoundaries({ srcDir });
|
|
28
|
+
res.json({ ok: true, violations: result.violations, filesChecked: result.filesChecked, cleanFiles: result.cleanFiles });
|
|
29
|
+
});
|
|
30
|
+
router.post("/arch/risk", (req, res) => {
|
|
31
|
+
const parsed = RiskBodySchema.safeParse(req.body);
|
|
32
|
+
if (!parsed.success) {
|
|
33
|
+
res.status(400).json({ error: "validation failed", details: parsed.error.issues });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const { files, srcDir = defaultSrcDir() } = parsed.data;
|
|
37
|
+
const absoluteFiles = files.map((f) => path.isAbsolute(f) ? f : path.resolve(srcDir, f));
|
|
38
|
+
const report = scoreChangeRisk(absoluteFiles, srcDir);
|
|
39
|
+
res.json({ ok: true, report });
|
|
40
|
+
});
|
|
41
|
+
router.get("/arch/graph", (req, res) => {
|
|
42
|
+
const srcDir = typeof req.query.srcDir === "string" ? req.query.srcDir : defaultSrcDir();
|
|
43
|
+
const graph = buildGraph(srcDir);
|
|
44
|
+
const zonePairs = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const [file, imports] of graph.forward) {
|
|
46
|
+
const fromZone = getZone(file);
|
|
47
|
+
for (const imp of imports) {
|
|
48
|
+
const toZone = getZone(imp);
|
|
49
|
+
const key = `${fromZone}\u2192${toZone}`;
|
|
50
|
+
zonePairs.set(key, (zonePairs.get(key) ?? 0) + 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
res.json({
|
|
54
|
+
ok: true,
|
|
55
|
+
files: graph.files.length,
|
|
56
|
+
builtAt: new Date(graph.builtAt).toISOString(),
|
|
57
|
+
zoneSummary: Object.fromEntries(zonePairs)
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
router.post("/arch/critique", (req, res) => {
|
|
61
|
+
const parsed = CritiqueBodySchema.safeParse(req.body);
|
|
62
|
+
if (!parsed.success) {
|
|
63
|
+
res.status(400).json({ error: "validation failed", details: parsed.error.issues });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const { files, srcDir = defaultSrcDir() } = parsed.data;
|
|
67
|
+
const absoluteFiles = files.map((f) => path.isAbsolute(f) ? f : path.resolve(srcDir, f));
|
|
68
|
+
const report = critiqueImplementation({ files: absoluteFiles, srcDir });
|
|
69
|
+
res.json({ ok: true, report });
|
|
70
|
+
});
|
|
71
|
+
return router;
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
createArchRouter
|
|
75
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { fetchBlunderEntries, fetchHttpAuditEntries, fetchChainVerification } from "../sensor/audit-fetch.js";
|
|
3
|
+
function createAuditExportRouter() {
|
|
4
|
+
const router = Router();
|
|
5
|
+
router.get("/audit/export", async (req, res) => {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
const format = (req.query.format ?? "ndjson").toLowerCase();
|
|
8
|
+
const type = format === "siem" ? "blunders" : (req.query.type ?? "all").toLowerCase();
|
|
9
|
+
const from = Number(req.query.from ?? now - 30 * 24 * 60 * 60 * 1e3);
|
|
10
|
+
const to = Number(req.query.to ?? now);
|
|
11
|
+
const limit = Math.min(5e4, Math.max(1, Number(req.query.limit ?? 5e3)));
|
|
12
|
+
const lines = [];
|
|
13
|
+
if (type === "blunders" || type === "all") {
|
|
14
|
+
const blunders = await fetchBlunderEntries(from, to);
|
|
15
|
+
for (const b of blunders) {
|
|
16
|
+
lines.push(JSON.stringify({
|
|
17
|
+
source: "blunder_log",
|
|
18
|
+
ts: new Date(b.recordedAt).toISOString(),
|
|
19
|
+
ts_ms: b.recordedAt,
|
|
20
|
+
id: b.id,
|
|
21
|
+
actor: b.actor ?? "agent",
|
|
22
|
+
blunderType: b.blunderType,
|
|
23
|
+
tool: b.command ?? null,
|
|
24
|
+
blockReason: b.blockReason,
|
|
25
|
+
service: b.service ?? null,
|
|
26
|
+
tag: b.tag ?? null,
|
|
27
|
+
pid: b.pid ?? null,
|
|
28
|
+
gateDecision: "block",
|
|
29
|
+
previousHash: b.previousHash,
|
|
30
|
+
hash: b.hash
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (type === "http" || type === "all") {
|
|
35
|
+
const httpEntries = await fetchHttpAuditEntries(from, to);
|
|
36
|
+
for (const e of httpEntries) {
|
|
37
|
+
lines.push(JSON.stringify({
|
|
38
|
+
source: "http_audit",
|
|
39
|
+
ts: e.ts,
|
|
40
|
+
ts_ms: new Date(e.ts).getTime(),
|
|
41
|
+
actor: e.actor,
|
|
42
|
+
method: e.method,
|
|
43
|
+
path: e.path,
|
|
44
|
+
status: e.status,
|
|
45
|
+
durationMs: e.durationMs,
|
|
46
|
+
ip: e.ip
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const trimmed = lines.slice(0, limit);
|
|
51
|
+
if (format === "soc2") {
|
|
52
|
+
const chainVerification = await fetchChainVerification();
|
|
53
|
+
const header = JSON.stringify({
|
|
54
|
+
source: "__export_header__",
|
|
55
|
+
exportFormat: "mergen-soc2-v1",
|
|
56
|
+
exportedAt: new Date(now).toISOString(),
|
|
57
|
+
windowFrom: new Date(from).toISOString(),
|
|
58
|
+
windowTo: new Date(to).toISOString(),
|
|
59
|
+
entryCount: trimmed.length,
|
|
60
|
+
limitApplied: lines.length > limit,
|
|
61
|
+
chainValid: chainVerification.valid,
|
|
62
|
+
chainVerified: chainVerification.verified ?? 0,
|
|
63
|
+
chainTruncated: chainVerification.truncated ?? false,
|
|
64
|
+
// What guarantee actually applies given this deployment's configuration —
|
|
65
|
+
// not a blanket "tamper-evident" claim regardless of setup. See
|
|
66
|
+
// agent-blunder-store.ts's tamperEvidenceLevel() for the precise semantics.
|
|
67
|
+
tamperEvidenceLevel: chainVerification.tamperEvidenceLevel ?? "unknown",
|
|
68
|
+
hmacProtected: chainVerification.hmacProtected ?? false,
|
|
69
|
+
note: 'Verify blunder log integrity: SHA-256(previousHash + JSON(fields)) === hash for each entry. Chain starts from the genesis hash (64 zeros) or the oldest surviving entry when the ring buffer has wrapped. tamperEvidenceLevel "hash-chain" (not "hmac-sealed") means an attacker with the same local filesystem access as the Mergen process could re-link the chain around a deletion \u2014 set MERGEN_AUDIT_SECRET for hmac-sealed protection.'
|
|
70
|
+
});
|
|
71
|
+
res.setHeader("Content-Type", "application/x-ndjson");
|
|
72
|
+
res.setHeader("Content-Disposition", `attachment; filename="mergen-audit-soc2-${new Date(now).toISOString().slice(0, 10)}.ndjson"`);
|
|
73
|
+
res.setHeader("Cache-Control", "no-store");
|
|
74
|
+
res.send([header, ...trimmed].join("\n") + "\n");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (format === "siem") {
|
|
78
|
+
res.setHeader("Content-Type", "application/x-ndjson");
|
|
79
|
+
res.setHeader("Content-Disposition", `attachment; filename="mergen-siem-${new Date(now).toISOString().slice(0, 10)}.ndjson"`);
|
|
80
|
+
res.setHeader("Cache-Control", "no-store");
|
|
81
|
+
res.send(trimmed.join("\n") + "\n");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
res.setHeader("Content-Type", "application/x-ndjson");
|
|
85
|
+
res.setHeader("Content-Disposition", `attachment; filename="mergen-audit-${new Date(now).toISOString().slice(0, 10)}.ndjson"`);
|
|
86
|
+
res.setHeader("Cache-Control", "no-store");
|
|
87
|
+
res.send(trimmed.join("\n") + "\n");
|
|
88
|
+
});
|
|
89
|
+
return router;
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
createAuditExportRouter
|
|
93
|
+
};
|