@praxis.guard/auditor-cli 0.0.30 → 0.0.33
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/hooks/before-mcp-argv.d.ts +17 -0
- package/dist/hooks/before-mcp-argv.d.ts.map +1 -0
- package/dist/hooks/before-mcp-argv.js +67 -0
- package/dist/hooks/before-mcp-argv.js.map +1 -0
- package/dist/hooks/before-mcp-mutate.d.ts +23 -0
- package/dist/hooks/before-mcp-mutate.d.ts.map +1 -0
- package/dist/hooks/before-mcp-mutate.js +76 -0
- package/dist/hooks/before-mcp-mutate.js.map +1 -0
- package/dist/hooks/before-mcp-skipped.d.ts +14 -0
- package/dist/hooks/before-mcp-skipped.d.ts.map +1 -0
- package/dist/hooks/before-mcp-skipped.js +56 -0
- package/dist/hooks/before-mcp-skipped.js.map +1 -0
- package/dist/hooks/before-mcp-types.d.ts +15 -0
- package/dist/hooks/before-mcp-types.d.ts.map +1 -0
- package/dist/hooks/before-mcp-types.js +2 -0
- package/dist/hooks/before-mcp-types.js.map +1 -0
- package/dist/hooks/run-before-mcp.d.ts +3 -27
- package/dist/hooks/run-before-mcp.d.ts.map +1 -1
- package/dist/hooks/run-before-mcp.js +57 -195
- package/dist/hooks/run-before-mcp.js.map +1 -1
- package/dist/mcp/evaluate-guard.d.ts +11 -0
- package/dist/mcp/evaluate-guard.d.ts.map +1 -0
- package/dist/mcp/evaluate-guard.js +148 -0
- package/dist/mcp/evaluate-guard.js.map +1 -0
- package/dist/mcp/guard-approval-block.d.ts +26 -0
- package/dist/mcp/guard-approval-block.d.ts.map +1 -0
- package/dist/mcp/guard-approval-block.js +154 -0
- package/dist/mcp/guard-approval-block.js.map +1 -0
- package/dist/mcp/guard-heartbeat.d.ts +6 -0
- package/dist/mcp/guard-heartbeat.d.ts.map +1 -0
- package/dist/mcp/guard-heartbeat.js +68 -0
- package/dist/mcp/guard-heartbeat.js.map +1 -0
- package/dist/mcp/guard-schemas.d.ts +42 -0
- package/dist/mcp/guard-schemas.d.ts.map +1 -0
- package/dist/mcp/guard-schemas.js +39 -0
- package/dist/mcp/guard-schemas.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +4 -327
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BeforeMCPExecutionPayload } from "./before-mcp-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* When Cursor encodes MCP tools as `MCP:<server>:<tool>` (see Cursor hooks docs / preToolUse), split into
|
|
4
|
+
* server + bare tool name for policy rows under `policies.mcp.<server>.<tool>`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function splitMcpToolName(raw: string): {
|
|
7
|
+
serverGuess: string | null;
|
|
8
|
+
tool: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Maps hook payload → argv for `policies.v1.json` under tool key `mcp`.
|
|
12
|
+
* Omits raw `tool_input` from argv tokens so JSON metacharacters do not trip shell metachar heuristics.
|
|
13
|
+
*/
|
|
14
|
+
export declare function mcpHookArgvFromPayload(payload: BeforeMCPExecutionPayload): string[];
|
|
15
|
+
export declare function stringifyToolInput(raw: unknown): string;
|
|
16
|
+
export declare function preferredHookCwd(payload: BeforeMCPExecutionPayload): string | undefined;
|
|
17
|
+
//# sourceMappingURL=before-mcp-argv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-argv.d.ts","sourceRoot":"","sources":["../../src/hooks/before-mcp-argv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAEvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAa1F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,EAAE,CAkBnF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQvD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,GAAG,SAAS,CASvF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When Cursor encodes MCP tools as `MCP:<server>:<tool>` (see Cursor hooks docs / preToolUse), split into
|
|
3
|
+
* server + bare tool name for policy rows under `policies.mcp.<server>.<tool>`.
|
|
4
|
+
*/
|
|
5
|
+
export function splitMcpToolName(raw) {
|
|
6
|
+
const t = raw.trim();
|
|
7
|
+
if (!t)
|
|
8
|
+
return { serverGuess: null, tool: "_" };
|
|
9
|
+
if (t.startsWith("MCP:")) {
|
|
10
|
+
const body = t.slice(4).trim();
|
|
11
|
+
const idx = body.lastIndexOf(":");
|
|
12
|
+
if (idx !== -1) {
|
|
13
|
+
const serverPart = body.slice(0, idx).trim();
|
|
14
|
+
const toolPart = body.slice(idx + 1).trim();
|
|
15
|
+
if (serverPart && toolPart)
|
|
16
|
+
return { serverGuess: serverPart, tool: toolPart };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { serverGuess: null, tool: t };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Maps hook payload → argv for `policies.v1.json` under tool key `mcp`.
|
|
23
|
+
* Omits raw `tool_input` from argv tokens so JSON metacharacters do not trip shell metachar heuristics.
|
|
24
|
+
*/
|
|
25
|
+
export function mcpHookArgvFromPayload(payload) {
|
|
26
|
+
const rawName = typeof payload.tool_name === "string" ? payload.tool_name.trim() : "";
|
|
27
|
+
const { serverGuess, tool } = splitMcpToolName(rawName);
|
|
28
|
+
let server = "stdio";
|
|
29
|
+
if (typeof payload.url === "string" && payload.url.trim()) {
|
|
30
|
+
const u = payload.url.trim();
|
|
31
|
+
try {
|
|
32
|
+
server = new URL(u).host || u;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
server = u;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (serverGuess) {
|
|
39
|
+
server = serverGuess;
|
|
40
|
+
}
|
|
41
|
+
else if (typeof payload.command === "string" && payload.command.trim()) {
|
|
42
|
+
server = payload.command.trim().slice(0, 400);
|
|
43
|
+
}
|
|
44
|
+
return ["mcp", server, tool || "_"];
|
|
45
|
+
}
|
|
46
|
+
export function stringifyToolInput(raw) {
|
|
47
|
+
if (raw === undefined || raw === null)
|
|
48
|
+
return "";
|
|
49
|
+
if (typeof raw === "string")
|
|
50
|
+
return raw;
|
|
51
|
+
try {
|
|
52
|
+
return JSON.stringify(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return String(raw);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function preferredHookCwd(payload) {
|
|
59
|
+
if (typeof payload.cwd === "string")
|
|
60
|
+
return payload.cwd;
|
|
61
|
+
if (Array.isArray(payload.workspace_roots) &&
|
|
62
|
+
typeof payload.workspace_roots[0] === "string") {
|
|
63
|
+
return payload.workspace_roots[0];
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=before-mcp-argv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-argv.js","sourceRoot":"","sources":["../../src/hooks/before-mcp-argv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Tier } from "../policy/index.js";
|
|
2
|
+
import type { BeforeMCPExecutionResponse } from "./before-mcp-types.js";
|
|
3
|
+
export type MutateHookPermission = {
|
|
4
|
+
permission: BeforeMCPExecutionResponse["permission"];
|
|
5
|
+
ticketConsumed: boolean;
|
|
6
|
+
inlineApproval: {
|
|
7
|
+
request_id: string;
|
|
8
|
+
open_url: string;
|
|
9
|
+
} | null;
|
|
10
|
+
approvalFlowSignal: string | null;
|
|
11
|
+
reasons: string[];
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveMutateHookPermission(input: {
|
|
14
|
+
argv: string[];
|
|
15
|
+
tier: Tier;
|
|
16
|
+
storageRoot: string;
|
|
17
|
+
toolInputHash: string | null;
|
|
18
|
+
rawToolName: string;
|
|
19
|
+
toolInputPreview: string;
|
|
20
|
+
policyRevision: number | null;
|
|
21
|
+
initialReasons: string[];
|
|
22
|
+
}): Promise<MutateHookPermission>;
|
|
23
|
+
//# sourceMappingURL=before-mcp-mutate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-mutate.d.ts","sourceRoot":"","sources":["../../src/hooks/before-mcp-mutate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAM/C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAExE,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,EAAE,0BAA0B,CAAC,YAAY,CAAC,CAAC;IACrD,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAsB,2BAA2B,CAAC,KAAK,EAAE;IACvD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAmFhC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { argvSha256 } from "../approval/argv-fingerprint.js";
|
|
3
|
+
import { resolveMutateApproval } from "../approval/mcp-flow.js";
|
|
4
|
+
import { tryHookInlineApprovalRequest } from "../approval/hook-inline-approval.js";
|
|
5
|
+
import { readPendingApprovalIndex } from "../bridge/pending-approval-index.js";
|
|
6
|
+
import { tryConsumeExecutionTicket } from "../bridge/execution-ticket.js";
|
|
7
|
+
export async function resolveMutateHookPermission(input) {
|
|
8
|
+
const { argv, tier, storageRoot, toolInputHash, rawToolName, toolInputPreview, policyRevision, initialReasons, } = input;
|
|
9
|
+
const reasons = [...initialReasons];
|
|
10
|
+
let permission = "deny";
|
|
11
|
+
let ticketConsumed = false;
|
|
12
|
+
let approvalFlowSignal = null;
|
|
13
|
+
let inlineApproval = null;
|
|
14
|
+
if (tier !== "MUTATE") {
|
|
15
|
+
return {
|
|
16
|
+
permission: tier === "READ" ? "allow" : "deny",
|
|
17
|
+
ticketConsumed,
|
|
18
|
+
inlineApproval,
|
|
19
|
+
approvalFlowSignal,
|
|
20
|
+
reasons,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
ticketConsumed = await tryConsumeExecutionTicket(argv, {
|
|
24
|
+
storageRoot,
|
|
25
|
+
kind: "mcp",
|
|
26
|
+
tool_input_sha256: toolInputHash,
|
|
27
|
+
});
|
|
28
|
+
if (ticketConsumed) {
|
|
29
|
+
return { permission: "allow", ticketConsumed, inlineApproval, approvalFlowSignal, reasons };
|
|
30
|
+
}
|
|
31
|
+
const hash = argvSha256(argv);
|
|
32
|
+
const pending = await readPendingApprovalIndex(hash, { storageRoot });
|
|
33
|
+
if (pending) {
|
|
34
|
+
const autoRedeem = await resolveMutateApproval({
|
|
35
|
+
argv: [...argv],
|
|
36
|
+
proposalKind: "mcp",
|
|
37
|
+
storageRoot,
|
|
38
|
+
rawDisplay: `${rawToolName} ${toolInputPreview}`,
|
|
39
|
+
eventId: randomUUID(),
|
|
40
|
+
policyRevision,
|
|
41
|
+
reasons,
|
|
42
|
+
approval: { request_id: pending.request_id },
|
|
43
|
+
waitMs: 0,
|
|
44
|
+
tool_input_sha256: toolInputHash,
|
|
45
|
+
});
|
|
46
|
+
if (autoRedeem.kind === "allow" && autoRedeem.ticketRecorded) {
|
|
47
|
+
ticketConsumed = await tryConsumeExecutionTicket(argv, {
|
|
48
|
+
storageRoot,
|
|
49
|
+
kind: "mcp",
|
|
50
|
+
tool_input_sha256: toolInputHash,
|
|
51
|
+
});
|
|
52
|
+
if (ticketConsumed) {
|
|
53
|
+
return { permission: "allow", ticketConsumed, inlineApproval, approvalFlowSignal, reasons };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
approvalFlowSignal = "retry_without_guard_wait_resolve";
|
|
57
|
+
reasons.push("retry_without_guard_wait_resolve");
|
|
58
|
+
inlineApproval = { request_id: pending.request_id, open_url: pending.open_url };
|
|
59
|
+
return { permission: "deny", ticketConsumed, inlineApproval, approvalFlowSignal, reasons };
|
|
60
|
+
}
|
|
61
|
+
const created = await tryHookInlineApprovalRequest({
|
|
62
|
+
argv: [...argv],
|
|
63
|
+
kind: "mcp",
|
|
64
|
+
rawDisplay: `${rawToolName} ${toolInputPreview}`,
|
|
65
|
+
policyRevision,
|
|
66
|
+
reasons,
|
|
67
|
+
eventId: randomUUID(),
|
|
68
|
+
storageRoot,
|
|
69
|
+
tool_input_sha256: toolInputHash,
|
|
70
|
+
});
|
|
71
|
+
if (created) {
|
|
72
|
+
inlineApproval = { request_id: created.request_id, open_url: created.open_url };
|
|
73
|
+
}
|
|
74
|
+
return { permission: "deny", ticketConsumed, inlineApproval, approvalFlowSignal, reasons };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=before-mcp-mutate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-mutate.js","sourceRoot":"","sources":["../../src/hooks/before-mcp-mutate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Tier } from "../policy/index.js";
|
|
2
|
+
import type { BeforeMCPExecutionPayload } from "./before-mcp-types.js";
|
|
3
|
+
export declare function handleSkippedMcpHook(input: {
|
|
4
|
+
payload: BeforeMCPExecutionPayload;
|
|
5
|
+
rawToolName: string;
|
|
6
|
+
bareTool: string;
|
|
7
|
+
argv: string[];
|
|
8
|
+
tier: Tier;
|
|
9
|
+
reasons: string[];
|
|
10
|
+
policyRevision: number | null;
|
|
11
|
+
auditLogRoot: string;
|
|
12
|
+
decisionStarted: number;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=before-mcp-skipped.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-skipped.d.ts","sourceRoot":"","sources":["../../src/hooks/before-mcp-skipped.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,KAAK,EAAE,yBAAyB,EAA8B,MAAM,uBAAuB,CAAC;AAGnG,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,OAAO,EAAE,yBAAyB,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkEhB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { appendAuditJsonl } from "../audit/jsonl.js";
|
|
2
|
+
import { getInstallId } from "../cli/install-id.js";
|
|
3
|
+
import { sendGuardEvent } from "../telemetry/guard-events.js";
|
|
4
|
+
import { stringifyToolInput } from "./before-mcp-argv.js";
|
|
5
|
+
export async function handleSkippedMcpHook(input) {
|
|
6
|
+
const { payload, rawToolName, bareTool, argv, tier, reasons, policyRevision, auditLogRoot, decisionStarted, } = input;
|
|
7
|
+
const latency_ms = performance.now() - decisionStarted;
|
|
8
|
+
const toolInputStr = stringifyToolInput(payload.tool_input);
|
|
9
|
+
try {
|
|
10
|
+
await appendAuditJsonl({
|
|
11
|
+
ts: new Date().toISOString(),
|
|
12
|
+
hook: "beforeMCPExecution",
|
|
13
|
+
tool_name: rawToolName,
|
|
14
|
+
bare_tool: bareTool,
|
|
15
|
+
tool_input: toolInputStr.slice(0, 8000),
|
|
16
|
+
argv,
|
|
17
|
+
status: "skipped",
|
|
18
|
+
skipped: true,
|
|
19
|
+
skip_reason: "mcp_policy_unmatched",
|
|
20
|
+
tier,
|
|
21
|
+
permission: "allow",
|
|
22
|
+
ticketConsumed: false,
|
|
23
|
+
reasons,
|
|
24
|
+
latency_ms,
|
|
25
|
+
}, auditLogRoot);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
29
|
+
process.stderr.write(`[auditor] audit log append failed: ${msg}\n`);
|
|
30
|
+
}
|
|
31
|
+
const skipResponse = { permission: "allow" };
|
|
32
|
+
process.stdout.write(JSON.stringify(skipResponse, null, 2));
|
|
33
|
+
await sendGuardEvent({
|
|
34
|
+
ts: new Date().toISOString(),
|
|
35
|
+
status: "skipped",
|
|
36
|
+
skipped: true,
|
|
37
|
+
skip_reason: "mcp_policy_unmatched",
|
|
38
|
+
tool: "auditor-hook-mcp",
|
|
39
|
+
command_path: argv[1] ?? null,
|
|
40
|
+
verb: argv[2] ?? null,
|
|
41
|
+
resource: toolInputStr ? toolInputStr.slice(0, 500) : null,
|
|
42
|
+
reason: reasons[0] ?? "mcp_policy_unmatched",
|
|
43
|
+
cmd: `${rawToolName}`,
|
|
44
|
+
tier,
|
|
45
|
+
decision: "allow",
|
|
46
|
+
latency_ms,
|
|
47
|
+
installId: getInstallId(),
|
|
48
|
+
kind: "mcp",
|
|
49
|
+
...(policyRevision !== null ? { policy_revision: policyRevision } : {}),
|
|
50
|
+
meta: {
|
|
51
|
+
hook: "beforeMCPExecution",
|
|
52
|
+
ticketConsumed: false,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=before-mcp-skipped.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-skipped.js","sourceRoot":"","sources":["../../src/hooks/before-mcp-skipped.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAG9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAU1C;IACC,MAAM,EACJ,OAAO,EACP,WAAW,EACX,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,cAAc,EACd,YAAY,EACZ,eAAe,GAChB,GAAG,KAAK,CAAC;IAEV,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;IACvD,MAAM,YAAY,GAAG,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAE5D,IAAI,CAAC;QACH,MAAM,gBAAgB,CACpB;YACE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,oBAAoB;YAC1B,SAAS,EAAE,WAAW;YACtB,SAAS,EAAE,QAAQ;YACnB,UAAU,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;YACvC,IAAI;YACJ,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,sBAAsB;YACnC,IAAI;YACJ,UAAU,EAAE,OAAO;YACnB,cAAc,EAAE,KAAK;YACrB,OAAO;YACP,UAAU;SACX,EACD,YAAY,CACb,CAAC;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,GAAG,IAAI,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,YAAY,GAA+B,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IACzE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAE5D,MAAM,cAAc,CAAC;QACnB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,sBAAsB;QACnC,IAAI,EAAE,kBAAkB;QACxB,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;QAC7B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;QACrB,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QAC1D,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,sBAAsB;QAC5C,GAAG,EAAE,GAAG,WAAW,EAAE;QACrB,IAAI;QACJ,QAAQ,EAAE,OAAO;QACjB,UAAU;QACV,SAAS,EAAE,YAAY,EAAE;QACzB,IAAI,EAAE,KAAK;QACX,GAAG,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,IAAI,EAAE;YACJ,IAAI,EAAE,oBAAoB;YAC1B,cAAc,EAAE,KAAK;SACtB;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Cursor `beforeMCPExecution` stdin (see https://cursor.com/docs/hooks.md). */
|
|
2
|
+
export type BeforeMCPExecutionPayload = {
|
|
3
|
+
tool_name?: unknown;
|
|
4
|
+
tool_input?: unknown;
|
|
5
|
+
url?: unknown;
|
|
6
|
+
command?: unknown;
|
|
7
|
+
cwd?: unknown;
|
|
8
|
+
workspace_roots?: unknown;
|
|
9
|
+
};
|
|
10
|
+
export type BeforeMCPExecutionResponse = {
|
|
11
|
+
permission: "allow" | "deny" | "ask";
|
|
12
|
+
user_message?: string;
|
|
13
|
+
agent_message?: string;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=before-mcp-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-types.d.ts","sourceRoot":"","sources":["../../src/hooks/before-mcp-types.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before-mcp-types.js","sourceRoot":"","sources":["../../src/hooks/before-mcp-types.ts"],"names":[],"mappings":""}
|
|
@@ -1,30 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
export type BeforeMCPExecutionPayload
|
|
3
|
-
|
|
4
|
-
tool_input?: unknown;
|
|
5
|
-
url?: unknown;
|
|
6
|
-
command?: unknown;
|
|
7
|
-
cwd?: unknown;
|
|
8
|
-
workspace_roots?: unknown;
|
|
9
|
-
};
|
|
10
|
-
export type BeforeMCPExecutionResponse = {
|
|
11
|
-
permission: "allow" | "deny" | "ask";
|
|
12
|
-
user_message?: string;
|
|
13
|
-
agent_message?: string;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* When Cursor encodes MCP tools as `MCP:<server>:<tool>` (see Cursor hooks docs / preToolUse), split into
|
|
17
|
-
* server + bare tool name for policy rows under `policies.mcp.<server>.<tool>`.
|
|
18
|
-
*/
|
|
19
|
-
export declare function splitMcpToolName(raw: string): {
|
|
20
|
-
serverGuess: string | null;
|
|
21
|
-
tool: string;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Maps hook payload → argv for `policies.v1.json` under tool key `mcp`.
|
|
25
|
-
* Omits raw `tool_input` from argv tokens so JSON metacharacters do not trip shell metachar heuristics.
|
|
26
|
-
*/
|
|
27
|
-
export declare function mcpHookArgvFromPayload(payload: BeforeMCPExecutionPayload): string[];
|
|
1
|
+
import type { BeforeMCPExecutionResponse } from "./before-mcp-types.js";
|
|
2
|
+
export type { BeforeMCPExecutionPayload, BeforeMCPExecutionResponse } from "./before-mcp-types.js";
|
|
3
|
+
export { mcpHookArgvFromPayload, splitMcpToolName } from "./before-mcp-argv.js";
|
|
28
4
|
/**
|
|
29
5
|
* Cursor `beforeMCPExecution`: stdin JSON → stdout JSON (`permission` only contract).
|
|
30
6
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-before-mcp.d.ts","sourceRoot":"","sources":["../../src/hooks/run-before-mcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run-before-mcp.d.ts","sourceRoot":"","sources":["../../src/hooks/run-before-mcp.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnG,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAsBhF;;GAEG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAkJ/D;AAED,wBAAgB,oCAAoC,CAAC,GAAG,EAAE,OAAO,GAAG,0BAA0B,CAM7F"}
|
|
@@ -3,77 +3,13 @@ import { appendAuditJsonl } from "../audit/jsonl.js";
|
|
|
3
3
|
import { getInstallId } from "../cli/install-id.js";
|
|
4
4
|
import { evaluateMcpProposal } from "../shell/evaluate.js";
|
|
5
5
|
import { resolveGuardStorageRoot } from "../bridge/guard-storage-root.js";
|
|
6
|
-
import { tryConsumeExecutionTicket } from "../bridge/execution-ticket.js";
|
|
7
|
-
import { tryHookInlineApprovalRequest } from "../approval/hook-inline-approval.js";
|
|
8
|
-
import { readPendingApprovalIndex } from "../bridge/pending-approval-index.js";
|
|
9
|
-
import { argvSha256 } from "../approval/argv-fingerprint.js";
|
|
10
6
|
import { toolInputSha256 } from "../approval/fingerprint.js";
|
|
11
|
-
import { resolveMutateApproval } from "../approval/mcp-flow.js";
|
|
12
7
|
import { formatHookAllowViaCredentialMessage, formatHookDenyMessages, } from "./agent-message.js";
|
|
13
|
-
import { randomUUID } from "node:crypto";
|
|
14
8
|
import { sendGuardEvent } from "../telemetry/guard-events.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
function stringifyToolInput(raw) {
|
|
21
|
-
if (raw === undefined || raw === null)
|
|
22
|
-
return "";
|
|
23
|
-
if (typeof raw === "string")
|
|
24
|
-
return raw;
|
|
25
|
-
try {
|
|
26
|
-
return JSON.stringify(raw);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return String(raw);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* When Cursor encodes MCP tools as `MCP:<server>:<tool>` (see Cursor hooks docs / preToolUse), split into
|
|
34
|
-
* server + bare tool name for policy rows under `policies.mcp.<server>.<tool>`.
|
|
35
|
-
*/
|
|
36
|
-
export function splitMcpToolName(raw) {
|
|
37
|
-
const t = raw.trim();
|
|
38
|
-
if (!t)
|
|
39
|
-
return { serverGuess: null, tool: "_" };
|
|
40
|
-
if (t.startsWith("MCP:")) {
|
|
41
|
-
const body = t.slice(4).trim();
|
|
42
|
-
const idx = body.lastIndexOf(":");
|
|
43
|
-
if (idx !== -1) {
|
|
44
|
-
const serverPart = body.slice(0, idx).trim();
|
|
45
|
-
const toolPart = body.slice(idx + 1).trim();
|
|
46
|
-
if (serverPart && toolPart)
|
|
47
|
-
return { serverGuess: serverPart, tool: toolPart };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return { serverGuess: null, tool: t };
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Maps hook payload → argv for `policies.v1.json` under tool key `mcp`.
|
|
54
|
-
* Omits raw `tool_input` from argv tokens so JSON metacharacters do not trip shell metachar heuristics.
|
|
55
|
-
*/
|
|
56
|
-
export function mcpHookArgvFromPayload(payload) {
|
|
57
|
-
const rawName = typeof payload.tool_name === "string" ? payload.tool_name.trim() : "";
|
|
58
|
-
const { serverGuess, tool } = splitMcpToolName(rawName);
|
|
59
|
-
let server = "stdio";
|
|
60
|
-
if (typeof payload.url === "string" && payload.url.trim()) {
|
|
61
|
-
const u = payload.url.trim();
|
|
62
|
-
try {
|
|
63
|
-
server = new URL(u).host || u;
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
server = u;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
else if (serverGuess) {
|
|
70
|
-
server = serverGuess;
|
|
71
|
-
}
|
|
72
|
-
else if (typeof payload.command === "string" && payload.command.trim()) {
|
|
73
|
-
server = payload.command.trim().slice(0, 400);
|
|
74
|
-
}
|
|
75
|
-
return ["mcp", server, tool || "_"];
|
|
76
|
-
}
|
|
9
|
+
import { mcpHookArgvFromPayload, preferredHookCwd, stringifyToolInput, } from "./before-mcp-argv.js";
|
|
10
|
+
import { resolveMutateHookPermission } from "./before-mcp-mutate.js";
|
|
11
|
+
import { handleSkippedMcpHook } from "./before-mcp-skipped.js";
|
|
12
|
+
export { mcpHookArgvFromPayload, splitMcpToolName } from "./before-mcp-argv.js";
|
|
77
13
|
async function readStdinJson() {
|
|
78
14
|
return await new Promise((resolve, reject) => {
|
|
79
15
|
let data = "";
|
|
@@ -89,14 +25,10 @@ async function readStdinJson() {
|
|
|
89
25
|
});
|
|
90
26
|
});
|
|
91
27
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
catch (e) {
|
|
97
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
98
|
-
process.stderr.write(`[auditor] audit log append failed: ${msg}\n`);
|
|
99
|
-
}
|
|
28
|
+
function tierToPermission(tier) {
|
|
29
|
+
if (tier === "READ")
|
|
30
|
+
return "allow";
|
|
31
|
+
return "deny";
|
|
100
32
|
}
|
|
101
33
|
/**
|
|
102
34
|
* Cursor `beforeMCPExecution`: stdin JSON → stdout JSON (`permission` only contract).
|
|
@@ -119,121 +51,45 @@ export async function runBeforeMcpHookFromStdin() {
|
|
|
119
51
|
const [policy, policyRevision] = await Promise.all([loadPoliciesV1(), readPoliciesV1Revision()]);
|
|
120
52
|
const { skipped, evaluation } = evaluateMcpProposal(policy, argv);
|
|
121
53
|
const { classification, flags, tier } = evaluation;
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
? payload.cwd
|
|
125
|
-
: Array.isArray(payload.workspace_roots) &&
|
|
126
|
-
typeof payload.workspace_roots[0] === "string"
|
|
127
|
-
? payload.workspace_roots[0]
|
|
128
|
-
: undefined;
|
|
129
|
-
const storageRoot = resolveGuardStorageRoot(preferredCwd);
|
|
54
|
+
const initialReasons = evaluation.reasons.map((r) => r.message);
|
|
55
|
+
const storageRoot = resolveGuardStorageRoot(preferredHookCwd(payload));
|
|
130
56
|
const auditLogRoot = storageRoot;
|
|
131
57
|
const toolInputHash = toolInputSha256(payload.tool_input);
|
|
132
58
|
if (skipped) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
hook: "beforeMCPExecution",
|
|
138
|
-
tool_name: rawToolName,
|
|
139
|
-
bare_tool: bareTool,
|
|
140
|
-
tool_input: toolInputStr.slice(0, 8000),
|
|
59
|
+
await handleSkippedMcpHook({
|
|
60
|
+
payload,
|
|
61
|
+
rawToolName,
|
|
62
|
+
bareTool,
|
|
141
63
|
argv,
|
|
142
|
-
status: "skipped",
|
|
143
|
-
skipped: true,
|
|
144
|
-
skip_reason: "mcp_policy_unmatched",
|
|
145
|
-
tier,
|
|
146
|
-
permission: "allow",
|
|
147
|
-
ticketConsumed: false,
|
|
148
|
-
reasons,
|
|
149
|
-
latency_ms,
|
|
150
|
-
}, auditLogRoot);
|
|
151
|
-
const skipResponse = { permission: "allow" };
|
|
152
|
-
process.stdout.write(JSON.stringify(skipResponse, null, 2));
|
|
153
|
-
await sendGuardEvent({
|
|
154
|
-
ts: new Date().toISOString(),
|
|
155
|
-
status: "skipped",
|
|
156
|
-
skipped: true,
|
|
157
|
-
skip_reason: "mcp_policy_unmatched",
|
|
158
|
-
tool: "auditor-hook-mcp",
|
|
159
|
-
command_path: argv[1] ?? null,
|
|
160
|
-
verb: argv[2] ?? null,
|
|
161
|
-
resource: toolInputStr ? toolInputStr.slice(0, 500) : null,
|
|
162
|
-
reason: reasons[0] ?? "mcp_policy_unmatched",
|
|
163
|
-
cmd: `${rawToolName}`,
|
|
164
64
|
tier,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
...(policyRevision !== null ? { policy_revision: policyRevision } : {}),
|
|
170
|
-
meta: {
|
|
171
|
-
hook: "beforeMCPExecution",
|
|
172
|
-
ticketConsumed: false,
|
|
173
|
-
},
|
|
65
|
+
reasons: initialReasons,
|
|
66
|
+
policyRevision,
|
|
67
|
+
auditLogRoot,
|
|
68
|
+
decisionStarted,
|
|
174
69
|
});
|
|
175
70
|
return;
|
|
176
71
|
}
|
|
177
72
|
let permission = tierToPermission(tier);
|
|
178
73
|
let ticketConsumed = false;
|
|
74
|
+
let inlineApproval = null;
|
|
179
75
|
let approvalFlowSignal = null;
|
|
76
|
+
let reasons = initialReasons;
|
|
180
77
|
if (permission === "deny" && tier === "MUTATE") {
|
|
181
|
-
|
|
78
|
+
const mutate = await resolveMutateHookPermission({
|
|
79
|
+
argv,
|
|
80
|
+
tier,
|
|
182
81
|
storageRoot,
|
|
183
|
-
|
|
184
|
-
|
|
82
|
+
toolInputHash,
|
|
83
|
+
rawToolName,
|
|
84
|
+
toolInputPreview: stringifyToolInput(payload.tool_input).slice(0, 200),
|
|
85
|
+
policyRevision,
|
|
86
|
+
initialReasons,
|
|
185
87
|
});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const hash = argvSha256(argv);
|
|
192
|
-
const pending = await readPendingApprovalIndex(hash, { storageRoot });
|
|
193
|
-
if (pending) {
|
|
194
|
-
const autoRedeem = await resolveMutateApproval({
|
|
195
|
-
argv: [...argv],
|
|
196
|
-
proposalKind: "mcp",
|
|
197
|
-
storageRoot,
|
|
198
|
-
rawDisplay: `${rawToolName} ${stringifyToolInput(payload.tool_input).slice(0, 200)}`,
|
|
199
|
-
eventId: randomUUID(),
|
|
200
|
-
policyRevision,
|
|
201
|
-
reasons,
|
|
202
|
-
approval: { request_id: pending.request_id },
|
|
203
|
-
waitMs: 0,
|
|
204
|
-
tool_input_sha256: toolInputHash,
|
|
205
|
-
});
|
|
206
|
-
if (autoRedeem.kind === "allow" && autoRedeem.ticketRecorded) {
|
|
207
|
-
ticketConsumed = await tryConsumeExecutionTicket(argv, {
|
|
208
|
-
storageRoot,
|
|
209
|
-
kind: "mcp",
|
|
210
|
-
tool_input_sha256: toolInputHash,
|
|
211
|
-
});
|
|
212
|
-
if (ticketConsumed) {
|
|
213
|
-
permission = "allow";
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (permission === "deny") {
|
|
217
|
-
approvalFlowSignal = "retry_without_guard_wait_resolve";
|
|
218
|
-
reasons.push("retry_without_guard_wait_resolve");
|
|
219
|
-
inlineApproval = { request_id: pending.request_id, open_url: pending.open_url };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
const created = await tryHookInlineApprovalRequest({
|
|
224
|
-
argv: [...argv],
|
|
225
|
-
kind: "mcp",
|
|
226
|
-
rawDisplay: `${rawToolName} ${stringifyToolInput(payload.tool_input).slice(0, 200)}`,
|
|
227
|
-
policyRevision,
|
|
228
|
-
reasons,
|
|
229
|
-
eventId: randomUUID(),
|
|
230
|
-
storageRoot,
|
|
231
|
-
tool_input_sha256: toolInputHash,
|
|
232
|
-
});
|
|
233
|
-
if (created) {
|
|
234
|
-
inlineApproval = { request_id: created.request_id, open_url: created.open_url };
|
|
235
|
-
}
|
|
236
|
-
}
|
|
88
|
+
permission = mutate.permission;
|
|
89
|
+
ticketConsumed = mutate.ticketConsumed;
|
|
90
|
+
inlineApproval = mutate.inlineApproval;
|
|
91
|
+
approvalFlowSignal = mutate.approvalFlowSignal;
|
|
92
|
+
reasons = mutate.reasons;
|
|
237
93
|
}
|
|
238
94
|
const latency_ms = performance.now() - decisionStarted;
|
|
239
95
|
const toolInputStr = stringifyToolInput(payload.tool_input);
|
|
@@ -258,24 +114,30 @@ export async function runBeforeMcpHookFromStdin() {
|
|
|
258
114
|
user_message: denyMessages.user_message,
|
|
259
115
|
agent_message: denyMessages.agent_message,
|
|
260
116
|
};
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
117
|
+
try {
|
|
118
|
+
await appendAuditJsonl({
|
|
119
|
+
ts: new Date().toISOString(),
|
|
120
|
+
hook: "beforeMCPExecution",
|
|
121
|
+
tool_name: rawToolName,
|
|
122
|
+
bare_tool: bareTool,
|
|
123
|
+
tool_input: toolInputStr.slice(0, 8000),
|
|
124
|
+
argv,
|
|
125
|
+
classification,
|
|
126
|
+
flags,
|
|
127
|
+
tier,
|
|
128
|
+
permission,
|
|
129
|
+
ticketConsumed,
|
|
130
|
+
inline_request_id: inlineApproval?.request_id ?? null,
|
|
131
|
+
tool_input_sha256: toolInputHash,
|
|
132
|
+
reasons,
|
|
133
|
+
approval_flow_signal: approvalFlowSignal,
|
|
134
|
+
latency_ms,
|
|
135
|
+
}, auditLogRoot);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
139
|
+
process.stderr.write(`[auditor] audit log append failed: ${msg}\n`);
|
|
140
|
+
}
|
|
279
141
|
process.stdout.write(JSON.stringify(response, null, 2));
|
|
280
142
|
const status = permission === "allow" ? "passed" : "blocked";
|
|
281
143
|
await sendGuardEvent({
|