@modootoday/pmcp 0.10.0 → 0.11.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.
@@ -0,0 +1,11 @@
1
+ import { S as ShellHookSpec, H as HookTool } from '../hook-spec-PRSnYGLV.js';
2
+
3
+ type Decision = {
4
+ readonly decision: "allow" | "deny";
5
+ readonly reason?: string;
6
+ };
7
+ declare function guardPath(root: string, command: readonly string[]): string;
8
+ declare function evaluateShellHook(root: string, policy: ShellHookSpec, tool: HookTool, payload: unknown): Decision;
9
+ declare function nativeHookDecision(tool: HookTool, decision: Decision): object;
10
+
11
+ export { evaluateShellHook, guardPath, nativeHookDecision };
@@ -0,0 +1,191 @@
1
+ // src/project/hook-adapter.ts
2
+ import { spawnSync } from "child_process";
3
+ import { createHash } from "crypto";
4
+ import { existsSync, readFileSync, realpathSync, statSync } from "fs";
5
+ import { dirname, isAbsolute, relative, resolve } from "path";
6
+ var denied = () => ({
7
+ decision: "deny",
8
+ reason: "Shell guard failed validation or execution"
9
+ });
10
+ function guardPath(root, command) {
11
+ const script = command[0]?.startsWith(".agents/guards/") ? command[0] : command[1];
12
+ if (!script?.startsWith(".agents/guards/")) {
13
+ throw new Error("Hook command must execute a script under .agents/guards/");
14
+ }
15
+ const absolute = resolve(root, script);
16
+ const base = resolve(root, ".agents/guards");
17
+ const inside = relative(base, absolute);
18
+ if (isAbsolute(inside) || inside === ".." || inside.startsWith("../") || inside.startsWith("..\\")) {
19
+ throw new Error("Hook guard must stay under .agents/guards/");
20
+ }
21
+ if (!existsSync(absolute) || !statSync(absolute).isFile()) {
22
+ throw new Error("Hook guard script must exist");
23
+ }
24
+ const actual = relative(base, realpathSync(absolute));
25
+ if (isAbsolute(actual) || actual === ".." || actual.startsWith("../") || actual.startsWith("..\\")) {
26
+ throw new Error("Hook guard cannot resolve outside .agents/guards/");
27
+ }
28
+ return absolute;
29
+ }
30
+ function object(value) {
31
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
32
+ return {};
33
+ }
34
+ return value;
35
+ }
36
+ function scalar(value) {
37
+ if (typeof value !== "string") {
38
+ return void 0;
39
+ }
40
+ if (value.startsWith('"')) {
41
+ try {
42
+ const parsed = JSON.parse(value);
43
+ if (typeof parsed === "string") {
44
+ return parsed;
45
+ }
46
+ } catch {
47
+ return value;
48
+ }
49
+ }
50
+ return value;
51
+ }
52
+ function evaluateShellHook(root, policy, tool, payload) {
53
+ try {
54
+ const value = object(payload);
55
+ const call = object(value["toolCall"]);
56
+ const input = tool === "antigravity" ? object(call["args"]) : object(value["tool_input"]);
57
+ const name = tool === "antigravity" ? call["name"] : value["tool_name"];
58
+ const shellNames = {
59
+ codex: ["Bash", "exec_command", "shell", "shell_command"],
60
+ gemini: ["run_shell_command"],
61
+ antigravity: ["run_command"]
62
+ };
63
+ if (typeof name !== "string") {
64
+ return denied();
65
+ }
66
+ if (!shellNames[tool].includes(name)) {
67
+ return { decision: "allow" };
68
+ }
69
+ const command = scalar(
70
+ input[tool === "antigravity" ? "CommandLine" : "command"]
71
+ );
72
+ if (command === void 0 || command.length > 65536) {
73
+ return denied();
74
+ }
75
+ const cwd = scalar(input["Cwd"]) ?? scalar(value["cwd"]) ?? root;
76
+ const args = [...policy.command];
77
+ const script = guardPath(root, args);
78
+ if (args[0]?.startsWith(".agents/guards/")) {
79
+ args[0] = script;
80
+ } else {
81
+ args[1] = script;
82
+ }
83
+ const executable = args.shift();
84
+ if (!executable) {
85
+ return denied();
86
+ }
87
+ const result = spawnSync(executable, args, {
88
+ cwd: root,
89
+ env: { PATH: process.env["PATH"], LANG: "C.UTF-8" },
90
+ input: JSON.stringify({
91
+ runtime: tool,
92
+ event: "before_shell",
93
+ command,
94
+ cwd
95
+ }),
96
+ encoding: "utf8",
97
+ timeout: policy.timeoutMs,
98
+ killSignal: "SIGKILL",
99
+ maxBuffer: 65536
100
+ });
101
+ if (result.status !== 0 || result.error) {
102
+ return denied();
103
+ }
104
+ const response = object(JSON.parse(result.stdout));
105
+ if (Object.keys(response).some(
106
+ (key) => !["decision", "reason"].includes(key)
107
+ ) || !["allow", "deny"].includes(String(response["decision"])) || response["reason"] !== void 0 && typeof response["reason"] !== "string") {
108
+ return denied();
109
+ }
110
+ return {
111
+ decision: response["decision"],
112
+ ...typeof response["reason"] === "string" ? { reason: response["reason"].slice(0, 2e3) } : {}
113
+ };
114
+ } catch {
115
+ return denied();
116
+ }
117
+ }
118
+ function nativeHookDecision(tool, decision) {
119
+ if (tool === "codex") {
120
+ return {
121
+ hookSpecificOutput: {
122
+ hookEventName: "PreToolUse",
123
+ permissionDecision: decision.decision,
124
+ ...decision.reason ? { permissionDecisionReason: decision.reason } : {}
125
+ }
126
+ };
127
+ }
128
+ return decision;
129
+ }
130
+ async function main() {
131
+ const [, , , tool, file, integrity] = process.argv;
132
+ const supported = tool === "codex" || tool === "gemini" || tool === "antigravity";
133
+ if (!supported) {
134
+ process.exitCode = 2;
135
+ return;
136
+ }
137
+ let decision = denied();
138
+ try {
139
+ if (!file) {
140
+ throw new Error("Missing hook policy");
141
+ }
142
+ const bytes = readFileSync(file);
143
+ const adapter = readFileSync(process.argv[1]);
144
+ const actual = createHash("sha256").update(adapter).update(bytes).digest("hex");
145
+ if (actual !== integrity) {
146
+ throw new Error("Hook integrity mismatch");
147
+ }
148
+ const policy = JSON.parse(bytes.toString("utf8"));
149
+ if (!policy.targets.includes(tool)) {
150
+ throw new Error("Hook target mismatch");
151
+ }
152
+ const chunks = [];
153
+ let size = 0;
154
+ for await (const chunk of process.stdin) {
155
+ const bytes2 = Buffer.from(chunk);
156
+ size += bytes2.length;
157
+ if (size > 131072) {
158
+ throw new Error("Hook input too large");
159
+ }
160
+ chunks.push(bytes2);
161
+ }
162
+ const root = resolve(dirname(file), "../..");
163
+ const guardSha256 = createHash("sha256").update(readFileSync(guardPath(root, policy.command))).digest("hex");
164
+ if (policy.contractVersion !== 1 || guardSha256 !== policy.guardSha256) {
165
+ throw new Error(
166
+ "Hook guard source changed; reproject and review native trust"
167
+ );
168
+ }
169
+ decision = evaluateShellHook(
170
+ root,
171
+ policy,
172
+ tool,
173
+ JSON.parse(Buffer.concat(chunks).toString("utf8"))
174
+ );
175
+ } catch {
176
+ decision = denied();
177
+ }
178
+ process.stdout.write(
179
+ `${JSON.stringify(nativeHookDecision(tool, decision))}
180
+ `
181
+ );
182
+ }
183
+ if (process.argv[2] === "--pmcp-shell-hook") {
184
+ await main();
185
+ }
186
+ export {
187
+ evaluateShellHook,
188
+ guardPath,
189
+ nativeHookDecision
190
+ };
191
+ //# sourceMappingURL=hook-adapter.js.map