zero-config-cli-bridge 1.5.0 → 2.0.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.
- package/dist/approval.d.ts +15 -0
- package/dist/approval.d.ts.map +1 -0
- package/dist/approval.js +48 -0
- package/dist/approval.js.map +1 -0
- package/dist/index.js +37 -39
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +6 -15
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +140 -35
- package/dist/schema.js.map +1 -1
- package/dist/security.d.ts +11 -4
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +20 -10
- package/dist/security.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True server-side Human-in-the-Loop gate.
|
|
3
|
+
*
|
|
4
|
+
* The MCP connection stays pending (agent receives nothing) until a human
|
|
5
|
+
* physically types 'y' at the terminal where this server runs.
|
|
6
|
+
*
|
|
7
|
+
* Uses /dev/tty for input — stdin is occupied by the MCP protocol and must
|
|
8
|
+
* not be consumed. /dev/tty provides direct TTY access regardless of how
|
|
9
|
+
* stdin/stdout are redirected, exactly as sudo(8) and ssh(1) do.
|
|
10
|
+
*
|
|
11
|
+
* If no TTY is available (headless server, CI environment), the request is
|
|
12
|
+
* denied by default — fail-closed, not fail-open.
|
|
13
|
+
*/
|
|
14
|
+
export declare function requestApproval(preview: string): Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwCvE"}
|
package/dist/approval.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createReadStream } from 'fs';
|
|
2
|
+
import { createInterface } from 'readline';
|
|
3
|
+
/**
|
|
4
|
+
* True server-side Human-in-the-Loop gate.
|
|
5
|
+
*
|
|
6
|
+
* The MCP connection stays pending (agent receives nothing) until a human
|
|
7
|
+
* physically types 'y' at the terminal where this server runs.
|
|
8
|
+
*
|
|
9
|
+
* Uses /dev/tty for input — stdin is occupied by the MCP protocol and must
|
|
10
|
+
* not be consumed. /dev/tty provides direct TTY access regardless of how
|
|
11
|
+
* stdin/stdout are redirected, exactly as sudo(8) and ssh(1) do.
|
|
12
|
+
*
|
|
13
|
+
* If no TTY is available (headless server, CI environment), the request is
|
|
14
|
+
* denied by default — fail-closed, not fail-open.
|
|
15
|
+
*/
|
|
16
|
+
export async function requestApproval(preview) {
|
|
17
|
+
process.stderr.write('\n\x1b[33m━━━ APPROVAL REQUIRED ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n' +
|
|
18
|
+
'\x1b[33mAn agent wants to execute a write operation:\x1b[0m\n\n' +
|
|
19
|
+
` \x1b[1m${preview}\x1b[0m\n\n` +
|
|
20
|
+
'Type \x1b[32my\x1b[0m to approve, anything else to deny: ');
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
let tty = null;
|
|
23
|
+
try {
|
|
24
|
+
tty = createReadStream('/dev/tty');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
process.stderr.write('\x1b[31mNo TTY available — denied by default.\x1b[0m\n');
|
|
28
|
+
resolve(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const rl = createInterface({ input: tty });
|
|
32
|
+
rl.once('line', (line) => {
|
|
33
|
+
rl.close();
|
|
34
|
+
tty?.destroy();
|
|
35
|
+
const approved = line.trim().toLowerCase() === 'y';
|
|
36
|
+
process.stderr.write(approved
|
|
37
|
+
? '\x1b[32m✓ Approved — executing.\x1b[0m\n'
|
|
38
|
+
: '\x1b[31m✗ Denied — operation cancelled.\x1b[0m\n');
|
|
39
|
+
resolve(approved);
|
|
40
|
+
});
|
|
41
|
+
tty.on('error', () => {
|
|
42
|
+
rl.close();
|
|
43
|
+
process.stderr.write('\x1b[31mTTY error — denied by default.\x1b[0m\n');
|
|
44
|
+
resolve(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,IAAI,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe;IACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,6EAA6E;QAC7E,iEAAiE;QACjE,YAAY,OAAO,aAAa;QAChC,2DAA2D,CAC5D,CAAC;IAEF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,GAAG,GAA+C,IAAI,CAAC;QAE3D,IAAI,CAAC;YACH,GAAG,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;YAC/E,OAAO,CAAC,KAAK,CAAC,CAAC;YACf,OAAO;QACT,CAAC;QAED,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAE3C,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,EAAE,OAAO,EAAE,CAAC;YAEf,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC;YACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,QAAQ;gBACN,CAAC,CAAC,0CAA0C;gBAC5C,CAAC,CAAC,kDAAkD,CACvD,CAAC;YACF,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACnB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACxE,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,14 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { executeCommand } from './executor.js';
|
|
6
|
-
import {
|
|
7
|
-
import { buildToolDefinitions, buildGhArgs } from './schema.js';
|
|
6
|
+
import { getOperationTier, validateArgs } from './security.js';
|
|
7
|
+
import { buildToolDefinitions, buildGhArgs, buildCommandPreview } from './schema.js';
|
|
8
|
+
import { requestApproval } from './approval.js';
|
|
8
9
|
const MAX_JSON_ITEMS = 30;
|
|
9
|
-
const MAX_SERIALISED_CHARS = 200_000;
|
|
10
|
+
const MAX_SERIALISED_CHARS = 200_000;
|
|
10
11
|
/**
|
|
11
|
-
* Converts gh stdout (JSON) into a bounded
|
|
12
|
-
* Called only
|
|
13
|
-
*
|
|
14
|
-
* stdout is passed unmodified from executor — no byte-level truncation
|
|
15
|
-
* has occurred. item-level truncation is the sole guard here.
|
|
12
|
+
* Converts gh stdout (always JSON when --json flag is used) into a bounded envelope.
|
|
13
|
+
* Called only on exitCode === 0.
|
|
16
14
|
*/
|
|
17
15
|
function stdoutToEnvelope(stdout) {
|
|
18
16
|
if (!stdout.trim()) {
|
|
@@ -23,16 +21,11 @@ function stdoutToEnvelope(stdout) {
|
|
|
23
21
|
parsed = JSON.parse(stdout);
|
|
24
22
|
}
|
|
25
23
|
catch {
|
|
26
|
-
// gh returned non-JSON despite --json flag (should not happen in normal operation)
|
|
27
24
|
return {
|
|
28
25
|
data: null,
|
|
29
|
-
meta: {
|
|
30
|
-
truncated: false,
|
|
31
|
-
error: `Unexpected non-JSON output from gh: ${stdout.slice(0, 200)}`,
|
|
32
|
-
},
|
|
26
|
+
meta: { truncated: false, error: `Unexpected non-JSON output: ${stdout.slice(0, 200)}` },
|
|
33
27
|
};
|
|
34
28
|
}
|
|
35
|
-
// Array response: truncate at item level — primary case for list commands
|
|
36
29
|
if (Array.isArray(parsed)) {
|
|
37
30
|
const truncated = parsed.length > MAX_JSON_ITEMS;
|
|
38
31
|
const data = truncated ? parsed.slice(0, MAX_JSON_ITEMS) : parsed;
|
|
@@ -47,37 +40,33 @@ function stdoutToEnvelope(stdout) {
|
|
|
47
40
|
},
|
|
48
41
|
};
|
|
49
42
|
}
|
|
50
|
-
// Non-array
|
|
43
|
+
// Non-array (e.g. single created resource from write command)
|
|
51
44
|
const serialised = JSON.stringify(parsed);
|
|
52
45
|
if (serialised.length > MAX_SERIALISED_CHARS) {
|
|
53
46
|
return {
|
|
54
47
|
data: null,
|
|
55
|
-
meta: {
|
|
56
|
-
truncated: true,
|
|
57
|
-
error: `Response object too large (${serialised.length} chars). Use filters to narrow results.`,
|
|
58
|
-
},
|
|
48
|
+
meta: { truncated: true, error: `Response too large (${serialised.length} chars).` },
|
|
59
49
|
};
|
|
60
50
|
}
|
|
61
51
|
return { data: parsed, meta: { truncated: false } };
|
|
62
52
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Wraps an error string in the standard envelope.
|
|
65
|
-
* stderr is already bounded to 4KB by executor.
|
|
66
|
-
*/
|
|
67
53
|
function stderrToEnvelope(stderr, stdout) {
|
|
68
54
|
const error = (stderr || stdout || 'Command failed with no output').trim();
|
|
69
55
|
return { data: null, meta: { truncated: false, error } };
|
|
70
56
|
}
|
|
71
|
-
function
|
|
57
|
+
function envelopeResponse(envelope, isError) {
|
|
72
58
|
return {
|
|
73
59
|
content: [{ type: 'text', text: JSON.stringify(envelope, null, 2) }],
|
|
74
60
|
isError,
|
|
75
61
|
};
|
|
76
62
|
}
|
|
77
|
-
|
|
63
|
+
function errorEnvelope(message) {
|
|
64
|
+
return envelopeResponse({ data: null, meta: { truncated: false, error: message } }, true);
|
|
65
|
+
}
|
|
66
|
+
// Tool registry populated synchronously at startup — no subprocess overhead.
|
|
78
67
|
const tools = buildToolDefinitions();
|
|
79
68
|
const toolRegistry = new Map(tools.map((t) => [t.name, t]));
|
|
80
|
-
const server = new Server({ name: 'zero-config-cli-bridge', version: '
|
|
69
|
+
const server = new Server({ name: 'zero-config-cli-bridge', version: '2.0.0' }, { capabilities: { tools: {} } });
|
|
81
70
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
82
71
|
tools: Array.from(toolRegistry.values()).map(({ name, description, inputSchema }) => ({
|
|
83
72
|
name,
|
|
@@ -89,33 +78,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
89
78
|
const { name: toolName, arguments: rawArgs } = request.params;
|
|
90
79
|
const args = (rawArgs ?? {});
|
|
91
80
|
const tool = toolRegistry.get(toolName);
|
|
92
|
-
if (!tool)
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
if (!tool)
|
|
82
|
+
return errorEnvelope(`Unknown tool "${toolName}".`);
|
|
83
|
+
// Security: verify subcommand is in allow-list and get its tier
|
|
84
|
+
let tier;
|
|
96
85
|
try {
|
|
97
|
-
validateSubcommand(tool.subcommand.join(' '));
|
|
98
86
|
validateArgs(args);
|
|
87
|
+
tier = getOperationTier(tool.subcommand.join(' '));
|
|
99
88
|
}
|
|
100
89
|
catch (err) {
|
|
101
|
-
return
|
|
90
|
+
return errorEnvelope(err instanceof Error ? err.message : String(err));
|
|
91
|
+
}
|
|
92
|
+
// Tier 3: never executes (not exposed as tools, but guard defensively)
|
|
93
|
+
if (tier === 3) {
|
|
94
|
+
return errorEnvelope('Irreversible operations are not permitted.');
|
|
95
|
+
}
|
|
96
|
+
// Tier 2: block until human physically approves at the terminal
|
|
97
|
+
if (tier === 2) {
|
|
98
|
+
const preview = buildCommandPreview(tool, args);
|
|
99
|
+
const approved = await requestApproval(preview);
|
|
100
|
+
if (!approved) {
|
|
101
|
+
return errorEnvelope('Operation denied by human operator.');
|
|
102
|
+
}
|
|
102
103
|
}
|
|
103
|
-
//
|
|
104
|
+
// Execute — direct spawn, no shell
|
|
104
105
|
const ghArgs = buildGhArgs(tool, args);
|
|
105
106
|
let result;
|
|
106
107
|
try {
|
|
107
108
|
result = await executeCommand('gh', ghArgs);
|
|
108
109
|
}
|
|
109
110
|
catch (err) {
|
|
110
|
-
return
|
|
111
|
+
return errorEnvelope(`Execution error: ${err instanceof Error ? err.message : String(err)}`);
|
|
111
112
|
}
|
|
112
|
-
// stdout and stderr are semantically distinct:
|
|
113
|
-
// exitCode === 0 → stdout is structured JSON data; stderr is ignored warnings
|
|
114
|
-
// exitCode !== 0 → stderr is the error message; stdout is typically empty
|
|
115
113
|
if (result.exitCode !== 0) {
|
|
116
|
-
return
|
|
114
|
+
return envelopeResponse(stderrToEnvelope(result.stderr, result.stdout), true);
|
|
117
115
|
}
|
|
118
|
-
return
|
|
116
|
+
return envelopeResponse(stdoutToEnvelope(result.stdout), false);
|
|
119
117
|
});
|
|
120
118
|
async function main() {
|
|
121
119
|
const transport = new StdioServerTransport();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Schema definitions for exposed gh tools.
|
|
3
|
-
*
|
|
4
|
-
* Field list design:
|
|
5
|
-
* STATIC_FIELDS is the authoritative source of truth.
|
|
6
|
-
* It contains only fields that work with the standard `repo` OAuth scope
|
|
7
|
-
* and have been stable across gh major versions.
|
|
8
|
-
*
|
|
9
|
-
* The probe (detectJsonFields) is NOT called at startup.
|
|
10
|
-
* Rationale: the probe has no reliability guarantee — gh's output format
|
|
11
|
-
* is human-readable and can change with any release. Running an unreliable
|
|
12
|
-
* subprocess at every server start adds latency and timeout risk for zero
|
|
13
|
-
* guaranteed gain. Static fields are the correct default.
|
|
14
|
-
*/
|
|
1
|
+
import type { OperationTier } from './security.js';
|
|
15
2
|
export interface ToolDefinition {
|
|
16
3
|
name: string;
|
|
17
4
|
description: string;
|
|
@@ -24,7 +11,9 @@ export interface ToolDefinition {
|
|
|
24
11
|
required?: string[];
|
|
25
12
|
};
|
|
26
13
|
subcommand: string[];
|
|
27
|
-
|
|
14
|
+
tier: OperationTier;
|
|
15
|
+
/** For read (Tier 0) tools: --json fields to request */
|
|
16
|
+
jsonFields?: string[];
|
|
28
17
|
}
|
|
29
18
|
export declare function buildToolDefinitions(): ToolDefinition[];
|
|
30
19
|
/**
|
|
@@ -33,4 +22,6 @@ export declare function buildToolDefinitions(): ToolDefinition[];
|
|
|
33
22
|
* misinterpreted as a separate flag by gh's argument parser.
|
|
34
23
|
*/
|
|
35
24
|
export declare function buildGhArgs(tool: ToolDefinition, args: Record<string, unknown>): string[];
|
|
25
|
+
/** Human-readable preview of the command an agent intends to execute */
|
|
26
|
+
export declare function buildCommandPreview(tool: ToolDefinition, args: Record<string, unknown>): string;
|
|
36
27
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,aAAa,CAAC;IACpB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAwBD,wBAAgB,oBAAoB,IAAI,cAAc,EAAE,CA0GvD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAkDzF;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE/F"}
|
package/dist/schema.js
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* Schema definitions for exposed gh tools.
|
|
3
|
-
*
|
|
4
|
-
* Field list design:
|
|
5
|
-
* STATIC_FIELDS is the authoritative source of truth.
|
|
6
|
-
* It contains only fields that work with the standard `repo` OAuth scope
|
|
7
|
-
* and have been stable across gh major versions.
|
|
8
|
-
*
|
|
9
|
-
* The probe (detectJsonFields) is NOT called at startup.
|
|
10
|
-
* Rationale: the probe has no reliability guarantee — gh's output format
|
|
11
|
-
* is human-readable and can change with any release. Running an unreliable
|
|
12
|
-
* subprocess at every server start adds latency and timeout risk for zero
|
|
13
|
-
* guaranteed gain. Static fields are the correct default.
|
|
14
|
-
*/
|
|
15
|
-
// Fields verified against `gh issue list --json` and `gh pr list --json` output.
|
|
1
|
+
// Fields verified against `gh issue list --json` and `gh pr list --json`.
|
|
16
2
|
// Excludes: `id` (requires read:project scope), `body` (unbounded text).
|
|
17
|
-
const
|
|
3
|
+
const READ_FIELDS = {
|
|
18
4
|
'issue list': [
|
|
19
5
|
'number', 'title', 'state', 'labels', 'assignees',
|
|
20
6
|
'author', 'createdAt', 'updatedAt', 'closedAt', 'url',
|
|
@@ -26,15 +12,22 @@ const STATIC_FIELDS = {
|
|
|
26
12
|
'baseRefName', 'headRefName', 'isDraft', 'mergedAt', 'reviewDecision',
|
|
27
13
|
],
|
|
28
14
|
};
|
|
15
|
+
// Fields returned by write commands (small, bounded — single created resource)
|
|
16
|
+
const WRITE_RESPONSE_FIELDS = {
|
|
17
|
+
'issue create': ['number', 'url', 'title', 'state'],
|
|
18
|
+
'pr create': ['number', 'url', 'title', 'state', 'baseRefName', 'headRefName'],
|
|
19
|
+
'issue comment': ['url', 'body'],
|
|
20
|
+
};
|
|
29
21
|
export function buildToolDefinitions() {
|
|
30
22
|
return [
|
|
23
|
+
// ── Tier 0: Read ────────────────────────────────────────────────────────
|
|
31
24
|
{
|
|
32
25
|
name: 'gh_issue_list',
|
|
33
26
|
description: 'List GitHub issues as structured JSON. ' +
|
|
34
|
-
'
|
|
35
|
-
'Read-only. Does not create, edit, or delete issues.',
|
|
27
|
+
'Read-only — executes immediately without approval.',
|
|
36
28
|
subcommand: ['issue', 'list'],
|
|
37
|
-
|
|
29
|
+
tier: 0,
|
|
30
|
+
jsonFields: READ_FIELDS['issue list'],
|
|
38
31
|
inputSchema: {
|
|
39
32
|
type: 'object',
|
|
40
33
|
properties: {
|
|
@@ -49,10 +42,10 @@ export function buildToolDefinitions() {
|
|
|
49
42
|
{
|
|
50
43
|
name: 'gh_pr_list',
|
|
51
44
|
description: 'List GitHub pull requests as structured JSON. ' +
|
|
52
|
-
'
|
|
53
|
-
'Read-only. Does not create, edit, merge, or close pull requests.',
|
|
45
|
+
'Read-only — executes immediately without approval.',
|
|
54
46
|
subcommand: ['pr', 'list'],
|
|
55
|
-
|
|
47
|
+
tier: 0,
|
|
48
|
+
jsonFields: READ_FIELDS['pr list'],
|
|
56
49
|
inputSchema: {
|
|
57
50
|
type: 'object',
|
|
58
51
|
properties: {
|
|
@@ -64,6 +57,66 @@ export function buildToolDefinitions() {
|
|
|
64
57
|
},
|
|
65
58
|
},
|
|
66
59
|
},
|
|
60
|
+
// ── Tier 2: Write (requires human TTY approval) ──────────────────────────
|
|
61
|
+
{
|
|
62
|
+
name: 'gh_issue_create',
|
|
63
|
+
description: 'Create a GitHub issue. ' +
|
|
64
|
+
'⚠️ WRITE OPERATION — blocks until a human approves at the terminal. ' +
|
|
65
|
+
'Uses local `gh` authentication. Requires title.',
|
|
66
|
+
subcommand: ['issue', 'create'],
|
|
67
|
+
tier: 2,
|
|
68
|
+
jsonFields: WRITE_RESPONSE_FIELDS['issue create'],
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
title: { type: 'string', description: 'Issue title (required).' },
|
|
73
|
+
body: { type: 'string', description: 'Issue body text.' },
|
|
74
|
+
repo: { type: 'string', description: 'OWNER/REPO. Omit to use current directory.' },
|
|
75
|
+
label: { type: 'string', description: 'Label name to apply.' },
|
|
76
|
+
assignee: { type: 'string', description: 'Assignee login.' },
|
|
77
|
+
},
|
|
78
|
+
required: ['title'],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'gh_pr_create',
|
|
83
|
+
description: 'Create a GitHub pull request. ' +
|
|
84
|
+
'⚠️ WRITE OPERATION — blocks until a human approves at the terminal. ' +
|
|
85
|
+
'Uses local `gh` authentication. Requires title.',
|
|
86
|
+
subcommand: ['pr', 'create'],
|
|
87
|
+
tier: 2,
|
|
88
|
+
jsonFields: WRITE_RESPONSE_FIELDS['pr create'],
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
title: { type: 'string', description: 'PR title (required).' },
|
|
93
|
+
body: { type: 'string', description: 'PR body text.' },
|
|
94
|
+
base: { type: 'string', description: 'Base branch (default: repo default branch).' },
|
|
95
|
+
head: { type: 'string', description: 'Head branch (default: current branch).' },
|
|
96
|
+
repo: { type: 'string', description: 'OWNER/REPO. Omit to use current directory.' },
|
|
97
|
+
draft: { type: 'boolean', description: 'Open as draft PR.' },
|
|
98
|
+
},
|
|
99
|
+
required: ['title'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'gh_issue_comment',
|
|
104
|
+
description: 'Add a comment to a GitHub issue. ' +
|
|
105
|
+
'⚠️ WRITE OPERATION — blocks until a human approves at the terminal. ' +
|
|
106
|
+
'Uses local `gh` authentication. Requires issue number and body.',
|
|
107
|
+
subcommand: ['issue', 'comment'],
|
|
108
|
+
tier: 2,
|
|
109
|
+
jsonFields: WRITE_RESPONSE_FIELDS['issue comment'],
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
issue: { type: 'number', description: 'Issue number (required).' },
|
|
114
|
+
body: { type: 'string', description: 'Comment text (required).' },
|
|
115
|
+
repo: { type: 'string', description: 'OWNER/REPO. Omit to use current directory.' },
|
|
116
|
+
},
|
|
117
|
+
required: ['issue', 'body'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
67
120
|
];
|
|
68
121
|
}
|
|
69
122
|
/**
|
|
@@ -72,19 +125,71 @@ export function buildToolDefinitions() {
|
|
|
72
125
|
* misinterpreted as a separate flag by gh's argument parser.
|
|
73
126
|
*/
|
|
74
127
|
export function buildGhArgs(tool, args) {
|
|
75
|
-
const parts = [...tool.subcommand
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
128
|
+
const parts = [...tool.subcommand];
|
|
129
|
+
// All tools request JSON output for structured responses
|
|
130
|
+
if (tool.jsonFields && tool.jsonFields.length > 0) {
|
|
131
|
+
parts.push(`--json=${tool.jsonFields.join(',')}`);
|
|
132
|
+
}
|
|
133
|
+
switch (tool.name) {
|
|
134
|
+
case 'gh_issue_list':
|
|
135
|
+
if (args['repo'])
|
|
136
|
+
parts.push(`--repo=${String(args['repo'])}`);
|
|
137
|
+
if (args['limit'])
|
|
138
|
+
parts.push(`--limit=${String(args['limit'])}`);
|
|
139
|
+
if (args['state'])
|
|
140
|
+
parts.push(`--state=${String(args['state'])}`);
|
|
141
|
+
if (args['label'])
|
|
142
|
+
parts.push(`--label=${String(args['label'])}`);
|
|
143
|
+
if (args['assignee'])
|
|
144
|
+
parts.push(`--assignee=${String(args['assignee'])}`);
|
|
145
|
+
break;
|
|
146
|
+
case 'gh_pr_list':
|
|
147
|
+
if (args['repo'])
|
|
148
|
+
parts.push(`--repo=${String(args['repo'])}`);
|
|
149
|
+
if (args['limit'])
|
|
150
|
+
parts.push(`--limit=${String(args['limit'])}`);
|
|
151
|
+
if (args['state'])
|
|
152
|
+
parts.push(`--state=${String(args['state'])}`);
|
|
153
|
+
if (args['base'])
|
|
154
|
+
parts.push(`--base=${String(args['base'])}`);
|
|
155
|
+
if (args['assignee'])
|
|
156
|
+
parts.push(`--assignee=${String(args['assignee'])}`);
|
|
157
|
+
break;
|
|
158
|
+
case 'gh_issue_create':
|
|
159
|
+
parts.push(`--title=${String(args['title'])}`);
|
|
160
|
+
if (args['body'])
|
|
161
|
+
parts.push(`--body=${String(args['body'])}`);
|
|
162
|
+
if (args['repo'])
|
|
163
|
+
parts.push(`--repo=${String(args['repo'])}`);
|
|
164
|
+
if (args['label'])
|
|
165
|
+
parts.push(`--label=${String(args['label'])}`);
|
|
166
|
+
if (args['assignee'])
|
|
167
|
+
parts.push(`--assignee=${String(args['assignee'])}`);
|
|
168
|
+
break;
|
|
169
|
+
case 'gh_pr_create':
|
|
170
|
+
parts.push(`--title=${String(args['title'])}`);
|
|
171
|
+
if (args['body'])
|
|
172
|
+
parts.push(`--body=${String(args['body'])}`);
|
|
173
|
+
if (args['base'])
|
|
174
|
+
parts.push(`--base=${String(args['base'])}`);
|
|
175
|
+
if (args['head'])
|
|
176
|
+
parts.push(`--head=${String(args['head'])}`);
|
|
177
|
+
if (args['repo'])
|
|
178
|
+
parts.push(`--repo=${String(args['repo'])}`);
|
|
179
|
+
if (args['draft'])
|
|
180
|
+
parts.push('--draft');
|
|
181
|
+
break;
|
|
182
|
+
case 'gh_issue_comment':
|
|
183
|
+
parts.push(String(args['issue']));
|
|
184
|
+
parts.push(`--body=${String(args['body'])}`);
|
|
185
|
+
if (args['repo'])
|
|
186
|
+
parts.push(`--repo=${String(args['repo'])}`);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
88
189
|
return parts;
|
|
89
190
|
}
|
|
191
|
+
/** Human-readable preview of the command an agent intends to execute */
|
|
192
|
+
export function buildCommandPreview(tool, args) {
|
|
193
|
+
return 'gh ' + buildGhArgs(tool, args).join(' ');
|
|
194
|
+
}
|
|
90
195
|
//# sourceMappingURL=schema.js.map
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/security.d.ts
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
* Security layer.
|
|
3
3
|
*
|
|
4
4
|
* With direct spawn(bin, args[]) there is no shell to inject into.
|
|
5
|
-
* This layer provides
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
5
|
+
* This layer provides defence-in-depth:
|
|
6
|
+
* 1. Whitelists the exact subcommand paths allowed to execute.
|
|
7
|
+
* 2. Associates each subcommand with an operation tier.
|
|
8
|
+
* 3. Validates individual argument values for anomalous content.
|
|
9
|
+
*
|
|
10
|
+
* Tiers:
|
|
11
|
+
* 0 READ — executes immediately, no approval
|
|
12
|
+
* 2 WRITE — blocks until human approves via TTY
|
|
13
|
+
* 3 IRREVERSIBLE — never executes; not exposed as tools
|
|
8
14
|
*/
|
|
9
|
-
export
|
|
15
|
+
export type OperationTier = 0 | 2 | 3;
|
|
16
|
+
export declare function getOperationTier(subcommand: string): OperationTier;
|
|
10
17
|
export declare function validateArgs(args: Record<string, unknown>): void;
|
|
11
18
|
//# sourceMappingURL=security.d.ts.map
|
package/dist/security.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAUtC,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,CAMlE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhE"}
|
package/dist/security.js
CHANGED
|
@@ -2,19 +2,29 @@
|
|
|
2
2
|
* Security layer.
|
|
3
3
|
*
|
|
4
4
|
* With direct spawn(bin, args[]) there is no shell to inject into.
|
|
5
|
-
* This layer provides
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
5
|
+
* This layer provides defence-in-depth:
|
|
6
|
+
* 1. Whitelists the exact subcommand paths allowed to execute.
|
|
7
|
+
* 2. Associates each subcommand with an operation tier.
|
|
8
|
+
* 3. Validates individual argument values for anomalous content.
|
|
9
|
+
*
|
|
10
|
+
* Tiers:
|
|
11
|
+
* 0 READ — executes immediately, no approval
|
|
12
|
+
* 2 WRITE — blocks until human approves via TTY
|
|
13
|
+
* 3 IRREVERSIBLE — never executes; not exposed as tools
|
|
8
14
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'
|
|
12
|
-
'
|
|
15
|
+
const SUBCOMMAND_TIERS = new Map([
|
|
16
|
+
['issue list', 0],
|
|
17
|
+
['pr list', 0],
|
|
18
|
+
['issue create', 2],
|
|
19
|
+
['pr create', 2],
|
|
20
|
+
['issue comment', 2],
|
|
13
21
|
]);
|
|
14
|
-
export function
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
export function getOperationTier(subcommand) {
|
|
23
|
+
const tier = SUBCOMMAND_TIERS.get(subcommand);
|
|
24
|
+
if (tier === undefined) {
|
|
25
|
+
throw new Error(`Error: Subcommand "${subcommand}" is not in the allow-list.`);
|
|
17
26
|
}
|
|
27
|
+
return tier;
|
|
18
28
|
}
|
|
19
29
|
export function validateArgs(args) {
|
|
20
30
|
for (const [, val] of Object.entries(args)) {
|
package/dist/security.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,gBAAgB,GAAuC,IAAI,GAAG,CAAC;IACnE,CAAC,YAAY,EAAK,CAAC,CAAC;IACpB,CAAC,SAAS,EAAQ,CAAC,CAAC;IACpB,CAAC,cAAc,EAAG,CAAC,CAAC;IACpB,CAAC,WAAW,EAAM,CAAC,CAAC;IACpB,CAAC,eAAe,EAAE,CAAC,CAAC;CACrB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,sBAAsB,UAAU,6BAA6B,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAA6B;IACxD,KAAK,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAClE,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
|