@schuttdev/gigai 0.3.5 → 0.4.0-beta.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/dist/{chunk-GIUPSQGA.js → chunk-NFKBLW3D.js} +21 -32
- package/dist/{chunk-P53UVHTF.js → chunk-ROMGFLOH.js} +201 -1
- package/dist/{chunk-75GPR4GR.js → chunk-VCFG6XXN.js} +26 -31
- package/dist/{dist-2BIIMXAI.js → dist-XQICZQ57.js} +96 -53
- package/dist/{filesystem-FWNLRLL6-LQG7PWAF.js → filesystem-CVLQFP7T-VBJWEKEE.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{shell-Z3WUF2GW-TVEYB5OM.js → shell-IBJTGIEW-UEOHS5M2.js} +2 -2
- package/package.json +1 -1
|
@@ -1,50 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ErrorCode,
|
|
4
|
-
GigaiError
|
|
5
|
-
|
|
4
|
+
GigaiError,
|
|
5
|
+
canExecuteCommand,
|
|
6
|
+
canUseSudo,
|
|
7
|
+
expandTilde,
|
|
8
|
+
validateCommandArgs
|
|
9
|
+
} from "./chunk-ROMGFLOH.js";
|
|
6
10
|
|
|
7
|
-
// ../server/dist/chunk-
|
|
11
|
+
// ../server/dist/chunk-BO7QE6T2.mjs
|
|
8
12
|
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
13
|
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
25
|
-
async function execCommandSafe(command, args, config) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
async function execCommandSafe(command, args, config, tier = "strict") {
|
|
15
|
+
if (command === "sudo") {
|
|
16
|
+
const sudoCheck = canUseSudo(config.allowSudo);
|
|
17
|
+
if (!sudoCheck.allowed) {
|
|
18
|
+
throw new GigaiError(ErrorCode.COMMAND_NOT_ALLOWED, sudoCheck.reason);
|
|
19
|
+
}
|
|
31
20
|
}
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
const check = canExecuteCommand(tier, command, config.allowlist);
|
|
22
|
+
if (!check.allowed) {
|
|
23
|
+
throw new GigaiError(ErrorCode.COMMAND_NOT_ALLOWED, check.reason);
|
|
34
24
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`Shell interpreter not allowed: ${command}`
|
|
39
|
-
);
|
|
25
|
+
const argCheck = validateCommandArgs(tier, command, args);
|
|
26
|
+
if (!argCheck.allowed) {
|
|
27
|
+
throw new GigaiError(ErrorCode.COMMAND_NOT_ALLOWED, argCheck.reason);
|
|
40
28
|
}
|
|
41
29
|
for (const arg of args) {
|
|
42
30
|
if (arg.includes("\0")) {
|
|
43
31
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Null byte in argument");
|
|
44
32
|
}
|
|
45
33
|
}
|
|
34
|
+
const expandedArgs = args.map(expandTilde);
|
|
46
35
|
return new Promise((resolve, reject) => {
|
|
47
|
-
const child = spawn(command,
|
|
36
|
+
const child = spawn(command, expandedArgs, {
|
|
48
37
|
shell: false,
|
|
49
38
|
stdio: ["ignore", "pipe", "pipe"]
|
|
50
39
|
});
|
|
@@ -1,5 +1,192 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// ../server/dist/chunk-NO4R43QM.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
|
+
var PROTECTED_PATHS = /* @__PURE__ */ new Set([
|
|
97
|
+
"/",
|
|
98
|
+
"/bin",
|
|
99
|
+
"/boot",
|
|
100
|
+
"/dev",
|
|
101
|
+
"/etc",
|
|
102
|
+
"/home",
|
|
103
|
+
"/lib",
|
|
104
|
+
"/opt",
|
|
105
|
+
"/proc",
|
|
106
|
+
"/root",
|
|
107
|
+
"/sbin",
|
|
108
|
+
"/sys",
|
|
109
|
+
"/usr",
|
|
110
|
+
"/var",
|
|
111
|
+
"/Users",
|
|
112
|
+
"/System",
|
|
113
|
+
"/Library",
|
|
114
|
+
"/Applications"
|
|
115
|
+
]);
|
|
116
|
+
function hasRecursiveFlag(args) {
|
|
117
|
+
for (const a of args) {
|
|
118
|
+
if (a === "--") break;
|
|
119
|
+
if (a === "-r" || a === "-R" || a === "--recursive") return true;
|
|
120
|
+
if (a.startsWith("-") && !a.startsWith("--") && /[rR]/.test(a)) return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
function validateCommandArgs(tier, command, args) {
|
|
125
|
+
if (tier === "unrestricted") return { allowed: true };
|
|
126
|
+
if (command === "rm" && hasRecursiveFlag(args)) {
|
|
127
|
+
const home = homedir();
|
|
128
|
+
for (const arg of args) {
|
|
129
|
+
if (arg.startsWith("-")) continue;
|
|
130
|
+
const resolved = resolve(arg);
|
|
131
|
+
if (PROTECTED_PATHS.has(resolved) || resolved === home) {
|
|
132
|
+
return { allowed: false, reason: `Refusing to recursively remove critical path: ${resolved}` };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { allowed: true };
|
|
137
|
+
}
|
|
138
|
+
function canUseSudo(allowSudo) {
|
|
139
|
+
if (allowSudo === false) return { allowed: false, reason: "sudo is not allowed" };
|
|
140
|
+
return { allowed: true };
|
|
141
|
+
}
|
|
142
|
+
var STANDARD_BLOCKED_PATHS = [
|
|
143
|
+
".ssh",
|
|
144
|
+
".gnupg",
|
|
145
|
+
".gpg",
|
|
146
|
+
".config/gigai",
|
|
147
|
+
".aws",
|
|
148
|
+
".azure",
|
|
149
|
+
".gcloud",
|
|
150
|
+
".kube",
|
|
151
|
+
".docker"
|
|
152
|
+
];
|
|
153
|
+
function canAccessPath(tier, resolvedPath, allowedPaths) {
|
|
154
|
+
switch (tier) {
|
|
155
|
+
case "strict": {
|
|
156
|
+
const paths = allowedPaths ?? [];
|
|
157
|
+
if (paths.length === 0) {
|
|
158
|
+
return { allowed: true };
|
|
159
|
+
}
|
|
160
|
+
const ok = paths.some((allowed) => {
|
|
161
|
+
const r = resolve(allowed);
|
|
162
|
+
return resolvedPath === r || resolvedPath.startsWith(r.endsWith("/") ? r : r + "/");
|
|
163
|
+
});
|
|
164
|
+
if (!ok) return { allowed: false, reason: `Path not within allowed directories: ${resolvedPath}` };
|
|
165
|
+
return { allowed: true };
|
|
166
|
+
}
|
|
167
|
+
case "standard": {
|
|
168
|
+
const home = homedir();
|
|
169
|
+
for (const blocked of STANDARD_BLOCKED_PATHS) {
|
|
170
|
+
const full = resolve(home, blocked);
|
|
171
|
+
if (resolvedPath === full || resolvedPath.startsWith(full + "/")) {
|
|
172
|
+
return { allowed: false, reason: `Path blocked by security policy: ${blocked}` };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { allowed: true };
|
|
176
|
+
}
|
|
177
|
+
case "unrestricted":
|
|
178
|
+
return { allowed: true };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function expandTilde(p) {
|
|
182
|
+
if (p === "~") return homedir();
|
|
183
|
+
if (p.startsWith("~/")) return homedir() + p.slice(1);
|
|
184
|
+
return p;
|
|
185
|
+
}
|
|
186
|
+
function shouldInjectSeparator(tier) {
|
|
187
|
+
return tier === "strict";
|
|
188
|
+
}
|
|
189
|
+
|
|
3
190
|
// ../shared/dist/index.mjs
|
|
4
191
|
import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
5
192
|
import { z } from "zod";
|
|
@@ -252,14 +439,27 @@ var ServerConfigSchema = z.object({
|
|
|
252
439
|
host: z.string().default("0.0.0.0"),
|
|
253
440
|
https: HttpsConfigSchema.optional()
|
|
254
441
|
});
|
|
442
|
+
var SecurityTierSchema = z.enum(["strict", "standard", "unrestricted"]);
|
|
443
|
+
var SecurityConfigSchema = z.object({
|
|
444
|
+
default: SecurityTierSchema.default("strict"),
|
|
445
|
+
overrides: z.record(SecurityTierSchema).default({})
|
|
446
|
+
});
|
|
255
447
|
var GigaiConfigSchema = z.object({
|
|
256
448
|
serverName: z.string().optional(),
|
|
257
449
|
server: ServerConfigSchema,
|
|
258
450
|
auth: AuthConfigSchema,
|
|
259
|
-
tools: z.array(ToolConfigSchema).default([])
|
|
451
|
+
tools: z.array(ToolConfigSchema).default([]),
|
|
452
|
+
security: SecurityConfigSchema.optional()
|
|
260
453
|
});
|
|
261
454
|
|
|
262
455
|
export {
|
|
456
|
+
getEffectiveTier,
|
|
457
|
+
canExecuteCommand,
|
|
458
|
+
validateCommandArgs,
|
|
459
|
+
canUseSudo,
|
|
460
|
+
canAccessPath,
|
|
461
|
+
expandTilde,
|
|
462
|
+
shouldInjectSeparator,
|
|
263
463
|
encrypt,
|
|
264
464
|
decrypt,
|
|
265
465
|
generateEncryptionKey,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ErrorCode,
|
|
4
|
-
GigaiError
|
|
5
|
-
|
|
4
|
+
GigaiError,
|
|
5
|
+
canAccessPath,
|
|
6
|
+
expandTilde
|
|
7
|
+
} from "./chunk-ROMGFLOH.js";
|
|
6
8
|
|
|
7
|
-
// ../server/dist/chunk-
|
|
9
|
+
// ../server/dist/chunk-HXHM5BKW.mjs
|
|
8
10
|
import {
|
|
9
11
|
readFile as fsReadFile,
|
|
10
12
|
writeFile as fsWriteFile,
|
|
@@ -15,41 +17,34 @@ import { realpath } from "fs/promises";
|
|
|
15
17
|
import { spawn } from "child_process";
|
|
16
18
|
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
17
19
|
var MAX_READ_SIZE = 2 * 1024 * 1024;
|
|
18
|
-
async function validatePath(targetPath, allowedPaths) {
|
|
19
|
-
const resolved = resolve(targetPath);
|
|
20
|
+
async function validatePath(targetPath, allowedPaths, tier = "strict") {
|
|
21
|
+
const resolved = resolve(expandTilde(targetPath));
|
|
20
22
|
let real;
|
|
21
23
|
try {
|
|
22
24
|
real = await realpath(resolved);
|
|
23
25
|
} catch {
|
|
24
26
|
real = resolved;
|
|
25
27
|
}
|
|
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
|
-
);
|
|
28
|
+
const check = canAccessPath(tier, real, allowedPaths);
|
|
29
|
+
if (!check.allowed) {
|
|
30
|
+
throw new GigaiError(ErrorCode.PATH_NOT_ALLOWED, check.reason);
|
|
36
31
|
}
|
|
37
32
|
return real;
|
|
38
33
|
}
|
|
39
|
-
async function readFileSafe(path, allowedPaths) {
|
|
40
|
-
const safePath = await validatePath(path, allowedPaths);
|
|
34
|
+
async function readFileSafe(path, allowedPaths, tier = "strict") {
|
|
35
|
+
const safePath = await validatePath(path, allowedPaths, tier);
|
|
41
36
|
return fsReadFile(safePath, "utf8");
|
|
42
37
|
}
|
|
43
|
-
async function listDirSafe(path, allowedPaths) {
|
|
44
|
-
const safePath = await validatePath(path, allowedPaths);
|
|
38
|
+
async function listDirSafe(path, allowedPaths, tier = "strict") {
|
|
39
|
+
const safePath = await validatePath(path, allowedPaths, tier);
|
|
45
40
|
const entries = await readdir(safePath, { withFileTypes: true });
|
|
46
41
|
return entries.map((e) => ({
|
|
47
42
|
name: e.name,
|
|
48
43
|
type: e.isDirectory() ? "directory" : "file"
|
|
49
44
|
}));
|
|
50
45
|
}
|
|
51
|
-
async function searchFilesSafe(path, pattern, allowedPaths) {
|
|
52
|
-
const safePath = await validatePath(path, allowedPaths);
|
|
46
|
+
async function searchFilesSafe(path, pattern, allowedPaths, tier = "strict") {
|
|
47
|
+
const safePath = await validatePath(path, allowedPaths, tier);
|
|
53
48
|
const results = [];
|
|
54
49
|
let regex;
|
|
55
50
|
try {
|
|
@@ -72,12 +67,12 @@ async function searchFilesSafe(path, pattern, allowedPaths) {
|
|
|
72
67
|
await walk(safePath);
|
|
73
68
|
return results;
|
|
74
69
|
}
|
|
75
|
-
async function readBuiltin(args, allowedPaths) {
|
|
70
|
+
async function readBuiltin(args, allowedPaths, tier = "strict") {
|
|
76
71
|
const filePath = args[0];
|
|
77
72
|
if (!filePath) {
|
|
78
73
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: read <file> [offset] [limit]");
|
|
79
74
|
}
|
|
80
|
-
const safePath = await validatePath(filePath, allowedPaths);
|
|
75
|
+
const safePath = await validatePath(filePath, allowedPaths, tier);
|
|
81
76
|
const content = await fsReadFile(safePath, "utf8");
|
|
82
77
|
if (content.length > MAX_READ_SIZE) {
|
|
83
78
|
throw new GigaiError(
|
|
@@ -96,20 +91,20 @@ async function readBuiltin(args, allowedPaths) {
|
|
|
96
91
|
}
|
|
97
92
|
return { stdout: content, stderr: "", exitCode: 0 };
|
|
98
93
|
}
|
|
99
|
-
async function writeBuiltin(args, allowedPaths) {
|
|
94
|
+
async function writeBuiltin(args, allowedPaths, tier = "strict") {
|
|
100
95
|
const filePath = args[0];
|
|
101
96
|
const content = args[1];
|
|
102
97
|
if (!filePath || content === void 0) {
|
|
103
98
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: write <file> <content>");
|
|
104
99
|
}
|
|
105
|
-
const safePath = await validatePath(filePath, allowedPaths);
|
|
100
|
+
const safePath = await validatePath(filePath, allowedPaths, tier);
|
|
106
101
|
const { mkdir } = await import("fs/promises");
|
|
107
102
|
const { dirname } = await import("path");
|
|
108
103
|
await mkdir(dirname(safePath), { recursive: true });
|
|
109
104
|
await fsWriteFile(safePath, content, "utf8");
|
|
110
105
|
return { stdout: `Written: ${safePath}`, stderr: "", exitCode: 0 };
|
|
111
106
|
}
|
|
112
|
-
async function editBuiltin(args, allowedPaths) {
|
|
107
|
+
async function editBuiltin(args, allowedPaths, tier = "strict") {
|
|
113
108
|
const filePath = args[0];
|
|
114
109
|
const oldStr = args[1];
|
|
115
110
|
const newStr = args[2];
|
|
@@ -117,7 +112,7 @@ async function editBuiltin(args, allowedPaths) {
|
|
|
117
112
|
if (!filePath || oldStr === void 0 || newStr === void 0) {
|
|
118
113
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: edit <file> <old_string> <new_string> [--all]");
|
|
119
114
|
}
|
|
120
|
-
const safePath = await validatePath(filePath, allowedPaths);
|
|
115
|
+
const safePath = await validatePath(filePath, allowedPaths, tier);
|
|
121
116
|
const content = await fsReadFile(safePath, "utf8");
|
|
122
117
|
if (!content.includes(oldStr)) {
|
|
123
118
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "old_string not found in file");
|
|
@@ -137,13 +132,13 @@ async function editBuiltin(args, allowedPaths) {
|
|
|
137
132
|
const count = replaceAll ? content.split(oldStr).length - 1 : 1;
|
|
138
133
|
return { stdout: `Replaced ${count} occurrence(s) in ${safePath}`, stderr: "", exitCode: 0 };
|
|
139
134
|
}
|
|
140
|
-
async function globBuiltin(args, allowedPaths) {
|
|
135
|
+
async function globBuiltin(args, allowedPaths, tier = "strict") {
|
|
141
136
|
const pattern = args[0];
|
|
142
137
|
if (!pattern) {
|
|
143
138
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: glob <pattern> [path]");
|
|
144
139
|
}
|
|
145
140
|
const searchPath = args[1] ?? ".";
|
|
146
|
-
const safePath = await validatePath(searchPath, allowedPaths);
|
|
141
|
+
const safePath = await validatePath(searchPath, allowedPaths, tier);
|
|
147
142
|
const results = [];
|
|
148
143
|
const globRegex = globToRegex(pattern);
|
|
149
144
|
async function walk(dir) {
|
|
@@ -168,7 +163,7 @@ async function globBuiltin(args, allowedPaths) {
|
|
|
168
163
|
await walk(safePath);
|
|
169
164
|
return { stdout: results.join("\n"), stderr: "", exitCode: 0 };
|
|
170
165
|
}
|
|
171
|
-
async function grepBuiltin(args, allowedPaths) {
|
|
166
|
+
async function grepBuiltin(args, allowedPaths, tier = "strict") {
|
|
172
167
|
if (args.length === 0) {
|
|
173
168
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: grep <pattern> [path] [--glob <filter>] [-i] [-n] [-C <num>]");
|
|
174
169
|
}
|
|
@@ -199,7 +194,7 @@ async function grepBuiltin(args, allowedPaths) {
|
|
|
199
194
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No search pattern provided");
|
|
200
195
|
}
|
|
201
196
|
const searchPath = positional[1] ?? ".";
|
|
202
|
-
const safePath = await validatePath(searchPath, allowedPaths);
|
|
197
|
+
const safePath = await validatePath(searchPath, allowedPaths, tier);
|
|
203
198
|
try {
|
|
204
199
|
return await spawnGrep("rg", [pattern, safePath, "-n", ...flags]);
|
|
205
200
|
} catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
execCommandSafe
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NFKBLW3D.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-VCFG6XXN.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-ROMGFLOH.js";
|
|
23
25
|
|
|
24
26
|
// ../server/dist/index.mjs
|
|
25
27
|
import { parseArgs } from "util";
|
|
@@ -46,7 +48,7 @@ import { nanoid as nanoid3 } from "nanoid";
|
|
|
46
48
|
import { dirname as dirname2 } from "path";
|
|
47
49
|
import { readFileSync } from "fs";
|
|
48
50
|
import { resolve as resolve2 } from "path";
|
|
49
|
-
import { platform, hostname as hostname2 } from "os";
|
|
51
|
+
import { platform, hostname as hostname2, userInfo } from "os";
|
|
50
52
|
import { platform as platform2 } from "os";
|
|
51
53
|
import { writeFile as writeFile2, readFile as readFile2, unlink, mkdir } from "fs/promises";
|
|
52
54
|
import { join } from "path";
|
|
@@ -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-IBJTGIEW-UEOHS5M2.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-CVLQFP7T-VBJWEKEE.js");
|
|
808
814
|
const builtinConfig = entry.config.config ?? {};
|
|
809
815
|
switch (entry.config.builtin) {
|
|
810
816
|
case "filesystem": {
|
|
@@ -885,7 +891,8 @@ async function healthRoutes(server) {
|
|
|
885
891
|
version: startupVersion,
|
|
886
892
|
uptime: Date.now() - startTime,
|
|
887
893
|
platform: platform(),
|
|
888
|
-
hostname: hostname2()
|
|
894
|
+
hostname: hostname2(),
|
|
895
|
+
homeDir: userInfo().homedir
|
|
889
896
|
};
|
|
890
897
|
});
|
|
891
898
|
}
|
|
@@ -955,10 +962,11 @@ async function execRoutes(server) {
|
|
|
955
962
|
}, async (request) => {
|
|
956
963
|
const { tool, args, timeout } = request.body;
|
|
957
964
|
const entry = server.registry.get(tool);
|
|
965
|
+
const tier = getEffectiveTier(server.securityConfig, tool);
|
|
958
966
|
if (entry.type === "builtin") {
|
|
959
|
-
return handleBuiltin(entry.config, args);
|
|
967
|
+
return handleBuiltin(entry.config, args, tier);
|
|
960
968
|
}
|
|
961
|
-
const result = await server.executor.execute(entry, args, timeout);
|
|
969
|
+
const result = await server.executor.execute(entry, args, timeout, tier);
|
|
962
970
|
return result;
|
|
963
971
|
});
|
|
964
972
|
server.post("/exec/mcp", {
|
|
@@ -992,65 +1000,65 @@ async function execRoutes(server) {
|
|
|
992
1000
|
};
|
|
993
1001
|
});
|
|
994
1002
|
}
|
|
995
|
-
async function handleBuiltin(config, args) {
|
|
1003
|
+
async function handleBuiltin(config, args, tier) {
|
|
996
1004
|
const builtinConfig = config.config ?? {};
|
|
997
1005
|
switch (config.builtin) {
|
|
998
1006
|
// Legacy combined filesystem tool
|
|
999
1007
|
case "filesystem": {
|
|
1000
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1008
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1001
1009
|
const subcommand = args[0];
|
|
1002
1010
|
const target = args[1] ?? ".";
|
|
1003
1011
|
switch (subcommand) {
|
|
1004
1012
|
case "read":
|
|
1005
|
-
return { stdout: await readFileSafe(target, allowedPaths), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1013
|
+
return { stdout: await readFileSafe(target, allowedPaths ?? [], tier), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1006
1014
|
case "list":
|
|
1007
|
-
return { stdout: JSON.stringify(await listDirSafe(target, allowedPaths), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1015
|
+
return { stdout: JSON.stringify(await listDirSafe(target, allowedPaths ?? [], tier), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1008
1016
|
case "search":
|
|
1009
|
-
return { stdout: JSON.stringify(await searchFilesSafe(target, args[2] ?? ".*", allowedPaths), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1017
|
+
return { stdout: JSON.stringify(await searchFilesSafe(target, args[2] ?? ".*", allowedPaths ?? [], tier), null, 2), stderr: "", exitCode: 0, durationMs: 0 };
|
|
1010
1018
|
default:
|
|
1011
1019
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, `Unknown filesystem subcommand: ${subcommand}. Use: read, list, search`);
|
|
1012
1020
|
}
|
|
1013
1021
|
}
|
|
1014
1022
|
// Legacy shell tool
|
|
1015
1023
|
case "shell": {
|
|
1016
|
-
const allowlist = builtinConfig.allowlist
|
|
1017
|
-
const allowSudo = builtinConfig.allowSudo
|
|
1024
|
+
const allowlist = builtinConfig.allowlist;
|
|
1025
|
+
const allowSudo = builtinConfig.allowSudo;
|
|
1018
1026
|
const command = args[0];
|
|
1019
1027
|
if (!command) {
|
|
1020
1028
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No command specified");
|
|
1021
1029
|
}
|
|
1022
|
-
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo });
|
|
1030
|
+
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo }, tier);
|
|
1023
1031
|
return { ...result, durationMs: 0 };
|
|
1024
1032
|
}
|
|
1025
1033
|
// --- New builtins ---
|
|
1026
1034
|
case "read": {
|
|
1027
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1028
|
-
return { ...await readBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1035
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1036
|
+
return { ...await readBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1029
1037
|
}
|
|
1030
1038
|
case "write": {
|
|
1031
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1032
|
-
return { ...await writeBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1039
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1040
|
+
return { ...await writeBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1033
1041
|
}
|
|
1034
1042
|
case "edit": {
|
|
1035
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1036
|
-
return { ...await editBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1043
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1044
|
+
return { ...await editBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1037
1045
|
}
|
|
1038
1046
|
case "glob": {
|
|
1039
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1040
|
-
return { ...await globBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1047
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1048
|
+
return { ...await globBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1041
1049
|
}
|
|
1042
1050
|
case "grep": {
|
|
1043
|
-
const allowedPaths = builtinConfig.allowedPaths
|
|
1044
|
-
return { ...await grepBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1051
|
+
const allowedPaths = builtinConfig.allowedPaths;
|
|
1052
|
+
return { ...await grepBuiltin(args, allowedPaths ?? [], tier), durationMs: 0 };
|
|
1045
1053
|
}
|
|
1046
1054
|
case "bash": {
|
|
1047
|
-
const allowlist = builtinConfig.allowlist
|
|
1048
|
-
const allowSudo = builtinConfig.allowSudo
|
|
1055
|
+
const allowlist = builtinConfig.allowlist;
|
|
1056
|
+
const allowSudo = builtinConfig.allowSudo;
|
|
1049
1057
|
const command = args[0];
|
|
1050
1058
|
if (!command) {
|
|
1051
1059
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No command specified");
|
|
1052
1060
|
}
|
|
1053
|
-
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo });
|
|
1061
|
+
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo }, tier);
|
|
1054
1062
|
return { ...result, durationMs: 0 };
|
|
1055
1063
|
}
|
|
1056
1064
|
default:
|
|
@@ -1224,6 +1232,7 @@ async function createServer(opts) {
|
|
|
1224
1232
|
await server.register(registryPlugin, { config });
|
|
1225
1233
|
await server.register(executorPlugin);
|
|
1226
1234
|
await server.register(mcpPlugin, { config });
|
|
1235
|
+
server.decorate("securityConfig", config.security);
|
|
1227
1236
|
if (configPath) {
|
|
1228
1237
|
await server.register(cronPlugin, { configPath });
|
|
1229
1238
|
}
|
|
@@ -1486,6 +1495,15 @@ async function runInit() {
|
|
|
1486
1495
|
default: "7443"
|
|
1487
1496
|
});
|
|
1488
1497
|
const port = parseInt(portStr, 10);
|
|
1498
|
+
const securityTier = await select({
|
|
1499
|
+
message: "Security tier:",
|
|
1500
|
+
choices: [
|
|
1501
|
+
{ name: "Strict \u2014 allowlist-only, explicit path restrictions (most secure)", value: "strict" },
|
|
1502
|
+
{ name: "Standard \u2014 denylist blocks catastrophic commands, home dir open (recommended)", value: "standard" },
|
|
1503
|
+
{ name: "Unrestricted \u2014 no restrictions, development only", value: "unrestricted" }
|
|
1504
|
+
],
|
|
1505
|
+
default: "standard"
|
|
1506
|
+
});
|
|
1489
1507
|
const selectedBuiltins = await checkbox({
|
|
1490
1508
|
message: "Built-in tools to enable:",
|
|
1491
1509
|
choices: [
|
|
@@ -1495,35 +1513,59 @@ async function runInit() {
|
|
|
1495
1513
|
});
|
|
1496
1514
|
const tools = [];
|
|
1497
1515
|
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 }
|
|
1516
|
+
const restrictPaths = await confirm({
|
|
1517
|
+
message: "Restrict filesystem to specific paths?",
|
|
1518
|
+
default: false
|
|
1509
1519
|
});
|
|
1520
|
+
if (restrictPaths) {
|
|
1521
|
+
const pathsStr = await input({
|
|
1522
|
+
message: "Allowed paths (comma-separated):",
|
|
1523
|
+
default: process.env.HOME ?? "~"
|
|
1524
|
+
});
|
|
1525
|
+
const allowedPaths = pathsStr.split(",").map((p) => p.trim());
|
|
1526
|
+
tools.push({
|
|
1527
|
+
type: "builtin",
|
|
1528
|
+
name: "fs",
|
|
1529
|
+
builtin: "filesystem",
|
|
1530
|
+
description: "Read, list, and search files",
|
|
1531
|
+
config: { allowedPaths }
|
|
1532
|
+
});
|
|
1533
|
+
} else {
|
|
1534
|
+
tools.push({
|
|
1535
|
+
type: "builtin",
|
|
1536
|
+
name: "fs",
|
|
1537
|
+
builtin: "filesystem",
|
|
1538
|
+
description: "Read, list, and search files",
|
|
1539
|
+
config: {}
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1510
1542
|
}
|
|
1511
1543
|
if (selectedBuiltins.includes("shell")) {
|
|
1512
|
-
const
|
|
1513
|
-
message: "
|
|
1514
|
-
default:
|
|
1544
|
+
const restrictCommands = await confirm({
|
|
1545
|
+
message: "Restrict shell to a command allowlist?",
|
|
1546
|
+
default: false
|
|
1515
1547
|
});
|
|
1516
|
-
const
|
|
1517
|
-
|
|
1518
|
-
|
|
1548
|
+
const shellConfig = {};
|
|
1549
|
+
if (restrictCommands) {
|
|
1550
|
+
const allowlistStr = await input({
|
|
1551
|
+
message: "Allowed commands (comma-separated):",
|
|
1552
|
+
default: "ls,cat,head,tail,grep,find,wc,echo,date,whoami,pwd,git,npm,node"
|
|
1553
|
+
});
|
|
1554
|
+
shellConfig.allowlist = allowlistStr.split(",").map((c) => c.trim());
|
|
1555
|
+
}
|
|
1556
|
+
const blockSudo = await confirm({
|
|
1557
|
+
message: "Block sudo?",
|
|
1519
1558
|
default: false
|
|
1520
1559
|
});
|
|
1560
|
+
if (blockSudo) {
|
|
1561
|
+
shellConfig.allowSudo = false;
|
|
1562
|
+
}
|
|
1521
1563
|
tools.push({
|
|
1522
1564
|
type: "builtin",
|
|
1523
1565
|
name: "shell",
|
|
1524
1566
|
builtin: "shell",
|
|
1525
|
-
description: "Execute
|
|
1526
|
-
config:
|
|
1567
|
+
description: "Execute shell commands",
|
|
1568
|
+
config: shellConfig
|
|
1527
1569
|
});
|
|
1528
1570
|
}
|
|
1529
1571
|
const configFilePath = await detectClaudeDesktopConfig();
|
|
@@ -1603,7 +1645,8 @@ async function runInit() {
|
|
|
1603
1645
|
pairingTtlSeconds: 300,
|
|
1604
1646
|
sessionTtlSeconds: 14400
|
|
1605
1647
|
},
|
|
1606
|
-
tools
|
|
1648
|
+
tools,
|
|
1649
|
+
security: { default: securityTier, overrides: {} }
|
|
1607
1650
|
};
|
|
1608
1651
|
const configPath = resolve4("gigai.config.json");
|
|
1609
1652
|
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.1";
|
|
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-XQICZQ57.js");
|
|
13
13
|
} catch {
|
|
14
14
|
console.error("Server dependencies not installed.");
|
|
15
15
|
console.error("Run: npm install -g @schuttdev/gigai");
|