protect-mcp 0.3.0 → 0.3.2
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 +11 -1
- package/dist/bundle-TXOTFJIJ.mjs +8 -0
- package/dist/chunk-5JXFV37Y.mjs +53 -0
- package/dist/{chunk-3WCA7O4D.mjs → chunk-GV7N53QE.mjs} +300 -11
- package/dist/chunk-JQDVKZBN.mjs +165 -0
- package/dist/cli.js +1180 -38
- package/dist/cli.mjs +640 -11
- package/dist/index.d.mts +156 -2
- package/dist/index.d.ts +156 -2
- package/dist/index.js +466 -19
- package/dist/index.mjs +17 -50
- package/dist/report-ENQ3KUI2.mjs +8 -0
- package/package.json +3 -3
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/report.ts
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
function generateReport(logPath, receiptPath, periodDays) {
|
|
4
|
+
const now = /* @__PURE__ */ new Date();
|
|
5
|
+
const from = new Date(now.getTime() - periodDays * 864e5);
|
|
6
|
+
const entries = [];
|
|
7
|
+
if (existsSync(logPath)) {
|
|
8
|
+
const raw = readFileSync(logPath, "utf-8");
|
|
9
|
+
for (const line of raw.split("\n")) {
|
|
10
|
+
const trimmed = line.trim();
|
|
11
|
+
if (!trimmed) continue;
|
|
12
|
+
const jsonStr = trimmed.replace(/^\[PROTECT_MCP\]\s*/, "");
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(jsonStr);
|
|
15
|
+
if (parsed.tool && parsed.decision && parsed.timestamp) {
|
|
16
|
+
const entryTime = typeof parsed.timestamp === "number" && parsed.timestamp > 1e12 ? parsed.timestamp : parsed.timestamp * 1e3;
|
|
17
|
+
if (entryTime >= from.getTime()) {
|
|
18
|
+
entries.push(parsed);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
let receiptsSigned = 0;
|
|
26
|
+
let signerKid = "";
|
|
27
|
+
let signerIssuer = "";
|
|
28
|
+
if (existsSync(receiptPath)) {
|
|
29
|
+
const raw = readFileSync(receiptPath, "utf-8");
|
|
30
|
+
for (const line of raw.split("\n")) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed) continue;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(trimmed);
|
|
35
|
+
if (parsed.signature) {
|
|
36
|
+
receiptsSigned++;
|
|
37
|
+
if (parsed.kid && !signerKid) signerKid = parsed.kid;
|
|
38
|
+
if (parsed.issuer && !signerIssuer) signerIssuer = parsed.issuer;
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
45
|
+
const tiers = /* @__PURE__ */ new Set();
|
|
46
|
+
const policyDigests = /* @__PURE__ */ new Map();
|
|
47
|
+
let allowed = 0;
|
|
48
|
+
let blocked = 0;
|
|
49
|
+
let rateLimited = 0;
|
|
50
|
+
let approvalRequired = 0;
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const tool = entry.tool;
|
|
53
|
+
if (!toolMap.has(tool)) {
|
|
54
|
+
toolMap.set(tool, { total: 0, allowed: 0, blocked: 0, rate_limited: 0, approval_required: 0 });
|
|
55
|
+
}
|
|
56
|
+
const tm = toolMap.get(tool);
|
|
57
|
+
tm.total++;
|
|
58
|
+
if (entry.decision === "allow") {
|
|
59
|
+
allowed++;
|
|
60
|
+
tm.allowed++;
|
|
61
|
+
} else if (entry.decision === "deny" && entry.reason_code === "rate_limit_exceeded") {
|
|
62
|
+
rateLimited++;
|
|
63
|
+
tm.rate_limited++;
|
|
64
|
+
} else if (entry.decision === "deny" && entry.reason_code === "require_approval") {
|
|
65
|
+
approvalRequired++;
|
|
66
|
+
tm.approval_required++;
|
|
67
|
+
} else {
|
|
68
|
+
blocked++;
|
|
69
|
+
tm.blocked++;
|
|
70
|
+
}
|
|
71
|
+
if (entry.tier) tiers.add(entry.tier);
|
|
72
|
+
if (entry.policy_digest && !policyDigests.has(entry.policy_digest)) {
|
|
73
|
+
policyDigests.set(entry.policy_digest, new Date(entry.timestamp).toISOString());
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const policyChanges = Array.from(policyDigests.entries()).map(([digest, at]) => ({
|
|
77
|
+
at,
|
|
78
|
+
policy_digest: digest
|
|
79
|
+
})).sort((a, b) => a.at.localeCompare(b.at));
|
|
80
|
+
return {
|
|
81
|
+
generated_at: now.toISOString(),
|
|
82
|
+
period: { from: from.toISOString(), to: now.toISOString() },
|
|
83
|
+
signing_identity: signerKid ? { kid: signerKid, issuer: signerIssuer } : null,
|
|
84
|
+
summary: {
|
|
85
|
+
total_decisions: entries.length,
|
|
86
|
+
allowed,
|
|
87
|
+
blocked,
|
|
88
|
+
rate_limited: rateLimited,
|
|
89
|
+
approval_required: approvalRequired,
|
|
90
|
+
unique_tools: toolMap.size,
|
|
91
|
+
unique_tiers: tiers.size
|
|
92
|
+
},
|
|
93
|
+
tool_breakdown: Array.from(toolMap.entries()).map(([tool, stats]) => ({ tool, ...stats })).sort((a, b) => b.total - a.total),
|
|
94
|
+
policy_changes: policyChanges,
|
|
95
|
+
verification: {
|
|
96
|
+
receipts_signed: receiptsSigned,
|
|
97
|
+
receipts_unsigned: entries.length - receiptsSigned,
|
|
98
|
+
verify_command: "npx @veritasacta/verify audit-bundle.json --bundle"
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function formatReportMarkdown(report) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push("# ScopeBlind Compliance Report");
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push(`**Generated:** ${report.generated_at}`);
|
|
107
|
+
lines.push(`**Period:** ${report.period.from.split("T")[0]} to ${report.period.to.split("T")[0]}`);
|
|
108
|
+
if (report.signing_identity) {
|
|
109
|
+
lines.push(`**Signing identity:** kid \`${report.signing_identity.kid}\`, issuer \`${report.signing_identity.issuer}\``);
|
|
110
|
+
}
|
|
111
|
+
lines.push("");
|
|
112
|
+
lines.push("## Summary");
|
|
113
|
+
lines.push("");
|
|
114
|
+
lines.push(`| Metric | Value |`);
|
|
115
|
+
lines.push(`|--------|-------|`);
|
|
116
|
+
lines.push(`| Total decisions | ${report.summary.total_decisions} |`);
|
|
117
|
+
lines.push(`| Allowed | ${report.summary.allowed} |`);
|
|
118
|
+
lines.push(`| Blocked | ${report.summary.blocked} |`);
|
|
119
|
+
lines.push(`| Rate-limited | ${report.summary.rate_limited} |`);
|
|
120
|
+
lines.push(`| Approval required | ${report.summary.approval_required} |`);
|
|
121
|
+
lines.push(`| Unique tools | ${report.summary.unique_tools} |`);
|
|
122
|
+
lines.push(`| Unique tiers | ${report.summary.unique_tiers} |`);
|
|
123
|
+
lines.push("");
|
|
124
|
+
if (report.tool_breakdown.length > 0) {
|
|
125
|
+
lines.push("## Tool Breakdown");
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push("| Tool | Total | Allowed | Blocked | Rate-limited | Approval |");
|
|
128
|
+
lines.push("|------|-------|---------|---------|--------------|----------|");
|
|
129
|
+
for (const t of report.tool_breakdown) {
|
|
130
|
+
lines.push(`| \`${t.tool}\` | ${t.total} | ${t.allowed} | ${t.blocked} | ${t.rate_limited} | ${t.approval_required} |`);
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
}
|
|
134
|
+
if (report.policy_changes.length > 0) {
|
|
135
|
+
lines.push("## Policy History");
|
|
136
|
+
lines.push("");
|
|
137
|
+
lines.push("| Timestamp | Policy Digest |");
|
|
138
|
+
lines.push("|-----------|--------------|");
|
|
139
|
+
for (const pc of report.policy_changes) {
|
|
140
|
+
lines.push(`| ${pc.at} | \`${pc.policy_digest}\` |`);
|
|
141
|
+
}
|
|
142
|
+
lines.push("");
|
|
143
|
+
}
|
|
144
|
+
lines.push("## Verification");
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push(`- Receipts signed: **${report.verification.receipts_signed}**`);
|
|
147
|
+
lines.push(`- Receipts unsigned: **${report.verification.receipts_unsigned}**`);
|
|
148
|
+
lines.push("");
|
|
149
|
+
lines.push("Verify the audit bundle:");
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push("```bash");
|
|
152
|
+
lines.push(report.verification.verify_command);
|
|
153
|
+
lines.push("```");
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push("The verifier is MIT-licensed and works offline. No ScopeBlind account required.");
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push("---");
|
|
158
|
+
lines.push("*Generated by protect-mcp \xB7 scopeblind.com*");
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export {
|
|
163
|
+
generateReport,
|
|
164
|
+
formatReportMarkdown
|
|
165
|
+
};
|