multicorn-shield 1.2.0 → 1.3.1
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/CHANGELOG.md +22 -1
- package/dist/multicorn-proxy.js +263 -179
- package/dist/multicorn-shield.js +263 -179
- package/dist/openclaw-hook/handler.js +3 -3
- package/dist/openclaw-plugin/multicorn-shield.js +3 -3
- package/dist/shield-extension.js +14 -19
- package/package.json +2 -1
- package/plugins/multicorn-shield/.claude-plugin/plugin.json +8 -0
- package/plugins/multicorn-shield/hooks/hooks.json +26 -0
- package/plugins/multicorn-shield/hooks/scripts/claude-code-tool-map.cjs +138 -0
- package/plugins/multicorn-shield/hooks/scripts/post-tool-use.cjs +253 -0
- package/plugins/multicorn-shield/hooks/scripts/pre-tool-use.cjs +690 -0
- package/plugins/multicorn-shield/skills/shield-governance/SKILL.md +24 -0
- package/plugins/windsurf/hooks/scripts/pre-action.cjs +69 -25
|
@@ -42,9 +42,9 @@ var TOOL_MAP = {
|
|
|
42
42
|
slack_read: { service: "slack", permissionLevel: "read" },
|
|
43
43
|
slack_message: { service: "slack", permissionLevel: "write" },
|
|
44
44
|
// Payments
|
|
45
|
-
payments: { service: "payments", permissionLevel: "
|
|
46
|
-
payment: { service: "payments", permissionLevel: "
|
|
47
|
-
stripe: { service: "payments", permissionLevel: "
|
|
45
|
+
payments: { service: "payments", permissionLevel: "write" },
|
|
46
|
+
payment: { service: "payments", permissionLevel: "write" },
|
|
47
|
+
stripe: { service: "payments", permissionLevel: "write" }
|
|
48
48
|
};
|
|
49
49
|
function isDestructiveExecCommand(command) {
|
|
50
50
|
const destructiveCommands = ["rm", "mv", "sudo", "chmod", "chown", "dd", "truncate", "shred"];
|
package/dist/shield-extension.js
CHANGED
|
@@ -8,6 +8,7 @@ import 'stream';
|
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
9
|
import { createHash } from 'crypto';
|
|
10
10
|
import 'url';
|
|
11
|
+
import 'module';
|
|
11
12
|
import 'readline';
|
|
12
13
|
|
|
13
14
|
// Multicorn Shield Claude Desktop Extension - https://multicorn.ai
|
|
@@ -22260,54 +22261,48 @@ function getClaudeDesktopConfigPath() {
|
|
|
22260
22261
|
}
|
|
22261
22262
|
}
|
|
22262
22263
|
var INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
22263
|
-
{ slug: "openclaw", displayName: "OpenClaw", section: "native"
|
|
22264
|
-
{ slug: "claude-code", displayName: "Claude Code", section: "native"
|
|
22265
|
-
{ slug: "windsurf", displayName: "Windsurf", section: "native"
|
|
22266
|
-
{ slug: "cline", displayName: "Cline", section: "native"
|
|
22267
|
-
{ slug: "gemini-cli", displayName: "Gemini CLI", section: "native"
|
|
22264
|
+
{ slug: "openclaw", displayName: "OpenClaw", section: "native" },
|
|
22265
|
+
{ slug: "claude-code", displayName: "Claude Code", section: "native" },
|
|
22266
|
+
{ slug: "windsurf", displayName: "Windsurf", section: "native" },
|
|
22267
|
+
{ slug: "cline", displayName: "Cline", section: "native" },
|
|
22268
|
+
{ slug: "gemini-cli", displayName: "Gemini CLI", section: "native" },
|
|
22268
22269
|
{
|
|
22269
22270
|
slug: "cursor",
|
|
22270
22271
|
displayName: "Cursor",
|
|
22271
22272
|
section: "hosted",
|
|
22272
|
-
prereqUrl: "https://www.cursor.com/downloads"
|
|
22273
|
-
detectable: true
|
|
22273
|
+
prereqUrl: "https://www.cursor.com/downloads"
|
|
22274
22274
|
},
|
|
22275
22275
|
{
|
|
22276
22276
|
slug: "claude-desktop",
|
|
22277
22277
|
displayName: "Claude Desktop",
|
|
22278
22278
|
section: "hosted",
|
|
22279
|
-
prereqUrl: "https://claude.ai/download"
|
|
22280
|
-
detectable: false
|
|
22279
|
+
prereqUrl: "https://claude.ai/download"
|
|
22281
22280
|
},
|
|
22282
22281
|
{
|
|
22283
22282
|
slug: "github-copilot",
|
|
22284
22283
|
displayName: "GitHub Copilot",
|
|
22285
22284
|
section: "hosted",
|
|
22286
|
-
prereqUrl: "https://docs.github.com/en/copilot/get-started"
|
|
22287
|
-
detectable: false
|
|
22285
|
+
prereqUrl: "https://docs.github.com/en/copilot/get-started"
|
|
22288
22286
|
},
|
|
22289
22287
|
{
|
|
22290
22288
|
slug: "kilo-code",
|
|
22291
22289
|
displayName: "Kilo Code",
|
|
22292
22290
|
section: "hosted",
|
|
22293
|
-
prereqUrl: "https://kilocode.ai/docs/getting-started"
|
|
22294
|
-
detectable: false
|
|
22291
|
+
prereqUrl: "https://kilocode.ai/docs/getting-started"
|
|
22295
22292
|
},
|
|
22296
22293
|
{
|
|
22297
22294
|
slug: "continue-dev",
|
|
22298
22295
|
displayName: "Continue",
|
|
22299
22296
|
section: "hosted",
|
|
22300
|
-
prereqUrl: "https://docs.continue.dev/ide-extensions/install"
|
|
22301
|
-
detectable: false
|
|
22297
|
+
prereqUrl: "https://docs.continue.dev/ide-extensions/install"
|
|
22302
22298
|
},
|
|
22303
22299
|
{
|
|
22304
22300
|
slug: "goose",
|
|
22305
22301
|
displayName: "Goose",
|
|
22306
22302
|
section: "hosted",
|
|
22307
|
-
prereqUrl: "https://goose-docs.ai/docs/quickstart/"
|
|
22308
|
-
detectable: false
|
|
22303
|
+
prereqUrl: "https://goose-docs.ai/docs/quickstart/"
|
|
22309
22304
|
},
|
|
22310
|
-
{ slug: "other-mcp", displayName: "Local MCP / Other", section: "hosted"
|
|
22305
|
+
{ slug: "other-mcp", displayName: "Local MCP / Other", section: "hosted" }
|
|
22311
22306
|
];
|
|
22312
22307
|
(() => {
|
|
22313
22308
|
const itemsFor = (section) => INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.section === section).map((e) => ({
|
|
@@ -22422,7 +22417,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
|
|
|
22422
22417
|
|
|
22423
22418
|
// package.json
|
|
22424
22419
|
var package_default = {
|
|
22425
|
-
version: "1.
|
|
22420
|
+
version: "1.3.1"};
|
|
22426
22421
|
|
|
22427
22422
|
// src/package-meta.ts
|
|
22428
22423
|
var PACKAGE_VERSION = package_default.version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multicorn-shield",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Multicorn AI Pty Ltd",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"dist",
|
|
39
|
+
"plugins/multicorn-shield",
|
|
39
40
|
"plugins/windsurf",
|
|
40
41
|
"plugins/cline",
|
|
41
42
|
"plugins/gemini-cli",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "multicorn-shield",
|
|
3
|
+
"description": "Agent governance plugin for Claude Code. Intercepts tool calls via PreToolUse hooks, enforces permissions, spending limits, and maintains audit trails.",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Multicorn AI",
|
|
6
|
+
"email": "hello@multicorn.ai"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-tool-use.cjs\""
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PostToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/post-tool-use.cjs\""
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// AUTO-GENERATED from src/hooks/claude-code-tool-map.ts — do not edit manually. Run pnpm build from the package root to regenerate.
|
|
4
|
+
|
|
5
|
+
// src/openclaw/tool-mapper.ts
|
|
6
|
+
var TOOL_MAP = {
|
|
7
|
+
// OpenClaw built-in tools
|
|
8
|
+
read: { service: "filesystem", permissionLevel: "read" },
|
|
9
|
+
write: { service: "filesystem", permissionLevel: "write" },
|
|
10
|
+
edit: { service: "filesystem", permissionLevel: "write" },
|
|
11
|
+
exec: { service: "terminal", permissionLevel: "execute" },
|
|
12
|
+
browser: { service: "browser", permissionLevel: "execute" },
|
|
13
|
+
message: { service: "messaging", permissionLevel: "write" },
|
|
14
|
+
process: { service: "terminal", permissionLevel: "execute" },
|
|
15
|
+
sessions_spawn: { service: "agents", permissionLevel: "execute" },
|
|
16
|
+
// Common integration tools (MCP servers, skills, etc.)
|
|
17
|
+
// Gmail
|
|
18
|
+
gmail: { service: "gmail", permissionLevel: "execute" },
|
|
19
|
+
gmail_send: { service: "gmail", permissionLevel: "write" },
|
|
20
|
+
gmail_read: { service: "gmail", permissionLevel: "read" },
|
|
21
|
+
// Google Calendar
|
|
22
|
+
google_calendar: { service: "google_calendar", permissionLevel: "execute" },
|
|
23
|
+
calendar: { service: "google_calendar", permissionLevel: "execute" },
|
|
24
|
+
calendar_create: { service: "google_calendar", permissionLevel: "write" },
|
|
25
|
+
calendar_read: { service: "google_calendar", permissionLevel: "read" },
|
|
26
|
+
// Google Drive
|
|
27
|
+
google_drive: { service: "google_drive", permissionLevel: "execute" },
|
|
28
|
+
drive: { service: "google_drive", permissionLevel: "execute" },
|
|
29
|
+
drive_read: { service: "google_drive", permissionLevel: "read" },
|
|
30
|
+
drive_write: { service: "google_drive", permissionLevel: "write" },
|
|
31
|
+
// Slack
|
|
32
|
+
slack: { service: "slack", permissionLevel: "execute" },
|
|
33
|
+
slack_send: { service: "slack", permissionLevel: "write" },
|
|
34
|
+
slack_read: { service: "slack", permissionLevel: "read" },
|
|
35
|
+
slack_message: { service: "slack", permissionLevel: "write" },
|
|
36
|
+
// Payments
|
|
37
|
+
payments: { service: "payments", permissionLevel: "write" },
|
|
38
|
+
payment: { service: "payments", permissionLevel: "write" },
|
|
39
|
+
stripe: { service: "payments", permissionLevel: "write" },
|
|
40
|
+
};
|
|
41
|
+
function isDestructiveExecCommand(command) {
|
|
42
|
+
const destructiveCommands = ["rm", "mv", "sudo", "chmod", "chown", "dd", "truncate", "shred"];
|
|
43
|
+
const normalized = command.toLowerCase();
|
|
44
|
+
return destructiveCommands.some((destructive) => normalized.includes(destructive));
|
|
45
|
+
}
|
|
46
|
+
function mapToolToScope(toolName, command) {
|
|
47
|
+
const normalized = toolName.trim().toLowerCase();
|
|
48
|
+
if (normalized.length === 0) {
|
|
49
|
+
return { service: "unknown", permissionLevel: "execute" };
|
|
50
|
+
}
|
|
51
|
+
const known = TOOL_MAP[normalized];
|
|
52
|
+
if (known !== void 0) {
|
|
53
|
+
return known;
|
|
54
|
+
}
|
|
55
|
+
const integrationPrefixes = {
|
|
56
|
+
gmail: "gmail",
|
|
57
|
+
google_calendar: "google_calendar",
|
|
58
|
+
calendar: "google_calendar",
|
|
59
|
+
google_drive: "google_drive",
|
|
60
|
+
drive: "google_drive",
|
|
61
|
+
slack: "slack",
|
|
62
|
+
payments: "payments",
|
|
63
|
+
payment: "payments",
|
|
64
|
+
stripe: "payments",
|
|
65
|
+
};
|
|
66
|
+
for (const [prefix, service] of Object.entries(integrationPrefixes)) {
|
|
67
|
+
if (normalized.startsWith(prefix + "_") || normalized === prefix) {
|
|
68
|
+
let permissionLevel = "execute";
|
|
69
|
+
if (
|
|
70
|
+
normalized.includes("_read") ||
|
|
71
|
+
normalized.includes("_get") ||
|
|
72
|
+
normalized.includes("_list")
|
|
73
|
+
) {
|
|
74
|
+
permissionLevel = "read";
|
|
75
|
+
} else if (
|
|
76
|
+
normalized.includes("_write") ||
|
|
77
|
+
normalized.includes("_send") ||
|
|
78
|
+
normalized.includes("_create") ||
|
|
79
|
+
normalized.includes("_update") ||
|
|
80
|
+
normalized.includes("_delete")
|
|
81
|
+
) {
|
|
82
|
+
permissionLevel = "write";
|
|
83
|
+
}
|
|
84
|
+
return { service, permissionLevel };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { service: normalized, permissionLevel: "execute" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/hooks/claude-code-tool-map.ts
|
|
91
|
+
function extractExecCommand(toolInput) {
|
|
92
|
+
if (toolInput === void 0 || toolInput === null) {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
if (typeof toolInput === "string") {
|
|
96
|
+
try {
|
|
97
|
+
return extractExecCommand(JSON.parse(toolInput));
|
|
98
|
+
} catch {
|
|
99
|
+
process.stderr.write("Shield: failed to parse tool input as JSON, using raw string\n");
|
|
100
|
+
return toolInput;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (typeof toolInput === "object") {
|
|
104
|
+
const o = toolInput;
|
|
105
|
+
const c = o["command"] ?? o["cmd"];
|
|
106
|
+
if (typeof c === "string") {
|
|
107
|
+
return c;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
function mapClaudeCodeToolToShield(toolName, toolInput) {
|
|
113
|
+
const n = toolName.trim().toLowerCase();
|
|
114
|
+
if (n.length === 0) {
|
|
115
|
+
return { service: "unknown", actionType: "execute" };
|
|
116
|
+
}
|
|
117
|
+
if (n === "bash" || n === "shell") {
|
|
118
|
+
const cmd = extractExecCommand(toolInput);
|
|
119
|
+
if (cmd !== void 0 && isDestructiveExecCommand(cmd)) {
|
|
120
|
+
return { service: "terminal", actionType: "write" };
|
|
121
|
+
}
|
|
122
|
+
return { service: "terminal", actionType: "execute" };
|
|
123
|
+
}
|
|
124
|
+
if (n === "glob" || n === "grep") {
|
|
125
|
+
return { service: "filesystem", actionType: "read" };
|
|
126
|
+
}
|
|
127
|
+
if (n === "webfetch") {
|
|
128
|
+
return { service: "web", actionType: "read" };
|
|
129
|
+
}
|
|
130
|
+
if (n === "task") {
|
|
131
|
+
return { service: "subagent", actionType: "execute" };
|
|
132
|
+
}
|
|
133
|
+
const scope = mapToolToScope(n);
|
|
134
|
+
return { service: scope.service, actionType: scope.permissionLevel };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
exports.extractExecCommand = extractExecCommand;
|
|
138
|
+
exports.mapClaudeCodeToolToShield = mapClaudeCodeToolToShield;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code PostToolUse hook: logs completed tool calls to Shield (audit trail).
|
|
3
|
+
* Never blocks; always exit 0.
|
|
4
|
+
*
|
|
5
|
+
* Tool mapping: see `./claude-code-tool-map.cjs` (built from `src/hooks/claude-code-tool-map.ts`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("node:fs");
|
|
11
|
+
const http = require("node:http");
|
|
12
|
+
const https = require("node:https");
|
|
13
|
+
const os = require("node:os");
|
|
14
|
+
const path = require("node:path");
|
|
15
|
+
|
|
16
|
+
const { mapClaudeCodeToolToShield } = require("./claude-code-tool-map.cjs");
|
|
17
|
+
|
|
18
|
+
const AUTH_HEADER = "X-Multicorn-Key";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @returns {Promise<string>}
|
|
22
|
+
*/
|
|
23
|
+
function readStdin() {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
process.stdin.setEncoding("utf8");
|
|
27
|
+
process.stdin.on("data", (c) => chunks.push(c));
|
|
28
|
+
process.stdin.on("end", () => resolve(chunks.join("")));
|
|
29
|
+
process.stdin.on("error", reject);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Agent resolution for Claude Code (duplicated in pre-tool-use.cjs).
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} cwdResolved
|
|
36
|
+
* @param {string} workspacePath
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function cwdUnderWorkspacePath(cwdResolved, workspacePath) {
|
|
40
|
+
const w = path.resolve(workspacePath);
|
|
41
|
+
if (cwdResolved === w) return true;
|
|
42
|
+
const prefix = w.endsWith(path.sep) ? w : w + path.sep;
|
|
43
|
+
return cwdResolved.startsWith(prefix);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {Record<string, unknown>} obj
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
function resolveClaudeCodeAgentName(obj) {
|
|
51
|
+
const cwdRaw =
|
|
52
|
+
process.env.PWD !== undefined && String(process.env.PWD).length > 0
|
|
53
|
+
? process.env.PWD
|
|
54
|
+
: process.cwd();
|
|
55
|
+
const agents = obj.agents;
|
|
56
|
+
const defaultAgentRaw = obj.defaultAgent;
|
|
57
|
+
const defaultAgentName =
|
|
58
|
+
typeof defaultAgentRaw === "string" && defaultAgentRaw.length > 0 ? defaultAgentRaw : "";
|
|
59
|
+
|
|
60
|
+
if (!Array.isArray(agents)) {
|
|
61
|
+
return typeof obj.agentName === "string" ? obj.agentName : "";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const matches = [];
|
|
65
|
+
for (const entry of agents) {
|
|
66
|
+
if (
|
|
67
|
+
entry &&
|
|
68
|
+
typeof entry === "object" &&
|
|
69
|
+
/** @type {{ platform?: string; name?: string; workspacePath?: string }} */ (entry)
|
|
70
|
+
.platform === "claude-code" &&
|
|
71
|
+
typeof (/** @type {{ name?: string }} */ (entry).name) === "string"
|
|
72
|
+
) {
|
|
73
|
+
matches.push(/** @type {{ name: string; workspacePath?: string }} */ (entry));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (matches.length === 0) {
|
|
78
|
+
return typeof obj.agentName === "string" ? obj.agentName : "";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const withWs = matches.filter(
|
|
82
|
+
(m) => typeof m.workspacePath === "string" && m.workspacePath.length > 0,
|
|
83
|
+
);
|
|
84
|
+
const resolvedCwd = path.resolve(cwdRaw);
|
|
85
|
+
let best = null;
|
|
86
|
+
let bestLen = -1;
|
|
87
|
+
for (const m of withWs) {
|
|
88
|
+
if (!cwdUnderWorkspacePath(resolvedCwd, /** @type {string} */ (m.workspacePath))) continue;
|
|
89
|
+
const len = path.resolve(/** @type {string} */ (m.workspacePath)).length;
|
|
90
|
+
if (len > bestLen) {
|
|
91
|
+
bestLen = len;
|
|
92
|
+
best = m;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (best !== null) {
|
|
96
|
+
return best.name;
|
|
97
|
+
}
|
|
98
|
+
if (defaultAgentName.length > 0) {
|
|
99
|
+
const d = matches.find((m) => m.name === defaultAgentName);
|
|
100
|
+
if (d !== undefined) return d.name;
|
|
101
|
+
}
|
|
102
|
+
return matches[0].name;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @returns {{ apiKey: string; baseUrl: string; agentName: string } | null}
|
|
107
|
+
*/
|
|
108
|
+
function loadConfig() {
|
|
109
|
+
try {
|
|
110
|
+
const configPath = path.join(os.homedir(), ".multicorn", "config.json");
|
|
111
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
112
|
+
const obj = JSON.parse(raw);
|
|
113
|
+
const apiKey = typeof obj.apiKey === "string" ? obj.apiKey : "";
|
|
114
|
+
const baseUrl =
|
|
115
|
+
typeof obj.baseUrl === "string" && obj.baseUrl.length > 0
|
|
116
|
+
? obj.baseUrl.replace(/\/+$/, "")
|
|
117
|
+
: "https://api.multicorn.ai";
|
|
118
|
+
const agentName = resolveClaudeCodeAgentName(obj);
|
|
119
|
+
return { apiKey, baseUrl, agentName };
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {string} urlString
|
|
127
|
+
* @param {string} apiKey
|
|
128
|
+
* @param {Record<string, unknown>} bodyObj
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
function postJson(baseUrl, apiKey, bodyObj) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
let u;
|
|
134
|
+
try {
|
|
135
|
+
const root = String(baseUrl).replace(/\/+$/, "");
|
|
136
|
+
u = new URL(`${root}/api/v1/actions`);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
reject(e);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const payload = JSON.stringify(bodyObj);
|
|
142
|
+
const isHttps = u.protocol === "https:";
|
|
143
|
+
const lib = isHttps ? https : http;
|
|
144
|
+
const port = u.port || (isHttps ? 443 : 80);
|
|
145
|
+
const options = {
|
|
146
|
+
hostname: u.hostname,
|
|
147
|
+
port,
|
|
148
|
+
path: u.pathname + u.search,
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
"Content-Length": Buffer.byteLength(payload, "utf8"),
|
|
153
|
+
[AUTH_HEADER]: apiKey,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
const req = lib.request(options, (res) => {
|
|
157
|
+
res.resume();
|
|
158
|
+
res.on("end", () => resolve());
|
|
159
|
+
});
|
|
160
|
+
req.on("error", reject);
|
|
161
|
+
req.write(payload);
|
|
162
|
+
req.end();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function main() {
|
|
167
|
+
let raw;
|
|
168
|
+
try {
|
|
169
|
+
raw = await readStdin();
|
|
170
|
+
} catch {
|
|
171
|
+
process.exit(0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const config = loadConfig();
|
|
175
|
+
if (config === null || config.apiKey.length === 0 || config.agentName.length === 0) {
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** @type {Record<string, unknown>} */
|
|
180
|
+
let hookPayload;
|
|
181
|
+
try {
|
|
182
|
+
hookPayload = JSON.parse(raw.length > 0 ? raw : "{}");
|
|
183
|
+
} catch {
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const toolNameRaw =
|
|
188
|
+
(typeof hookPayload.tool_name === "string" && hookPayload.tool_name) ||
|
|
189
|
+
(typeof hookPayload.toolName === "string" && hookPayload.toolName) ||
|
|
190
|
+
"";
|
|
191
|
+
const toolInput =
|
|
192
|
+
hookPayload.tool_input !== undefined ? hookPayload.tool_input : hookPayload.toolInput;
|
|
193
|
+
const toolResult =
|
|
194
|
+
hookPayload.tool_result !== undefined
|
|
195
|
+
? hookPayload.tool_result
|
|
196
|
+
: hookPayload.toolResult !== undefined
|
|
197
|
+
? hookPayload.toolResult
|
|
198
|
+
: undefined;
|
|
199
|
+
|
|
200
|
+
let toolInputSerialized;
|
|
201
|
+
let toolResultSerialized;
|
|
202
|
+
try {
|
|
203
|
+
toolInputSerialized =
|
|
204
|
+
typeof toolInput === "string"
|
|
205
|
+
? toolInput
|
|
206
|
+
: JSON.stringify(toolInput === undefined ? null : toolInput);
|
|
207
|
+
toolResultSerialized =
|
|
208
|
+
typeof toolResult === "string"
|
|
209
|
+
? toolResult
|
|
210
|
+
: JSON.stringify(toolResult === undefined ? null : toolResult);
|
|
211
|
+
} catch {
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { service, actionType } = mapClaudeCodeToolToShield(toolNameRaw, toolInput);
|
|
216
|
+
|
|
217
|
+
/** @type {Record<string, unknown>} */
|
|
218
|
+
const metadata = {
|
|
219
|
+
tool_name: toolNameRaw,
|
|
220
|
+
tool_input: toolInputSerialized,
|
|
221
|
+
tool_result: toolResultSerialized,
|
|
222
|
+
source: "claude-code",
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/** @type {Record<string, unknown>} */
|
|
226
|
+
const payload = {
|
|
227
|
+
agent: config.agentName,
|
|
228
|
+
service,
|
|
229
|
+
actionType,
|
|
230
|
+
status: "approved",
|
|
231
|
+
metadata,
|
|
232
|
+
platform: "claude-code",
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
await postJson(config.baseUrl, config.apiKey, payload);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
239
|
+
process.stderr.write(
|
|
240
|
+
`[multicorn-shield] PostToolUse: Warning: failed to log action to Shield audit trail.\n Detail: ${msg}\n`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
main().catch((e) => {
|
|
248
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
249
|
+
process.stderr.write(
|
|
250
|
+
`[multicorn-shield] PostToolUse: Warning: failed to log action to Shield audit trail.\n Detail: ${msg}\n`,
|
|
251
|
+
);
|
|
252
|
+
process.exit(0);
|
|
253
|
+
});
|