@quint-security/cli 0.1.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/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +87 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/http-proxy.d.ts +3 -0
- package/dist/commands/http-proxy.d.ts.map +1 -0
- package/dist/commands/http-proxy.js +35 -0
- package/dist/commands/http-proxy.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +343 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/keys.d.ts +3 -0
- package/dist/commands/keys.d.ts.map +1 -0
- package/dist/commands/keys.js +58 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/logs.d.ts +3 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +50 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/policy.d.ts +3 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +39 -0
- package/dist/commands/policy.js.map +1 -0
- package/dist/commands/proxy.d.ts +3 -0
- package/dist/commands/proxy.d.ts.map +1 -0
- package/dist/commands/proxy.js +24 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +46 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +104 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/package.json +27 -0
- package/src/commands/auth.ts +103 -0
- package/src/commands/http-proxy.ts +34 -0
- package/src/commands/init.ts +408 -0
- package/src/commands/keys.ts +71 -0
- package/src/commands/logs.ts +59 -0
- package/src/commands/policy.ts +39 -0
- package/src/commands/proxy.ts +22 -0
- package/src/commands/status.ts +51 -0
- package/src/commands/verify.ts +117 -0
- package/src/index.ts +29 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import {
|
|
3
|
+
loadPolicy,
|
|
4
|
+
resolveDataDir,
|
|
5
|
+
openAuditDb,
|
|
6
|
+
verifySignature,
|
|
7
|
+
canonicalize,
|
|
8
|
+
sha256,
|
|
9
|
+
type AuditEntry,
|
|
10
|
+
} from "@quint-security/core";
|
|
11
|
+
|
|
12
|
+
export const verifyCommand = new Command("verify")
|
|
13
|
+
.description("Verify Ed25519 signatures and hash chain on audit log entries")
|
|
14
|
+
.option("--id <n>", "Verify a specific entry by ID")
|
|
15
|
+
.option("--last <n>", "Verify the last N entries", "20")
|
|
16
|
+
.option("--all", "Verify all entries")
|
|
17
|
+
.option("--chain", "Also verify hash chain integrity (requires --all or --last)")
|
|
18
|
+
.action((opts: { id?: string; last?: string; all?: boolean; chain?: boolean }) => {
|
|
19
|
+
const policy = loadPolicy();
|
|
20
|
+
const dataDir = resolveDataDir(policy.data_dir);
|
|
21
|
+
const db = openAuditDb(dataDir);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
let entries: AuditEntry[];
|
|
25
|
+
|
|
26
|
+
if (opts.id) {
|
|
27
|
+
const entry = db.getById(parseInt(opts.id, 10));
|
|
28
|
+
entries = entry ? [entry] : [];
|
|
29
|
+
} else if (opts.all) {
|
|
30
|
+
entries = db.getAll(); // ascending order for chain verification
|
|
31
|
+
} else {
|
|
32
|
+
entries = db.getLast(parseInt(opts.last!, 10));
|
|
33
|
+
// Reverse to ascending for chain verification
|
|
34
|
+
entries.reverse();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (entries.length === 0) {
|
|
38
|
+
console.log("No entries to verify.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Signature verification
|
|
43
|
+
let sigValid = 0;
|
|
44
|
+
let sigInvalid = 0;
|
|
45
|
+
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const ok = verifyEntry(entry);
|
|
48
|
+
if (ok) {
|
|
49
|
+
sigValid++;
|
|
50
|
+
} else {
|
|
51
|
+
sigInvalid++;
|
|
52
|
+
console.log(` ✗ INVALID signature on entry #${entry.id} (${entry.timestamp})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`\nSignatures: ${entries.length} checked, ${sigValid} valid, ${sigInvalid} invalid`);
|
|
57
|
+
|
|
58
|
+
// Hash chain verification
|
|
59
|
+
if (opts.chain || opts.all) {
|
|
60
|
+
let chainValid = 0;
|
|
61
|
+
let chainBroken = 0;
|
|
62
|
+
|
|
63
|
+
for (let i = 1; i < entries.length; i++) {
|
|
64
|
+
const prev = entries[i - 1];
|
|
65
|
+
const curr = entries[i];
|
|
66
|
+
|
|
67
|
+
if (curr.prev_hash === "" && prev.prev_hash === "") {
|
|
68
|
+
// Legacy entries without hash chain — skip
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const expectedHash = sha256(prev.signature);
|
|
73
|
+
if (curr.prev_hash === expectedHash) {
|
|
74
|
+
chainValid++;
|
|
75
|
+
} else {
|
|
76
|
+
chainBroken++;
|
|
77
|
+
console.log(` ⛓ BROKEN chain at entry #${curr.id} — prev_hash doesn't match entry #${prev.id}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (chainValid + chainBroken > 0) {
|
|
82
|
+
console.log(`Chain: ${chainValid + chainBroken} links checked, ${chainValid} valid, ${chainBroken} broken`);
|
|
83
|
+
} else {
|
|
84
|
+
console.log(`Chain: no chain data (legacy entries)`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (sigInvalid > 0) {
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
db.close();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function verifyEntry(entry: AuditEntry): boolean {
|
|
97
|
+
const signable: Record<string, unknown> = {
|
|
98
|
+
timestamp: entry.timestamp,
|
|
99
|
+
server_name: entry.server_name,
|
|
100
|
+
direction: entry.direction,
|
|
101
|
+
method: entry.method,
|
|
102
|
+
message_id: entry.message_id,
|
|
103
|
+
tool_name: entry.tool_name,
|
|
104
|
+
arguments_json: entry.arguments_json,
|
|
105
|
+
response_json: entry.response_json,
|
|
106
|
+
verdict: entry.verdict,
|
|
107
|
+
risk_score: entry.risk_score ?? null,
|
|
108
|
+
risk_level: entry.risk_level ?? null,
|
|
109
|
+
policy_hash: entry.policy_hash ?? "",
|
|
110
|
+
prev_hash: entry.prev_hash ?? "",
|
|
111
|
+
nonce: entry.nonce ?? "",
|
|
112
|
+
public_key: entry.public_key,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const canonical = canonicalize(signable);
|
|
116
|
+
return verifySignature(canonical, entry.signature, entry.public_key);
|
|
117
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { proxyCommand } from "./commands/proxy.js";
|
|
5
|
+
import { httpProxyCommand } from "./commands/http-proxy.js";
|
|
6
|
+
import { logsCommand } from "./commands/logs.js";
|
|
7
|
+
import { keysCommand } from "./commands/keys.js";
|
|
8
|
+
import { verifyCommand } from "./commands/verify.js";
|
|
9
|
+
import { policyCommand } from "./commands/policy.js";
|
|
10
|
+
import { statusCommand } from "./commands/status.js";
|
|
11
|
+
import { authCommand } from "./commands/auth.js";
|
|
12
|
+
import { initCommand } from "./commands/init.js";
|
|
13
|
+
|
|
14
|
+
const program = new Command()
|
|
15
|
+
.name("quint")
|
|
16
|
+
.version("0.1.0")
|
|
17
|
+
.description("Local security proxy for MCP tool calls");
|
|
18
|
+
|
|
19
|
+
program.addCommand(initCommand);
|
|
20
|
+
program.addCommand(proxyCommand);
|
|
21
|
+
program.addCommand(httpProxyCommand);
|
|
22
|
+
program.addCommand(logsCommand);
|
|
23
|
+
program.addCommand(keysCommand);
|
|
24
|
+
program.addCommand(verifyCommand);
|
|
25
|
+
program.addCommand(policyCommand);
|
|
26
|
+
program.addCommand(statusCommand);
|
|
27
|
+
program.addCommand(authCommand);
|
|
28
|
+
|
|
29
|
+
program.parse();
|