@getmarrow/mcp 3.9.103 → 3.9.106
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 +26 -5
- package/dist/approval-reply.d.ts +2 -0
- package/dist/approval-reply.d.ts.map +1 -0
- package/dist/approval-reply.js +18 -0
- package/dist/approval-reply.js.map +1 -0
- package/dist/cli.js +1677 -1636
- package/dist/cli.js.map +1 -1
- package/dist/codex-plugin-pins.d.ts.map +1 -1
- package/dist/codex-plugin-pins.js +18 -0
- package/dist/codex-plugin-pins.js.map +1 -1
- package/dist/env.d.ts +8 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +83 -4
- package/dist/env.js.map +1 -1
- package/dist/hook-context.d.ts.map +1 -1
- package/dist/hook-context.js +25 -0
- package/dist/hook-context.js.map +1 -1
- package/dist/hook-contract.d.ts +4 -3
- package/dist/hook-contract.d.ts.map +1 -1
- package/dist/hook-contract.js +36 -3
- package/dist/hook-contract.js.map +1 -1
- package/dist/hook-pre-action.d.ts +38 -7
- package/dist/hook-pre-action.d.ts.map +1 -1
- package/dist/hook-pre-action.js +254 -21
- package/dist/hook-pre-action.js.map +1 -1
- package/dist/hook-tool-policy.d.ts +73 -4
- package/dist/hook-tool-policy.d.ts.map +1 -1
- package/dist/hook-tool-policy.js +287 -18
- package/dist/hook-tool-policy.js.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +5 -3
- package/dist/hook.js.map +1 -1
- package/dist/host-approval-state.d.ts +7 -0
- package/dist/host-approval-state.d.ts.map +1 -1
- package/dist/host-approval-state.js +15 -0
- package/dist/host-approval-state.js.map +1 -1
- package/dist/host-approval.d.ts +22 -1
- package/dist/host-approval.d.ts.map +1 -1
- package/dist/host-approval.js +65 -4
- package/dist/host-approval.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/keyless-server.d.ts +53 -0
- package/dist/keyless-server.d.ts.map +1 -0
- package/dist/keyless-server.js +230 -0
- package/dist/keyless-server.js.map +1 -0
- package/dist/normalized-action.d.ts +2 -0
- package/dist/normalized-action.d.ts.map +1 -1
- package/dist/normalized-action.js +65 -10
- package/dist/normalized-action.js.map +1 -1
- package/dist/own-server-identity.d.ts +22 -3
- package/dist/own-server-identity.d.ts.map +1 -1
- package/dist/own-server-identity.js +98 -27
- package/dist/own-server-identity.js.map +1 -1
- package/dist/own-tool.d.ts +14 -6
- package/dist/own-tool.d.ts.map +1 -1
- package/dist/own-tool.js +18 -7
- package/dist/own-tool.js.map +1 -1
- package/dist/seatbelt/env-dump.d.ts +45 -0
- package/dist/seatbelt/env-dump.d.ts.map +1 -0
- package/dist/seatbelt/env-dump.js +308 -0
- package/dist/seatbelt/env-dump.js.map +1 -0
- package/dist/seatbelt/fallback-copy.d.ts +16 -0
- package/dist/seatbelt/fallback-copy.d.ts.map +1 -0
- package/dist/seatbelt/fallback-copy.js +68 -0
- package/dist/seatbelt/fallback-copy.js.map +1 -0
- package/dist/seatbelt/hook.d.ts +21 -2
- package/dist/seatbelt/hook.d.ts.map +1 -1
- package/dist/seatbelt/hook.js +45 -11
- package/dist/seatbelt/hook.js.map +1 -1
- package/dist/seatbelt/mapper.d.ts.map +1 -1
- package/dist/seatbelt/mapper.js +360 -27
- package/dist/seatbelt/mapper.js.map +1 -1
- package/dist/seatbelt/tables.d.ts +1 -1
- package/dist/seatbelt/tables.d.ts.map +1 -1
- package/dist/seatbelt/tables.js +9 -16
- package/dist/seatbelt/tables.js.map +1 -1
- package/dist/secret-paths.d.ts +17 -0
- package/dist/secret-paths.d.ts.map +1 -0
- package/dist/secret-paths.js +32 -0
- package/dist/secret-paths.js.map +1 -0
- package/dist/session-loop-guard.d.ts.map +1 -1
- package/dist/session-loop-guard.js +15 -5
- package/dist/session-loop-guard.js.map +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyless start of the MCP server. With no Marrow key anywhere the server reads
|
|
3
|
+
* one, the server used to print "MARROW_API_KEY environment variable is
|
|
4
|
+
* required" and exit 1, which hosts show as a broken server. It now answers the
|
|
5
|
+
* MCP protocol with zero tools and an `instructions` string that says how the
|
|
6
|
+
* owner connects a key.
|
|
7
|
+
*
|
|
8
|
+
* Fail closed: keylessStart is true only for a clear "no key". A key, anything
|
|
9
|
+
* key-shaped, a refused owner key file, a base URL the server would refuse, or
|
|
10
|
+
* any doubt runs the real server path exactly as before.
|
|
11
|
+
*
|
|
12
|
+
* The keyless server judges, checks and records nothing. It starts no child
|
|
13
|
+
* process, opens no network and no file, reads no environment, and writes only
|
|
14
|
+
* to stdout and stderr. Hooks are separate entrypoints and are not touched.
|
|
15
|
+
* Protocol handling matches the plugin launcher's keyless stub
|
|
16
|
+
* (marrow-plugins plugins/<plugin>/scripts/marrow-launch.cjs, serveKeyless).
|
|
17
|
+
*
|
|
18
|
+
* Never looser than the exit 1 it replaces: until a valid `initialize` request has
|
|
19
|
+
* been answered, a frame that is not JSON-RPC 2.0 (a Cursor or Claude hook payload
|
|
20
|
+
* piped to the entry, a parse error, an oversize line) or an end of stdin ends the
|
|
21
|
+
* process with the no-key refusal on stderr, nothing on stdout, and exit 1. After
|
|
22
|
+
* an initialize, the end of stdin is exit 0.
|
|
23
|
+
*
|
|
24
|
+
* The key text (URL, activate command, plugin steps, the refusal) is owned by cli.ts
|
|
25
|
+
* and passed in; this file holds no second copy of it.
|
|
26
|
+
*/
|
|
27
|
+
export interface KeylessInstructionParts {
|
|
28
|
+
getKeyUrl: string;
|
|
29
|
+
activateCommand: string;
|
|
30
|
+
/** Set when the server runs under the Claude Code plugin: its key steps replace the activate command. */
|
|
31
|
+
pluginSteps?: string;
|
|
32
|
+
}
|
|
33
|
+
/** The `instructions` string of the initialize answer. */
|
|
34
|
+
export declare function keylessInstructions(parts: KeylessInstructionParts): string;
|
|
35
|
+
export interface KeylessTexts {
|
|
36
|
+
instructions: string;
|
|
37
|
+
/** Written to stderr once, when the first initialize is answered. */
|
|
38
|
+
notice: string;
|
|
39
|
+
/** The server's own no-key refusal, written to stderr before the exit 1 that a non-MCP first frame or an early end of stdin gets. */
|
|
40
|
+
refusal: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const KEYLESS_NOTICE = "Marrow: no Marrow key on this machine, so the Marrow MCP server started with no tools. Marrow hooks, where they are set up, keep their free keyless seatbelt.";
|
|
43
|
+
/**
|
|
44
|
+
* Whether the MCP server starts keyless. `resolved` must be the server's own resolveMarrowEnv() result.
|
|
45
|
+
* True only when it found no key (an empty MARROW_API_KEY or MARROW_KEY counts as none, a whitespace one does
|
|
46
|
+
* not), no owner key file was refused, no `--key` argument is given in any form, no fleet key
|
|
47
|
+
* (MARROW_KEY_<SUFFIX>) is set, and the base URL passes the server's own check.
|
|
48
|
+
*/
|
|
49
|
+
export declare function keylessStart(resolved: unknown, argv: readonly unknown[], env: Record<string, string | undefined>): boolean;
|
|
50
|
+
export declare function keylessProtocolVersion(params: unknown): string;
|
|
51
|
+
/** Newline-delimited JSON-RPC 2.0 on stdio with zero tools. Exits 0 when stdin ends after an initialize, 1 for a non-MCP start. */
|
|
52
|
+
export declare function serveKeylessMcp(serverVersion: string, texts: KeylessTexts): void;
|
|
53
|
+
//# sourceMappingURL=keyless-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyless-server.d.ts","sourceRoot":"","sources":["../src/keyless-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,yGAAyG;IACzG,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAS1E;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,qIAAqI;IACrI,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,cAAc,kKAAkK,CAAC;AAQ9L;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,OAAO,CAe1H;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAO9D;AAED,mIAAmI;AACnI,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAyHhF"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Keyless start of the MCP server. With no Marrow key anywhere the server reads
|
|
4
|
+
* one, the server used to print "MARROW_API_KEY environment variable is
|
|
5
|
+
* required" and exit 1, which hosts show as a broken server. It now answers the
|
|
6
|
+
* MCP protocol with zero tools and an `instructions` string that says how the
|
|
7
|
+
* owner connects a key.
|
|
8
|
+
*
|
|
9
|
+
* Fail closed: keylessStart is true only for a clear "no key". A key, anything
|
|
10
|
+
* key-shaped, a refused owner key file, a base URL the server would refuse, or
|
|
11
|
+
* any doubt runs the real server path exactly as before.
|
|
12
|
+
*
|
|
13
|
+
* The keyless server judges, checks and records nothing. It starts no child
|
|
14
|
+
* process, opens no network and no file, reads no environment, and writes only
|
|
15
|
+
* to stdout and stderr. Hooks are separate entrypoints and are not touched.
|
|
16
|
+
* Protocol handling matches the plugin launcher's keyless stub
|
|
17
|
+
* (marrow-plugins plugins/<plugin>/scripts/marrow-launch.cjs, serveKeyless).
|
|
18
|
+
*
|
|
19
|
+
* Never looser than the exit 1 it replaces: until a valid `initialize` request has
|
|
20
|
+
* been answered, a frame that is not JSON-RPC 2.0 (a Cursor or Claude hook payload
|
|
21
|
+
* piped to the entry, a parse error, an oversize line) or an end of stdin ends the
|
|
22
|
+
* process with the no-key refusal on stderr, nothing on stdout, and exit 1. After
|
|
23
|
+
* an initialize, the end of stdin is exit 0.
|
|
24
|
+
*
|
|
25
|
+
* The key text (URL, activate command, plugin steps, the refusal) is owned by cli.ts
|
|
26
|
+
* and passed in; this file holds no second copy of it.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.KEYLESS_NOTICE = void 0;
|
|
30
|
+
exports.keylessInstructions = keylessInstructions;
|
|
31
|
+
exports.keylessStart = keylessStart;
|
|
32
|
+
exports.keylessProtocolVersion = keylessProtocolVersion;
|
|
33
|
+
exports.serveKeylessMcp = serveKeylessMcp;
|
|
34
|
+
const index_1 = require("./index");
|
|
35
|
+
/** The `instructions` string of the initialize answer. */
|
|
36
|
+
function keylessInstructions(parts) {
|
|
37
|
+
const connect = parts.pluginSteps
|
|
38
|
+
? `To connect a Marrow account, the owner follows these steps. ${parts.pluginSteps} `
|
|
39
|
+
: `To connect a Marrow account, the owner creates a key at ${parts.getKeyUrl} and runs ${parts.activateCommand} in their own terminal, then restarts this agent host. `;
|
|
40
|
+
return 'This Marrow server started without a Marrow key on this machine, so it has no tools. '
|
|
41
|
+
+ 'It does not judge, check or record any action. '
|
|
42
|
+
+ (parts.pluginSteps ? '' : 'Marrow hooks, where they are set up on this machine, are separate from this server and keep their free keyless seatbelt; this server does not show whether any are set up. ')
|
|
43
|
+
+ connect
|
|
44
|
+
+ 'A key can not be added from this chat.';
|
|
45
|
+
}
|
|
46
|
+
exports.KEYLESS_NOTICE = 'Marrow: no Marrow key on this machine, so the Marrow MCP server started with no tools. Marrow hooks, where they are set up, keep their free keyless seatbelt.';
|
|
47
|
+
// The real server's protocolVersion rule (cli.ts noteClientCapabilities): the requested version only when it is one
|
|
48
|
+
// of these AND the client's capabilities.elicitation is an object; every other input gets 2024-11-05.
|
|
49
|
+
const ELICITATION_PROTOCOL_VERSIONS = ['2025-06-18', '2025-11-25'];
|
|
50
|
+
const DEFAULT_PROTOCOL_VERSION = '2024-11-05';
|
|
51
|
+
const MAX_LINE_BYTES = 1024 * 1024;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the MCP server starts keyless. `resolved` must be the server's own resolveMarrowEnv() result.
|
|
54
|
+
* True only when it found no key (an empty MARROW_API_KEY or MARROW_KEY counts as none, a whitespace one does
|
|
55
|
+
* not), no owner key file was refused, no `--key` argument is given in any form, no fleet key
|
|
56
|
+
* (MARROW_KEY_<SUFFIX>) is set, and the base URL passes the server's own check.
|
|
57
|
+
*/
|
|
58
|
+
function keylessStart(resolved, argv, env) {
|
|
59
|
+
try {
|
|
60
|
+
if (argv.some((arg) => typeof arg !== 'string' || arg.startsWith('--key')))
|
|
61
|
+
return false;
|
|
62
|
+
if (resolved === null || typeof resolved !== 'object')
|
|
63
|
+
return false;
|
|
64
|
+
const result = resolved;
|
|
65
|
+
if (result.apiKey !== '' || result.missing !== true || result.source !== null || result.ownerFileRefused !== undefined)
|
|
66
|
+
return false;
|
|
67
|
+
// The resolver uses a fleet key only with a direct key and a matching agent identity; one set without them is still a key.
|
|
68
|
+
if (Object.keys(env).some((name) => name.startsWith('MARROW_KEY_') && Boolean(env[name])))
|
|
69
|
+
return false;
|
|
70
|
+
const baseUrl = result.baseUrl || 'https://api.getmarrow.ai';
|
|
71
|
+
if (typeof baseUrl !== 'string')
|
|
72
|
+
return false;
|
|
73
|
+
(0, index_1.validateBaseUrl)(baseUrl);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function keylessProtocolVersion(params) {
|
|
81
|
+
const record = params && typeof params === 'object' ? params : {};
|
|
82
|
+
const capabilities = record.capabilities && typeof record.capabilities === 'object' ? record.capabilities : {};
|
|
83
|
+
const requested = typeof record.protocolVersion === 'string' ? record.protocolVersion : '';
|
|
84
|
+
const elicits = Boolean(capabilities.elicitation && typeof capabilities.elicitation === 'object')
|
|
85
|
+
&& ELICITATION_PROTOCOL_VERSIONS.includes(requested);
|
|
86
|
+
return elicits ? requested : DEFAULT_PROTOCOL_VERSION;
|
|
87
|
+
}
|
|
88
|
+
/** Newline-delimited JSON-RPC 2.0 on stdio with zero tools. Exits 0 when stdin ends after an initialize, 1 for a non-MCP start. */
|
|
89
|
+
function serveKeylessMcp(serverVersion, texts) {
|
|
90
|
+
const has = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
91
|
+
const send = (id, body) => process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, ...body })}\n`);
|
|
92
|
+
const refuse = (id, code, message) => send(id, { error: { code, message } });
|
|
93
|
+
let initialized = false;
|
|
94
|
+
// Today's no-key exit: the refusal on stderr, nothing on stdout, exit 1.
|
|
95
|
+
function exitAsNoKey() {
|
|
96
|
+
try {
|
|
97
|
+
process.stderr.write(texts.refusal);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// a closed stderr changes nothing
|
|
101
|
+
}
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
// A frame that is not a usable JSON-RPC 2.0 message. Before an initialize it proves the process was not started by an MCP host.
|
|
105
|
+
function badFrame(id, code, message) {
|
|
106
|
+
if (!initialized)
|
|
107
|
+
exitAsNoKey();
|
|
108
|
+
refuse(id, code, message);
|
|
109
|
+
}
|
|
110
|
+
function handle(text) {
|
|
111
|
+
let msg;
|
|
112
|
+
try {
|
|
113
|
+
msg = JSON.parse(text);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
badFrame(null, -32700, 'Parse error.');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (msg === null || typeof msg !== 'object' || Array.isArray(msg)) {
|
|
120
|
+
badFrame(null, -32600, 'Invalid request.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const request = msg;
|
|
124
|
+
if (request.jsonrpc !== '2.0' || typeof request.method !== 'string') {
|
|
125
|
+
badFrame(null, -32600, 'Invalid request.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!has(request, 'id'))
|
|
129
|
+
return; // a notification gets no reply
|
|
130
|
+
const id = request.id;
|
|
131
|
+
if (!(typeof id === 'string' || id === null || (typeof id === 'number' && Number.isFinite(id)))) {
|
|
132
|
+
badFrame(null, -32600, 'Invalid request.');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (request.method === 'initialize') {
|
|
136
|
+
initialized = true;
|
|
137
|
+
try {
|
|
138
|
+
process.stderr.write(texts.notice);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// a closed stderr changes nothing
|
|
142
|
+
}
|
|
143
|
+
send(id, {
|
|
144
|
+
result: {
|
|
145
|
+
protocolVersion: keylessProtocolVersion(request.params),
|
|
146
|
+
capabilities: { tools: { listChanged: false } },
|
|
147
|
+
serverInfo: { name: 'marrow', version: serverVersion },
|
|
148
|
+
instructions: texts.instructions,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!initialized) {
|
|
154
|
+
refuse(id, -32002, 'Server not initialized. Send initialize first.');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
switch (request.method) {
|
|
158
|
+
case 'ping':
|
|
159
|
+
send(id, { result: {} });
|
|
160
|
+
return;
|
|
161
|
+
case 'tools/list':
|
|
162
|
+
send(id, { result: { tools: [] } });
|
|
163
|
+
return;
|
|
164
|
+
case 'prompts/list':
|
|
165
|
+
send(id, { result: { prompts: [] } });
|
|
166
|
+
return;
|
|
167
|
+
case 'resources/list':
|
|
168
|
+
send(id, { result: { resources: [] } });
|
|
169
|
+
return;
|
|
170
|
+
case 'resources/templates/list':
|
|
171
|
+
send(id, { result: { resourceTemplates: [] } });
|
|
172
|
+
return;
|
|
173
|
+
case 'tools/call':
|
|
174
|
+
refuse(id, -32602, 'This server has no tools.');
|
|
175
|
+
return;
|
|
176
|
+
default: refuse(id, -32601, 'Method not found.');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function dispatch(chunks) {
|
|
180
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
181
|
+
if (!text.trim())
|
|
182
|
+
return;
|
|
183
|
+
try {
|
|
184
|
+
handle(text);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
badFrame(null, -32603, 'Internal error.');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let chunks = [];
|
|
191
|
+
let size = 0;
|
|
192
|
+
let skipping = false; // inside a line over the limit: dropped up to the next newline, never buffered
|
|
193
|
+
process.stdout.on('error', () => process.exit(initialized ? 0 : 1));
|
|
194
|
+
process.stdin.on('error', () => { process.exitCode = 1; });
|
|
195
|
+
process.stdin.on('data', (data) => {
|
|
196
|
+
const chunk = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
197
|
+
let from = 0;
|
|
198
|
+
while (from < chunk.length) {
|
|
199
|
+
const newline = chunk.indexOf(10, from);
|
|
200
|
+
const to = newline === -1 ? chunk.length : newline;
|
|
201
|
+
if (!skipping) {
|
|
202
|
+
size += to - from;
|
|
203
|
+
if (size > MAX_LINE_BYTES) {
|
|
204
|
+
skipping = true;
|
|
205
|
+
chunks = [];
|
|
206
|
+
size = 0;
|
|
207
|
+
badFrame(null, -32600, 'Request too large.');
|
|
208
|
+
}
|
|
209
|
+
else if (to > from) {
|
|
210
|
+
chunks.push(chunk.subarray(from, to));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (newline === -1)
|
|
214
|
+
return;
|
|
215
|
+
if (!skipping)
|
|
216
|
+
dispatch(chunks);
|
|
217
|
+
chunks = [];
|
|
218
|
+
size = 0;
|
|
219
|
+
skipping = false;
|
|
220
|
+
from = newline + 1;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
process.stdin.on('end', () => {
|
|
224
|
+
if (!skipping)
|
|
225
|
+
dispatch(chunks);
|
|
226
|
+
if (!initialized)
|
|
227
|
+
exitAsNoKey(); // empty stdin, or an end before any initialize: no MCP host ever spoke
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=keyless-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyless-server.js","sourceRoot":"","sources":["../src/keyless-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -129,6 +129,8 @@ export declare function normalizeShellCommand(command: string, deadline?: number
|
|
|
129
129
|
* word ever leaves the machine.
|
|
130
130
|
*/
|
|
131
131
|
export declare function deployCommandText(command: string): string | null;
|
|
132
|
+
/** runOptionsKey as a standalone identity (no deadline), for the exact target of a call (hook-contract exactHookTarget). */
|
|
133
|
+
export declare function shellToolRunOptionsKey(event: ToolEvent): string | null;
|
|
132
134
|
/**
|
|
133
135
|
* The normalized action of a hook event. `deadlineAt` bounds the time it may
|
|
134
136
|
* take (default one second from now): past it, or past the size limits, the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalized-action.d.ts","sourceRoot":"","sources":["../src/normalized-action.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"normalized-action.d.ts","sourceRoot":"","sources":["../src/normalized-action.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7J,yGAAyG;IACzG,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC;AAMF,yHAAyH;AACzH,eAAO,MAAM,cAAc,QAAS,CAAC;AAWrC,KAAK,SAAS,GAAG;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA8C/D,4GAA4G;AAC5G,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAUnD;AAgDD,KAAK,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAW1D;AAm5BD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAWjJ;AA4HD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyChE;AA2BD,4HAA4H;AAC5H,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAEtE;AAkLD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GAAG,oBAAoB,CAgCxI"}
|
|
@@ -5,6 +5,7 @@ exports.looksLikeKey = looksLikeKey;
|
|
|
5
5
|
exports.classifySecretName = classifySecretName;
|
|
6
6
|
exports.normalizeShellCommand = normalizeShellCommand;
|
|
7
7
|
exports.deployCommandText = deployCommandText;
|
|
8
|
+
exports.shellToolRunOptionsKey = shellToolRunOptionsKey;
|
|
8
9
|
exports.normalizedHookAction = normalizedHookAction;
|
|
9
10
|
const node_crypto_1 = require("node:crypto");
|
|
10
11
|
const node_os_1 = require("node:os");
|
|
@@ -1194,8 +1195,9 @@ function redactedInput(value, state, depth = 0) {
|
|
|
1194
1195
|
function untrimmedShellText(event, trimmed) {
|
|
1195
1196
|
const input = event.tool_input && typeof event.tool_input === 'object' && !Array.isArray(event.tool_input)
|
|
1196
1197
|
? event.tool_input : null;
|
|
1197
|
-
const host =
|
|
1198
|
-
|
|
1198
|
+
const host = (0, hook_tool_policy_1.shellCommandToolText)(event)
|
|
1199
|
+
?? (typeof event.tool_input === 'string' ? event.tool_input
|
|
1200
|
+
: typeof input?.command === 'string' ? input.command : typeof input?.cmd === 'string' ? input.cmd : null);
|
|
1199
1201
|
if (host === null || host === trimmed)
|
|
1200
1202
|
return null;
|
|
1201
1203
|
const at = host.indexOf(trimmed);
|
|
@@ -1210,6 +1212,10 @@ function untrimmedShellText(event, trimmed) {
|
|
|
1210
1212
|
* would carry, so both give the same normalized action.
|
|
1211
1213
|
*/
|
|
1212
1214
|
function shellCommand(event) {
|
|
1215
|
+
// A plain Monitor or PowerShell call: its one command or script text, read as Bash's command.
|
|
1216
|
+
const toolText = (0, hook_tool_policy_1.shellCommandToolText)(event);
|
|
1217
|
+
if (toolText !== null)
|
|
1218
|
+
return toolText.trim();
|
|
1213
1219
|
const input = event.tool_input && typeof event.tool_input === 'object' && !Array.isArray(event.tool_input)
|
|
1214
1220
|
? event.tool_input : null;
|
|
1215
1221
|
const argv = Array.isArray(input?.command) ? input.command : Array.isArray(input?.cmd) ? input.cmd : null;
|
|
@@ -1329,6 +1335,28 @@ function inputHash(input, state) {
|
|
|
1329
1335
|
state.truncated = true; // exact: n/a (pass 1 only; exactMaterial is the exact form)
|
|
1330
1336
|
return { input_sha256: sha256(`${HASH_VERSION}\ninput\n${text}`) };
|
|
1331
1337
|
}
|
|
1338
|
+
/**
|
|
1339
|
+
* The run options of a Monitor or PowerShell call (timeout, timeout_ms, run_in_background) as key material:
|
|
1340
|
+
* the hash of their canonical JSON with credentials replaced, as any tool input is hashed (so a replaced
|
|
1341
|
+
* credential marks the action truncated here too), or null when the call sets none (and for any other tool).
|
|
1342
|
+
* They decide how long and where the command runs, so an approval of one set is not an approval of another;
|
|
1343
|
+
* `description` is not key material.
|
|
1344
|
+
*/
|
|
1345
|
+
function runOptionsKey(event, state) {
|
|
1346
|
+
const options = (0, hook_tool_policy_1.shellCommandToolRunOptions)(event);
|
|
1347
|
+
if (options === null)
|
|
1348
|
+
return null;
|
|
1349
|
+
return inputHash(options, state).input_sha256;
|
|
1350
|
+
}
|
|
1351
|
+
/** runOptionsKey as a standalone identity (no deadline), for the exact target of a call (hook-contract exactHookTarget). */
|
|
1352
|
+
function shellToolRunOptionsKey(event) {
|
|
1353
|
+
return runOptionsKey(event, newExactness(Number.POSITIVE_INFINITY));
|
|
1354
|
+
}
|
|
1355
|
+
/** The exact form of the run options (only certain credentials replaced), for the exact material of a truncated action. */
|
|
1356
|
+
function exactRunOptions(event, state) {
|
|
1357
|
+
const options = (0, hook_tool_policy_1.shellCommandToolRunOptions)(event);
|
|
1358
|
+
return options === null ? null : JSON.stringify(sortedValue(redactedInput(options, state)));
|
|
1359
|
+
}
|
|
1332
1360
|
function withExactness(action, state) {
|
|
1333
1361
|
return state.truncated ? { ...action, truncated: true } : action;
|
|
1334
1362
|
}
|
|
@@ -1433,13 +1461,22 @@ function shellReferences(command) {
|
|
|
1433
1461
|
function exactShellForm(text, command, state) {
|
|
1434
1462
|
return `${text}\n${JSON.stringify(shellReferences(command))}\n${JSON.stringify(state.sigs)}`;
|
|
1435
1463
|
}
|
|
1464
|
+
/** The raw shell text as material; a plain Monitor or PowerShell call also carries its run options (raw). */
|
|
1465
|
+
function rawShellMaterial(event, command) {
|
|
1466
|
+
const options = (0, hook_tool_policy_1.shellCommandToolText)(event) === null
|
|
1467
|
+
? null : (0, hook_tool_policy_1.shellCommandToolRunOptions)(event);
|
|
1468
|
+
if (options === null)
|
|
1469
|
+
return `raw-shell\n${command}`;
|
|
1470
|
+
const json = canonicalJson(options);
|
|
1471
|
+
return json === null ? null : `raw-shell+run\n${json}\n${command}`;
|
|
1472
|
+
}
|
|
1436
1473
|
/** The raw input as material (a placeholder, or an exact pass that hit a limit): the command, or the canonical input. */
|
|
1437
1474
|
function rawExactMaterial(event, kind) {
|
|
1438
1475
|
if (kind === 'shell') {
|
|
1439
1476
|
try {
|
|
1440
1477
|
const trimmed = shellCommand(event);
|
|
1441
1478
|
const command = untrimmedShellText(event, trimmed) ?? trimmed;
|
|
1442
|
-
return typeof command === 'string' && command.length <= MAX_RAW_EXACT_CHARS ?
|
|
1479
|
+
return typeof command === 'string' && command.length <= MAX_RAW_EXACT_CHARS ? rawShellMaterial(event, command) : null;
|
|
1443
1480
|
}
|
|
1444
1481
|
catch {
|
|
1445
1482
|
return null;
|
|
@@ -1460,10 +1497,14 @@ function exactMaterial(event, kind, deadline) {
|
|
|
1460
1497
|
if (kind === 'shell') {
|
|
1461
1498
|
const trimmed = shellCommand(event);
|
|
1462
1499
|
const host = untrimmedShellText(event, trimmed);
|
|
1463
|
-
if (host !== null)
|
|
1464
|
-
|
|
1500
|
+
if (host !== null) {
|
|
1501
|
+
const raw = rawShellMaterial(event, host);
|
|
1502
|
+
return raw === null ? null : bounded(raw);
|
|
1503
|
+
}
|
|
1465
1504
|
const { text } = normalizeInner(trimmed, state, 0);
|
|
1466
|
-
|
|
1505
|
+
const runOptions = (0, hook_tool_policy_1.shellCommandToolText)(event) !== null
|
|
1506
|
+
? exactRunOptions(event, newExactness(deadline, true)) : null;
|
|
1507
|
+
return bounded(`exact-shell${runOptions === null ? '' : `+run\n${runOptions}`}\n${exactShellForm(text, trimmed, state)}`);
|
|
1467
1508
|
}
|
|
1468
1509
|
const input = event.tool_input;
|
|
1469
1510
|
const text = input === undefined || input === null ? 'null' : JSON.stringify(sortedValue(redactedInput(input, state)));
|
|
@@ -1528,10 +1569,14 @@ function normalizedHookAction(event, options = {}) {
|
|
|
1528
1569
|
const hostToolName = rawToolName.slice(0, 128);
|
|
1529
1570
|
if (rawToolName.length > 128)
|
|
1530
1571
|
state.truncated = true; // exact: kept (the full name is in exact_hmac)
|
|
1572
|
+
// Claude Code built-ins first hooked in 3.9.105 that are not plain shell calls (a native git
|
|
1573
|
+
// equivalent, Skill, a call whose words are not actions, an irregular Monitor or PowerShell call)
|
|
1574
|
+
// are keyed by their whole input, never by one field of it.
|
|
1531
1575
|
const kind = (0, hook_tool_policy_1.isMcpHookTool)(event.tool_name) || /^MCP:/i.test(hostToolName) ? 'mcp'
|
|
1532
|
-
: (0, hook_tool_policy_1.
|
|
1533
|
-
:
|
|
1534
|
-
: '
|
|
1576
|
+
: (0, hook_tool_policy_1.isWholeInputNativeTool)(event) ? 'other'
|
|
1577
|
+
: (0, hook_tool_policy_1.isShellGovernedTool)(event) ? 'shell'
|
|
1578
|
+
: EDIT_TOOLS.test(toolName) ? 'edit'
|
|
1579
|
+
: 'other';
|
|
1535
1580
|
const inputCut = event.input_truncated === true;
|
|
1536
1581
|
if (inputCut)
|
|
1537
1582
|
state.truncated = true; // exact: nonce (the cut or dropped input is all that is left)
|
|
@@ -1569,13 +1614,23 @@ function normalizedOfKind(event, kind, hostToolName, state) {
|
|
|
1569
1614
|
const input = event.tool_input && typeof event.tool_input === 'object' && !Array.isArray(event.tool_input) ? event.tool_input : null;
|
|
1570
1615
|
const exact = typeof input?.command === 'string' ? input.command : typeof input?.cmd === 'string' ? input.cmd : raw;
|
|
1571
1616
|
const deployText = state.truncated ? null : deployCommandText(exact);
|
|
1617
|
+
// A Monitor or PowerShell command carries its host tool name, so it never shares a hash with the
|
|
1618
|
+
// same Bash command (or another tool's): the line after the version is `shell` only for every
|
|
1619
|
+
// other shell call, whose hash is unchanged.
|
|
1620
|
+
// Its run options (timeout, timeout_ms, run_in_background), when it sets any, are in the hash too: a
|
|
1621
|
+
// different line after `shell-tool+run`, so no command text can stand for them.
|
|
1622
|
+
const plainShellTool = (0, hook_tool_policy_1.shellCommandToolText)(event) !== null;
|
|
1623
|
+
const runOptions = plainShellTool ? runOptionsKey(event, state) : null;
|
|
1624
|
+
const domain = !plainShellTool ? 'shell'
|
|
1625
|
+
: runOptions === null ? `shell-tool\n${JSON.stringify(hostToolName)}`
|
|
1626
|
+
: `shell-tool+run\n${JSON.stringify(hostToolName)}\n${runOptions}`;
|
|
1572
1627
|
return withExactness({
|
|
1573
1628
|
tool_kind: 'shell',
|
|
1574
1629
|
tool_name: hostToolName,
|
|
1575
1630
|
programs,
|
|
1576
1631
|
tool_input: {
|
|
1577
1632
|
// An exact action also hashes its quoting (never for a truncated one: a secret's characters stay out of it).
|
|
1578
|
-
command_sha256: sha256(`${HASH_VERSION}\
|
|
1633
|
+
command_sha256: sha256(`${HASH_VERSION}\n${domain}\n${state.truncated || !state.sigs.length ? text : `${text}\n${JSON.stringify(state.sigs)}`}`),
|
|
1579
1634
|
...(deployText !== null && deployText === text ? { command: deployText } : {}),
|
|
1580
1635
|
},
|
|
1581
1636
|
}, state);
|