@schuttdev/gigai 0.3.4 → 0.4.0-beta.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/{chunk-75GPR4GR.js → chunk-FR2BGYIC.js} +24 -30
- package/dist/{chunk-GIUPSQGA.js → chunk-WUAGWZNA.js} +15 -33
- package/dist/{chunk-P53UVHTF.js → chunk-XL27JFUS.js} +152 -1
- package/dist/{dist-2BIIMXAI.js → dist-SDKK67CH.js} +93 -51
- package/dist/{filesystem-FWNLRLL6-LQG7PWAF.js → filesystem-FTLLT7JC-3DA3AAA7.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{shell-Z3WUF2GW-TVEYB5OM.js → shell-F2MZGP6N-WUIBVJZF.js} +2 -2
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ErrorCode,
|
|
4
|
-
GigaiError
|
|
5
|
-
|
|
4
|
+
GigaiError,
|
|
5
|
+
canAccessPath
|
|
6
|
+
} from "./chunk-XL27JFUS.js";
|
|
6
7
|
|
|
7
|
-
// ../server/dist/chunk-
|
|
8
|
+
// ../server/dist/chunk-XK4BHB3B.mjs
|
|
8
9
|
import {
|
|
9
10
|
readFile as fsReadFile,
|
|
10
11
|
writeFile as fsWriteFile,
|
|
@@ -15,7 +16,7 @@ import { realpath } from "fs/promises";
|
|
|
15
16
|
import { spawn } from "child_process";
|
|
16
17
|
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
17
18
|
var MAX_READ_SIZE = 2 * 1024 * 1024;
|
|
18
|
-
async function validatePath(targetPath, allowedPaths) {
|
|
19
|
+
async function validatePath(targetPath, allowedPaths, tier = "strict") {
|
|
19
20
|
const resolved = resolve(targetPath);
|
|
20
21
|
let real;
|
|
21
22
|
try {
|
|
@@ -23,33 +24,26 @@ async function validatePath(targetPath, allowedPaths) {
|
|
|
23
24
|
} catch {
|
|
24
25
|
real = resolved;
|
|
25
26
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return real === resolvedAllowed || real.startsWith(allowedPrefix);
|
|
30
|
-
});
|
|
31
|
-
if (!isAllowed) {
|
|
32
|
-
throw new GigaiError(
|
|
33
|
-
ErrorCode.PATH_NOT_ALLOWED,
|
|
34
|
-
`Path not within allowed directories: ${targetPath}`
|
|
35
|
-
);
|
|
27
|
+
const check = canAccessPath(tier, real, allowedPaths);
|
|
28
|
+
if (!check.allowed) {
|
|
29
|
+
throw new GigaiError(ErrorCode.PATH_NOT_ALLOWED, check.reason);
|
|
36
30
|
}
|
|
37
31
|
return real;
|
|
38
32
|
}
|
|
39
|
-
async function readFileSafe(path, allowedPaths) {
|
|
40
|
-
const safePath = await validatePath(path, allowedPaths);
|
|
33
|
+
async function readFileSafe(path, allowedPaths, tier = "strict") {
|
|
34
|
+
const safePath = await validatePath(path, allowedPaths, tier);
|
|
41
35
|
return fsReadFile(safePath, "utf8");
|
|
42
36
|
}
|
|
43
|
-
async function listDirSafe(path, allowedPaths) {
|
|
44
|
-
const safePath = await validatePath(path, allowedPaths);
|
|
37
|
+
async function listDirSafe(path, allowedPaths, tier = "strict") {
|
|
38
|
+
const safePath = await validatePath(path, allowedPaths, tier);
|
|
45
39
|
const entries = await readdir(safePath, { withFileTypes: true });
|
|
46
40
|
return entries.map((e) => ({
|
|
47
41
|
name: e.name,
|
|
48
42
|
type: e.isDirectory() ? "directory" : "file"
|
|
49
43
|
}));
|
|
50
44
|
}
|
|
51
|
-
async function searchFilesSafe(path, pattern, allowedPaths) {
|
|
52
|
-
const safePath = await validatePath(path, allowedPaths);
|
|
45
|
+
async function searchFilesSafe(path, pattern, allowedPaths, tier = "strict") {
|
|
46
|
+
const safePath = await validatePath(path, allowedPaths, tier);
|
|
53
47
|
const results = [];
|
|
54
48
|
let regex;
|
|
55
49
|
try {
|
|
@@ -72,12 +66,12 @@ async function searchFilesSafe(path, pattern, allowedPaths) {
|
|
|
72
66
|
await walk(safePath);
|
|
73
67
|
return results;
|
|
74
68
|
}
|
|
75
|
-
async function readBuiltin(args, allowedPaths) {
|
|
69
|
+
async function readBuiltin(args, allowedPaths, tier = "strict") {
|
|
76
70
|
const filePath = args[0];
|
|
77
71
|
if (!filePath) {
|
|
78
72
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: read <file> [offset] [limit]");
|
|
79
73
|
}
|
|
80
|
-
const safePath = await validatePath(filePath, allowedPaths);
|
|
74
|
+
const safePath = await validatePath(filePath, allowedPaths, tier);
|
|
81
75
|
const content = await fsReadFile(safePath, "utf8");
|
|
82
76
|
if (content.length > MAX_READ_SIZE) {
|
|
83
77
|
throw new GigaiError(
|
|
@@ -96,20 +90,20 @@ async function readBuiltin(args, allowedPaths) {
|
|
|
96
90
|
}
|
|
97
91
|
return { stdout: content, stderr: "", exitCode: 0 };
|
|
98
92
|
}
|
|
99
|
-
async function writeBuiltin(args, allowedPaths) {
|
|
93
|
+
async function writeBuiltin(args, allowedPaths, tier = "strict") {
|
|
100
94
|
const filePath = args[0];
|
|
101
95
|
const content = args[1];
|
|
102
96
|
if (!filePath || content === void 0) {
|
|
103
97
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: write <file> <content>");
|
|
104
98
|
}
|
|
105
|
-
const safePath = await validatePath(filePath, allowedPaths);
|
|
99
|
+
const safePath = await validatePath(filePath, allowedPaths, tier);
|
|
106
100
|
const { mkdir } = await import("fs/promises");
|
|
107
101
|
const { dirname } = await import("path");
|
|
108
102
|
await mkdir(dirname(safePath), { recursive: true });
|
|
109
103
|
await fsWriteFile(safePath, content, "utf8");
|
|
110
104
|
return { stdout: `Written: ${safePath}`, stderr: "", exitCode: 0 };
|
|
111
105
|
}
|
|
112
|
-
async function editBuiltin(args, allowedPaths) {
|
|
106
|
+
async function editBuiltin(args, allowedPaths, tier = "strict") {
|
|
113
107
|
const filePath = args[0];
|
|
114
108
|
const oldStr = args[1];
|
|
115
109
|
const newStr = args[2];
|
|
@@ -117,7 +111,7 @@ async function editBuiltin(args, allowedPaths) {
|
|
|
117
111
|
if (!filePath || oldStr === void 0 || newStr === void 0) {
|
|
118
112
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: edit <file> <old_string> <new_string> [--all]");
|
|
119
113
|
}
|
|
120
|
-
const safePath = await validatePath(filePath, allowedPaths);
|
|
114
|
+
const safePath = await validatePath(filePath, allowedPaths, tier);
|
|
121
115
|
const content = await fsReadFile(safePath, "utf8");
|
|
122
116
|
if (!content.includes(oldStr)) {
|
|
123
117
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "old_string not found in file");
|
|
@@ -137,13 +131,13 @@ async function editBuiltin(args, allowedPaths) {
|
|
|
137
131
|
const count = replaceAll ? content.split(oldStr).length - 1 : 1;
|
|
138
132
|
return { stdout: `Replaced ${count} occurrence(s) in ${safePath}`, stderr: "", exitCode: 0 };
|
|
139
133
|
}
|
|
140
|
-
async function globBuiltin(args, allowedPaths) {
|
|
134
|
+
async function globBuiltin(args, allowedPaths, tier = "strict") {
|
|
141
135
|
const pattern = args[0];
|
|
142
136
|
if (!pattern) {
|
|
143
137
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: glob <pattern> [path]");
|
|
144
138
|
}
|
|
145
139
|
const searchPath = args[1] ?? ".";
|
|
146
|
-
const safePath = await validatePath(searchPath, allowedPaths);
|
|
140
|
+
const safePath = await validatePath(searchPath, allowedPaths, tier);
|
|
147
141
|
const results = [];
|
|
148
142
|
const globRegex = globToRegex(pattern);
|
|
149
143
|
async function walk(dir) {
|
|
@@ -168,7 +162,7 @@ async function globBuiltin(args, allowedPaths) {
|
|
|
168
162
|
await walk(safePath);
|
|
169
163
|
return { stdout: results.join("\n"), stderr: "", exitCode: 0 };
|
|
170
164
|
}
|
|
171
|
-
async function grepBuiltin(args, allowedPaths) {
|
|
165
|
+
async function grepBuiltin(args, allowedPaths, tier = "strict") {
|
|
172
166
|
if (args.length === 0) {
|
|
173
167
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: grep <pattern> [path] [--glob <filter>] [-i] [-n] [-C <num>]");
|
|
174
168
|
}
|
|
@@ -199,7 +193,7 @@ async function grepBuiltin(args, allowedPaths) {
|
|
|
199
193
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No search pattern provided");
|
|
200
194
|
}
|
|
201
195
|
const searchPath = positional[1] ?? ".";
|
|
202
|
-
const safePath = await validatePath(searchPath, allowedPaths);
|
|
196
|
+
const safePath = await validatePath(searchPath, allowedPaths, tier);
|
|
203
197
|
try {
|
|
204
198
|
return await spawnGrep("rg", [pattern, safePath, "-n", ...flags]);
|
|
205
199
|
} catch {
|
|
@@ -1,42 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ErrorCode,
|
|
4
|
-
GigaiError
|
|
5
|
-
|
|
4
|
+
GigaiError,
|
|
5
|
+
canExecuteCommand,
|
|
6
|
+
canUseSudo
|
|
7
|
+
} from "./chunk-XL27JFUS.js";
|
|
6
8
|
|
|
7
|
-
// ../server/dist/chunk-
|
|
9
|
+
// ../server/dist/chunk-NYES5MEC.mjs
|
|
8
10
|
import { spawn } from "child_process";
|
|
9
|
-
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set([
|
|
10
|
-
"sh",
|
|
11
|
-
"bash",
|
|
12
|
-
"zsh",
|
|
13
|
-
"fish",
|
|
14
|
-
"csh",
|
|
15
|
-
"tcsh",
|
|
16
|
-
"dash",
|
|
17
|
-
"ksh",
|
|
18
|
-
"env",
|
|
19
|
-
"xargs",
|
|
20
|
-
"nohup",
|
|
21
|
-
"strace",
|
|
22
|
-
"ltrace"
|
|
23
|
-
]);
|
|
24
11
|
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
25
|
-
async function execCommandSafe(command, args, config) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
if (command === "sudo" && !config.allowSudo) {
|
|
33
|
-
throw new GigaiError(ErrorCode.COMMAND_NOT_ALLOWED, "sudo is not allowed");
|
|
12
|
+
async function execCommandSafe(command, args, config, tier = "strict") {
|
|
13
|
+
if (command === "sudo") {
|
|
14
|
+
const sudoCheck = canUseSudo(config.allowSudo);
|
|
15
|
+
if (!sudoCheck.allowed) {
|
|
16
|
+
throw new GigaiError(ErrorCode.COMMAND_NOT_ALLOWED, sudoCheck.reason);
|
|
17
|
+
}
|
|
34
18
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`Shell interpreter not allowed: ${command}`
|
|
39
|
-
);
|
|
19
|
+
const check = canExecuteCommand(tier, command, config.allowlist);
|
|
20
|
+
if (!check.allowed) {
|
|
21
|
+
throw new GigaiError(ErrorCode.COMMAND_NOT_ALLOWED, check.reason);
|
|
40
22
|
}
|
|
41
23
|
for (const arg of args) {
|
|
42
24
|
if (arg.includes("\0")) {
|
|
@@ -44,7 +26,7 @@ async function execCommandSafe(command, args, config) {
|
|
|
44
26
|
}
|
|
45
27
|
}
|
|
46
28
|
return new Promise((resolve, reject) => {
|
|
47
|
-
const child = spawn(command,
|
|
29
|
+
const child = spawn(command, args, {
|
|
48
30
|
shell: false,
|
|
49
31
|
stdio: ["ignore", "pipe", "pipe"]
|
|
50
32
|
});
|
|
@@ -1,5 +1,145 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// ../server/dist/chunk-NQN6AX3Q.mjs
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
var DEFAULT_SECURITY = { default: "strict", overrides: {} };
|
|
7
|
+
function getEffectiveTier(config, toolName) {
|
|
8
|
+
const c = config ?? DEFAULT_SECURITY;
|
|
9
|
+
return c.overrides[toolName] ?? c.default;
|
|
10
|
+
}
|
|
11
|
+
var STANDARD_DENYLIST = /* @__PURE__ */ new Set([
|
|
12
|
+
"dd",
|
|
13
|
+
// raw disk I/O
|
|
14
|
+
"mkfs",
|
|
15
|
+
// format filesystem
|
|
16
|
+
"fdisk",
|
|
17
|
+
// partition editor
|
|
18
|
+
"parted",
|
|
19
|
+
// partition editor
|
|
20
|
+
"mkswap",
|
|
21
|
+
// overwrite partition with swap
|
|
22
|
+
"mount",
|
|
23
|
+
// arbitrary fs mounting
|
|
24
|
+
"umount",
|
|
25
|
+
// unmount filesystems
|
|
26
|
+
"insmod",
|
|
27
|
+
// load kernel modules
|
|
28
|
+
"rmmod",
|
|
29
|
+
// remove kernel modules
|
|
30
|
+
"modprobe",
|
|
31
|
+
// kernel module loader
|
|
32
|
+
"iptables",
|
|
33
|
+
// firewall rules
|
|
34
|
+
"ip6tables",
|
|
35
|
+
// ipv6 firewall
|
|
36
|
+
"reboot",
|
|
37
|
+
// reboot machine
|
|
38
|
+
"shutdown",
|
|
39
|
+
// power off
|
|
40
|
+
"halt",
|
|
41
|
+
// halt machine
|
|
42
|
+
"poweroff",
|
|
43
|
+
// power off
|
|
44
|
+
"init",
|
|
45
|
+
// change runlevel
|
|
46
|
+
"telinit",
|
|
47
|
+
// change runlevel
|
|
48
|
+
"systemctl",
|
|
49
|
+
// service management (can make remote machine unreachable)
|
|
50
|
+
"chown"
|
|
51
|
+
// change file ownership
|
|
52
|
+
]);
|
|
53
|
+
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set([
|
|
54
|
+
"sh",
|
|
55
|
+
"bash",
|
|
56
|
+
"zsh",
|
|
57
|
+
"fish",
|
|
58
|
+
"csh",
|
|
59
|
+
"tcsh",
|
|
60
|
+
"dash",
|
|
61
|
+
"ksh",
|
|
62
|
+
"env",
|
|
63
|
+
"xargs",
|
|
64
|
+
"nohup",
|
|
65
|
+
"strace",
|
|
66
|
+
"ltrace"
|
|
67
|
+
]);
|
|
68
|
+
function canExecuteCommand(tier, command, allowlist) {
|
|
69
|
+
if (command.includes("\0")) {
|
|
70
|
+
return { allowed: false, reason: "Null byte in command" };
|
|
71
|
+
}
|
|
72
|
+
switch (tier) {
|
|
73
|
+
case "strict": {
|
|
74
|
+
const list = allowlist ?? [];
|
|
75
|
+
if (list.length > 0 && !list.includes(command)) {
|
|
76
|
+
return { allowed: false, reason: `Command not in allowlist: ${command}. Allowed: ${list.join(", ")}` };
|
|
77
|
+
}
|
|
78
|
+
if (SHELL_INTERPRETERS.has(command)) {
|
|
79
|
+
return { allowed: false, reason: `Shell interpreter not allowed: ${command}` };
|
|
80
|
+
}
|
|
81
|
+
return { allowed: true };
|
|
82
|
+
}
|
|
83
|
+
case "standard": {
|
|
84
|
+
if (STANDARD_DENYLIST.has(command)) {
|
|
85
|
+
return { allowed: false, reason: `Command blocked by security policy: ${command}` };
|
|
86
|
+
}
|
|
87
|
+
if (SHELL_INTERPRETERS.has(command)) {
|
|
88
|
+
return { allowed: false, reason: `Shell interpreter not allowed: ${command}` };
|
|
89
|
+
}
|
|
90
|
+
return { allowed: true };
|
|
91
|
+
}
|
|
92
|
+
case "unrestricted":
|
|
93
|
+
return { allowed: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function canUseSudo(allowSudo) {
|
|
97
|
+
if (allowSudo === false) return { allowed: false, reason: "sudo is not allowed" };
|
|
98
|
+
return { allowed: true };
|
|
99
|
+
}
|
|
100
|
+
var STANDARD_BLOCKED_PATHS = [
|
|
101
|
+
".ssh",
|
|
102
|
+
".gnupg",
|
|
103
|
+
".gpg",
|
|
104
|
+
".config/gigai",
|
|
105
|
+
".aws",
|
|
106
|
+
".azure",
|
|
107
|
+
".gcloud",
|
|
108
|
+
".kube",
|
|
109
|
+
".docker"
|
|
110
|
+
];
|
|
111
|
+
function canAccessPath(tier, resolvedPath, allowedPaths) {
|
|
112
|
+
switch (tier) {
|
|
113
|
+
case "strict": {
|
|
114
|
+
const paths = allowedPaths ?? [];
|
|
115
|
+
if (paths.length === 0) {
|
|
116
|
+
return { allowed: true };
|
|
117
|
+
}
|
|
118
|
+
const ok = paths.some((allowed) => {
|
|
119
|
+
const r = resolve(allowed);
|
|
120
|
+
return resolvedPath === r || resolvedPath.startsWith(r.endsWith("/") ? r : r + "/");
|
|
121
|
+
});
|
|
122
|
+
if (!ok) return { allowed: false, reason: `Path not within allowed directories: ${resolvedPath}` };
|
|
123
|
+
return { allowed: true };
|
|
124
|
+
}
|
|
125
|
+
case "standard": {
|
|
126
|
+
const home = homedir();
|
|
127
|
+
for (const blocked of STANDARD_BLOCKED_PATHS) {
|
|
128
|
+
const full = resolve(home, blocked);
|
|
129
|
+
if (resolvedPath === full || resolvedPath.startsWith(full + "/")) {
|
|
130
|
+
return { allowed: false, reason: `Path blocked by security policy: ${blocked}` };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { allowed: true };
|
|
134
|
+
}
|
|
135
|
+
case "unrestricted":
|
|
136
|
+
return { allowed: true };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function shouldInjectSeparator(tier) {
|
|
140
|
+
return tier === "strict";
|
|
141
|
+
}
|
|
142
|
+
|
|
3
143
|
// ../shared/dist/index.mjs
|
|
4
144
|
import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
5
145
|
import { z } from "zod";
|
|
@@ -252,14 +392,25 @@ var ServerConfigSchema = z.object({
|
|
|
252
392
|
host: z.string().default("0.0.0.0"),
|
|
253
393
|
https: HttpsConfigSchema.optional()
|
|
254
394
|
});
|
|
395
|
+
var SecurityTierSchema = z.enum(["strict", "standard", "unrestricted"]);
|
|
396
|
+
var SecurityConfigSchema = z.object({
|
|
397
|
+
default: SecurityTierSchema.default("strict"),
|
|
398
|
+
overrides: z.record(SecurityTierSchema).default({})
|
|
399
|
+
});
|
|
255
400
|
var GigaiConfigSchema = z.object({
|
|
256
401
|
serverName: z.string().optional(),
|
|
257
402
|
server: ServerConfigSchema,
|
|
258
403
|
auth: AuthConfigSchema,
|
|
259
|
-
tools: z.array(ToolConfigSchema).default([])
|
|
404
|
+
tools: z.array(ToolConfigSchema).default([]),
|
|
405
|
+
security: SecurityConfigSchema.optional()
|
|
260
406
|
});
|
|
261
407
|
|
|
262
408
|
export {
|
|
409
|
+
getEffectiveTier,
|
|
410
|
+
canExecuteCommand,
|
|
411
|
+
canUseSudo,
|
|
412
|
+
canAccessPath,
|
|
413
|
+
shouldInjectSeparator,
|
|
263
414
|
encrypt,
|
|
264
415
|
decrypt,
|
|
265
416
|
generateEncryptionKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
execCommandSafe
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WUAGWZNA.js";
|
|
5
5
|
import {
|
|
6
6
|
editBuiltin,
|
|
7
7
|
globBuiltin,
|
|
@@ -11,15 +11,17 @@ import {
|
|
|
11
11
|
readFileSafe,
|
|
12
12
|
searchFilesSafe,
|
|
13
13
|
writeBuiltin
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FR2BGYIC.js";
|
|
15
15
|
import {
|
|
16
16
|
ErrorCode,
|
|
17
17
|
GigaiConfigSchema,
|
|
18
18
|
GigaiError,
|
|
19
19
|
decrypt,
|
|
20
20
|
encrypt,
|
|
21
|
-
generateEncryptionKey
|
|
22
|
-
|
|
21
|
+
generateEncryptionKey,
|
|
22
|
+
getEffectiveTier,
|
|
23
|
+
shouldInjectSeparator
|
|
24
|
+
} from "./chunk-XL27JFUS.js";
|
|
23
25
|
|
|
24
26
|
// ../server/dist/index.mjs
|
|
25
27
|
import { parseArgs } from "util";
|
|
@@ -357,7 +359,7 @@ function sanitizeArgs(args) {
|
|
|
357
359
|
var DEFAULT_TIMEOUT = 3e4;
|
|
358
360
|
var KILL_GRACE_PERIOD = 5e3;
|
|
359
361
|
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
360
|
-
function executeTool(entry, args, timeout) {
|
|
362
|
+
function executeTool(entry, args, timeout, tier = "strict") {
|
|
361
363
|
const sanitized = sanitizeArgs(args);
|
|
362
364
|
const effectiveTimeout = timeout ?? DEFAULT_TIMEOUT;
|
|
363
365
|
let command;
|
|
@@ -367,7 +369,11 @@ function executeTool(entry, args, timeout) {
|
|
|
367
369
|
switch (entry.type) {
|
|
368
370
|
case "cli":
|
|
369
371
|
command = entry.config.command;
|
|
370
|
-
spawnArgs = [...entry.config.args ?? []
|
|
372
|
+
spawnArgs = [...entry.config.args ?? []];
|
|
373
|
+
if (shouldInjectSeparator(tier)) {
|
|
374
|
+
spawnArgs.push("--");
|
|
375
|
+
}
|
|
376
|
+
spawnArgs.push(...sanitized);
|
|
371
377
|
cwd = entry.config.cwd;
|
|
372
378
|
env = entry.config.env;
|
|
373
379
|
break;
|
|
@@ -794,7 +800,7 @@ var cronPlugin = fp5(async (server, opts) => {
|
|
|
794
800
|
const executor = async (tool, args) => {
|
|
795
801
|
const entry = server.registry.get(tool);
|
|
796
802
|
if (entry.type === "builtin") {
|
|
797
|
-
const { execCommandSafe: execCommandSafe2 } = await import("./shell-
|
|
803
|
+
const { execCommandSafe: execCommandSafe2 } = await import("./shell-F2MZGP6N-WUIBVJZF.js");
|
|
798
804
|
const {
|
|
799
805
|
readFileSafe: readFileSafe2,
|
|
800
806
|
listDirSafe: listDirSafe2,
|
|
@@ -804,7 +810,7 @@ var cronPlugin = fp5(async (server, opts) => {
|
|
|
804
810
|
editBuiltin: editBuiltin2,
|
|
805
811
|
globBuiltin: globBuiltin2,
|
|
806
812
|
grepBuiltin: grepBuiltin2
|
|
807
|
-
} = await import("./filesystem-
|
|
813
|
+
} = await import("./filesystem-FTLLT7JC-3DA3AAA7.js");
|
|
808
814
|
const builtinConfig = entry.config.config ?? {};
|
|
809
815
|
switch (entry.config.builtin) {
|
|
810
816
|
case "filesystem": {
|
|
@@ -955,10 +961,11 @@ async function execRoutes(server) {
|
|
|
955
961
|
}, async (request) => {
|
|
956
962
|
const { tool, args, timeout } = request.body;
|
|
957
963
|
const entry = server.registry.get(tool);
|
|
964
|
+
const tier = getEffectiveTier(server.securityConfig, tool);
|
|
958
965
|
if (entry.type === "builtin") {
|
|
959
|
-
return handleBuiltin(entry.config, args);
|
|
966
|
+
return handleBuiltin(entry.config, args, tier);
|
|
960
967
|
}
|
|
961
|
-
const result = await server.executor.execute(entry, args, timeout);
|
|
968
|
+
const result = await server.executor.execute(entry, args, timeout, tier);
|
|
962
969
|
return result;
|
|
963
970
|
});
|
|
964
971
|
server.post("/exec/mcp", {
|
|
@@ -992,65 +999,65 @@ async function execRoutes(server) {
|
|
|
992
999
|
};
|
|
993
1000
|
});
|
|
994
1001
|
}
|
|
995
|
-
async function handleBuiltin(config, args) {
|
|
1002
|
+
async function handleBuiltin(config, args, tier) {
|
|
996
1003
|
const builtinConfig = config.config ?? {};
|
|
997
1004
|
switch (config.builtin) {
|
|
998
1005
|
// Legacy combined filesystem tool
|
|
999
1006
|
case "filesystem": {
|
|
1000
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1007
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1001
1008
|
const subcommand = args[0];
|
|
1002
1009
|
const target = args[1] ?? ".";
|
|
1003
1010
|
switch (subcommand) {
|
|
1004
1011
|
case "read":
|
|
1005
|
-
return { stdout: await readFileSafe(target, allowedPaths), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1012
|
+
return { stdout: await readFileSafe(target, allowedPaths ?? [], tier), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1006
1013
|
case "list":
|
|
1007
|
-
return { stdout: JSON.stringify(await listDirSafe(target, allowedPaths), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1014
|
+
return { stdout: JSON.stringify(await listDirSafe(target, allowedPaths ?? [], tier), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1008
1015
|
case "search":
|
|
1009
|
-
return { stdout: JSON.stringify(await searchFilesSafe(target, args[2] ?? ".*", allowedPaths), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1016
|
+
return { stdout: JSON.stringify(await searchFilesSafe(target, args[2] ?? ".*", allowedPaths ?? [], tier), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1010
1017
|
default:
|
|
1011
1018
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, `Unknown filesystem subcommand: ${subcommand}. Use: read, list, search`);
|
|
1012
1019
|
}
|
|
1013
1020
|
}
|
|
1014
1021
|
// Legacy shell tool
|
|
1015
1022
|
case "shell": {
|
|
1016
|
-
const allowlist = builtinConfig.allowlist
|
|
1017
|
-
const allowSudo = builtinConfig.allowSudo
|
|
1023
|
+
const allowlist = builtinConfig.allowlist;
|
|
1024
|
+
const allowSudo = builtinConfig.allowSudo;
|
|
1018
1025
|
const command = args[0];
|
|
1019
1026
|
if (!command) {
|
|
1020
1027
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No command specified");
|
|
1021
1028
|
}
|
|
1022
|
-
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo });
|
|
1029
|
+
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo }, tier);
|
|
1023
1030
|
return { ...result, durationMs: 0 };
|
|
1024
1031
|
}
|
|
1025
1032
|
// --- New builtins ---
|
|
1026
1033
|
case "read": {
|
|
1027
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1028
|
-
return { ...await readBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1034
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1035
|
+
return { ...await readBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1029
1036
|
}
|
|
1030
1037
|
case "write": {
|
|
1031
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1032
|
-
return { ...await writeBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1038
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1039
|
+
return { ...await writeBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1033
1040
|
}
|
|
1034
1041
|
case "edit": {
|
|
1035
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1036
|
-
return { ...await editBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1042
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1043
|
+
return { ...await editBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1037
1044
|
}
|
|
1038
1045
|
case "glob": {
|
|
1039
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1040
|
-
return { ...await globBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1046
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1047
|
+
return { ...await globBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1041
1048
|
}
|
|
1042
1049
|
case "grep": {
|
|
1043
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1044
|
-
return { ...await grepBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1050
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1051
|
+
return { ...await grepBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1045
1052
|
}
|
|
1046
1053
|
case "bash": {
|
|
1047
|
-
const allowlist = builtinConfig.allowlist
|
|
1048
|
-
const allowSudo = builtinConfig.allowSudo
|
|
1054
|
+
const allowlist = builtinConfig.allowlist;
|
|
1055
|
+
const allowSudo = builtinConfig.allowSudo;
|
|
1049
1056
|
const command = args[0];
|
|
1050
1057
|
if (!command) {
|
|
1051
1058
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No command specified");
|
|
1052
1059
|
}
|
|
1053
|
-
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo });
|
|
1060
|
+
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo }, tier);
|
|
1054
1061
|
return { ...result, durationMs: 0 };
|
|
1055
1062
|
}
|
|
1056
1063
|
default:
|
|
@@ -1224,6 +1231,7 @@ async function createServer(opts) {
|
|
|
1224
1231
|
await server.register(registryPlugin, { config });
|
|
1225
1232
|
await server.register(executorPlugin);
|
|
1226
1233
|
await server.register(mcpPlugin, { config });
|
|
1234
|
+
server.decorate("securityConfig", config.security);
|
|
1227
1235
|
if (configPath) {
|
|
1228
1236
|
await server.register(cronPlugin, { configPath });
|
|
1229
1237
|
}
|
|
@@ -1486,6 +1494,15 @@ async function runInit() {
|
|
|
1486
1494
|
default: "7443"
|
|
1487
1495
|
});
|
|
1488
1496
|
const port = parseInt(portStr, 10);
|
|
1497
|
+
const securityTier = await select({
|
|
1498
|
+
message: "Security tier:",
|
|
1499
|
+
choices: [
|
|
1500
|
+
{ name: "Strict \u2014 allowlist-only, explicit path restrictions (most secure)", value: "strict" },
|
|
1501
|
+
{ name: "Standard \u2014 denylist blocks catastrophic commands, home dir open (recommended)", value: "standard" },
|
|
1502
|
+
{ name: "Unrestricted \u2014 no restrictions, development only", value: "unrestricted" }
|
|
1503
|
+
],
|
|
1504
|
+
default: "standard"
|
|
1505
|
+
});
|
|
1489
1506
|
const selectedBuiltins = await checkbox({
|
|
1490
1507
|
message: "Built-in tools to enable:",
|
|
1491
1508
|
choices: [
|
|
@@ -1495,35 +1512,59 @@ async function runInit() {
|
|
|
1495
1512
|
});
|
|
1496
1513
|
const tools = [];
|
|
1497
1514
|
if (selectedBuiltins.includes("filesystem")) {
|
|
1498
|
-
const
|
|
1499
|
-
message: "
|
|
1500
|
-
default:
|
|
1501
|
-
});
|
|
1502
|
-
const allowedPaths = pathsStr.split(",").map((p) => p.trim());
|
|
1503
|
-
tools.push({
|
|
1504
|
-
type: "builtin",
|
|
1505
|
-
name: "fs",
|
|
1506
|
-
builtin: "filesystem",
|
|
1507
|
-
description: "Read, list, and search files",
|
|
1508
|
-
config: { allowedPaths }
|
|
1515
|
+
const restrictPaths = await confirm({
|
|
1516
|
+
message: "Restrict filesystem to specific paths?",
|
|
1517
|
+
default: false
|
|
1509
1518
|
});
|
|
1519
|
+
if (restrictPaths) {
|
|
1520
|
+
const pathsStr = await input({
|
|
1521
|
+
message: "Allowed paths (comma-separated):",
|
|
1522
|
+
default: process.env.HOME ?? "~"
|
|
1523
|
+
});
|
|
1524
|
+
const allowedPaths = pathsStr.split(",").map((p) => p.trim());
|
|
1525
|
+
tools.push({
|
|
1526
|
+
type: "builtin",
|
|
1527
|
+
name: "fs",
|
|
1528
|
+
builtin: "filesystem",
|
|
1529
|
+
description: "Read, list, and search files",
|
|
1530
|
+
config: { allowedPaths }
|
|
1531
|
+
});
|
|
1532
|
+
} else {
|
|
1533
|
+
tools.push({
|
|
1534
|
+
type: "builtin",
|
|
1535
|
+
name: "fs",
|
|
1536
|
+
builtin: "filesystem",
|
|
1537
|
+
description: "Read, list, and search files",
|
|
1538
|
+
config: {}
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1510
1541
|
}
|
|
1511
1542
|
if (selectedBuiltins.includes("shell")) {
|
|
1512
|
-
const
|
|
1513
|
-
message: "
|
|
1514
|
-
default:
|
|
1543
|
+
const restrictCommands = await confirm({
|
|
1544
|
+
message: "Restrict shell to a command allowlist?",
|
|
1545
|
+
default: false
|
|
1515
1546
|
});
|
|
1516
|
-
const
|
|
1517
|
-
|
|
1518
|
-
|
|
1547
|
+
const shellConfig = {};
|
|
1548
|
+
if (restrictCommands) {
|
|
1549
|
+
const allowlistStr = await input({
|
|
1550
|
+
message: "Allowed commands (comma-separated):",
|
|
1551
|
+
default: "ls,cat,head,tail,grep,find,wc,echo,date,whoami,pwd,git,npm,node"
|
|
1552
|
+
});
|
|
1553
|
+
shellConfig.allowlist = allowlistStr.split(",").map((c) => c.trim());
|
|
1554
|
+
}
|
|
1555
|
+
const blockSudo = await confirm({
|
|
1556
|
+
message: "Block sudo?",
|
|
1519
1557
|
default: false
|
|
1520
1558
|
});
|
|
1559
|
+
if (blockSudo) {
|
|
1560
|
+
shellConfig.allowSudo = false;
|
|
1561
|
+
}
|
|
1521
1562
|
tools.push({
|
|
1522
1563
|
type: "builtin",
|
|
1523
1564
|
name: "shell",
|
|
1524
1565
|
builtin: "shell",
|
|
1525
|
-
description: "Execute
|
|
1526
|
-
config:
|
|
1566
|
+
description: "Execute shell commands",
|
|
1567
|
+
config: shellConfig
|
|
1527
1568
|
});
|
|
1528
1569
|
}
|
|
1529
1570
|
const configFilePath = await detectClaudeDesktopConfig();
|
|
@@ -1603,7 +1644,8 @@ async function runInit() {
|
|
|
1603
1644
|
pairingTtlSeconds: 300,
|
|
1604
1645
|
sessionTtlSeconds: 14400
|
|
1605
1646
|
},
|
|
1606
|
-
tools
|
|
1647
|
+
tools,
|
|
1648
|
+
security: { default: securityTier, overrides: {} }
|
|
1607
1649
|
};
|
|
1608
1650
|
const configPath = resolve4("gigai.config.json");
|
|
1609
1651
|
await writeFile3(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
package/dist/index.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
|
-
var VERSION = "0.
|
|
7
|
+
var VERSION = "0.4.0-beta.0";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
10
|
async function requireServer() {
|
|
11
11
|
try {
|
|
12
|
-
return await import("./dist-
|
|
12
|
+
return await import("./dist-SDKK67CH.js");
|
|
13
13
|
} catch {
|
|
14
14
|
console.error("Server dependencies not installed.");
|
|
15
15
|
console.error("Run: npm install -g @schuttdev/gigai");
|