@agenshield/sandbox 0.6.2 → 0.7.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/backup.d.ts +3 -3
- package/backup.d.ts.map +1 -1
- package/detect.d.ts.map +1 -1
- package/directories.d.ts +3 -0
- package/directories.d.ts.map +1 -1
- package/guarded-shell.d.ts +2 -2
- package/guarded-shell.d.ts.map +1 -1
- package/host-scanner.d.ts +53 -0
- package/host-scanner.d.ts.map +1 -0
- package/index.d.ts +2 -1
- package/index.d.ts.map +1 -1
- package/index.js +793 -388
- package/macos.d.ts.map +1 -1
- package/migration.d.ts +20 -5
- package/migration.d.ts.map +1 -1
- package/package.json +2 -2
- package/presets/custom.d.ts.map +1 -1
- package/presets/dev-harness.d.ts.map +1 -1
- package/presets/openclaw.d.ts.map +1 -1
- package/presets/types.d.ts +13 -3
- package/presets/types.d.ts.map +1 -1
- package/shield-exec.d.ts +2 -2
- package/shield-exec.d.ts.map +1 -1
- package/skill-injector.d.ts +5 -0
- package/skill-injector.d.ts.map +1 -1
- package/types.d.ts +2 -2
- package/types.d.ts.map +1 -1
- package/wrappers.d.ts +42 -0
- package/wrappers.d.ts.map +1 -1
package/index.js
CHANGED
|
@@ -50,9 +50,13 @@ export HISTFILE="$HOME/.zsh_history"
|
|
|
50
50
|
# Suppress locale to prevent /etc/zshrc from calling locale command
|
|
51
51
|
export LC_ALL=C LANG=C
|
|
52
52
|
|
|
53
|
-
export PATH="$HOME/bin"
|
|
53
|
+
export PATH="$HOME/bin:$HOME/homebrew/bin"
|
|
54
54
|
export SHELL="/usr/local/bin/guarded-shell"
|
|
55
55
|
|
|
56
|
+
# NVM initialization
|
|
57
|
+
export NVM_DIR="$HOME/.nvm"
|
|
58
|
+
[ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"
|
|
59
|
+
|
|
56
60
|
# Clear any leftover env tricks
|
|
57
61
|
unset DYLD_LIBRARY_PATH DYLD_FALLBACK_LIBRARY_PATH DYLD_INSERT_LIBRARIES
|
|
58
62
|
unset PYTHONPATH NODE_PATH RUBYLIB PERL5LIB
|
|
@@ -71,8 +75,12 @@ emulate -LR zsh
|
|
|
71
75
|
# Re-set HISTFILE (safety: ensure it points to agent's home, not ZDOTDIR)
|
|
72
76
|
HISTFILE="$HOME/.zsh_history"
|
|
73
77
|
|
|
74
|
-
# Re-set PATH (
|
|
75
|
-
PATH="$HOME/bin"
|
|
78
|
+
# Re-set PATH (~/bin + ~/homebrew/bin \u2014 override anything that may have been added)
|
|
79
|
+
PATH="$HOME/bin:$HOME/homebrew/bin"
|
|
80
|
+
|
|
81
|
+
# NVM re-source for interactive shell
|
|
82
|
+
export NVM_DIR="$HOME/.nvm"
|
|
83
|
+
[ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"
|
|
76
84
|
|
|
77
85
|
# ---- Shell options ----
|
|
78
86
|
# Note: NOT using setopt RESTRICTED as it disables cd entirely.
|
|
@@ -81,7 +89,7 @@ setopt NO_CASE_GLOB
|
|
|
81
89
|
setopt NO_BEEP
|
|
82
90
|
|
|
83
91
|
# ---- Lock critical variables (readonly) ----
|
|
84
|
-
typeset -r PATH HOME SHELL HISTFILE
|
|
92
|
+
typeset -r PATH HOME SHELL HISTFILE NVM_DIR
|
|
85
93
|
|
|
86
94
|
# ---- Enforcement helpers ----
|
|
87
95
|
deny() {
|
|
@@ -109,8 +117,10 @@ is_allowed_cmd() {
|
|
|
109
117
|
local resolved
|
|
110
118
|
resolved="$(whence -p -- "$cmd" 2>/dev/null)" || return 1
|
|
111
119
|
|
|
112
|
-
# Must live under HOME/bin
|
|
120
|
+
# Must live under HOME/bin, HOME/homebrew/bin, or HOME/.nvm
|
|
113
121
|
[[ "$resolved" == "$HOME/bin/"* ]] && return 0
|
|
122
|
+
[[ "$resolved" == "$HOME/homebrew/bin/"* ]] && return 0
|
|
123
|
+
[[ "$resolved" == "$HOME/.nvm/"* ]] && return 0
|
|
114
124
|
return 1
|
|
115
125
|
}
|
|
116
126
|
|
|
@@ -161,7 +171,7 @@ __export(shield_exec_exports, {
|
|
|
161
171
|
SHIELD_EXEC_CONTENT: () => SHIELD_EXEC_CONTENT,
|
|
162
172
|
SHIELD_EXEC_PATH: () => SHIELD_EXEC_PATH
|
|
163
173
|
});
|
|
164
|
-
import * as
|
|
174
|
+
import * as path5 from "node:path";
|
|
165
175
|
import * as net from "node:net";
|
|
166
176
|
function sendRequest(socketPath, request) {
|
|
167
177
|
return new Promise((resolve6, reject) => {
|
|
@@ -208,7 +218,7 @@ function generateId() {
|
|
|
208
218
|
}
|
|
209
219
|
async function main() {
|
|
210
220
|
const socketPath = process.env["AGENSHIELD_SOCKET"] || DEFAULT_SOCKET_PATH;
|
|
211
|
-
const invoked =
|
|
221
|
+
const invoked = path5.basename(process.argv[1] || "shield-exec");
|
|
212
222
|
const args = process.argv.slice(2);
|
|
213
223
|
const commandName = invoked === "shield-exec" ? args.shift() || "" : invoked;
|
|
214
224
|
if (!commandName) {
|
|
@@ -222,7 +232,8 @@ async function main() {
|
|
|
222
232
|
params: {
|
|
223
233
|
command: commandName,
|
|
224
234
|
args,
|
|
225
|
-
cwd: process.cwd()
|
|
235
|
+
cwd: process.cwd(),
|
|
236
|
+
env: process.env
|
|
226
237
|
}
|
|
227
238
|
};
|
|
228
239
|
try {
|
|
@@ -232,18 +243,7 @@ async function main() {
|
|
|
232
243
|
`);
|
|
233
244
|
process.exit(1);
|
|
234
245
|
}
|
|
235
|
-
const
|
|
236
|
-
if (!result) {
|
|
237
|
-
process.stderr.write("Error: Empty response from broker\n");
|
|
238
|
-
process.exit(1);
|
|
239
|
-
}
|
|
240
|
-
if (!result.success) {
|
|
241
|
-
const errMsg = result.error?.message || "Unknown error";
|
|
242
|
-
process.stderr.write(`Error: ${errMsg}
|
|
243
|
-
`);
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
const data = result.data;
|
|
246
|
+
const data = response.result;
|
|
247
247
|
if (!data) {
|
|
248
248
|
process.exit(0);
|
|
249
249
|
}
|
|
@@ -267,6 +267,7 @@ var init_shield_exec = __esm({
|
|
|
267
267
|
SHIELD_EXEC_PATH = "/opt/agenshield/bin/shield-exec";
|
|
268
268
|
DEFAULT_SOCKET_PATH = "/var/run/agenshield/agenshield.sock";
|
|
269
269
|
PROXIED_COMMANDS = [
|
|
270
|
+
"bash",
|
|
270
271
|
"curl",
|
|
271
272
|
"wget",
|
|
272
273
|
"git",
|
|
@@ -346,7 +347,7 @@ async function main() {
|
|
|
346
347
|
jsonrpc: '2.0',
|
|
347
348
|
id: 'shield-exec-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8),
|
|
348
349
|
method: 'exec',
|
|
349
|
-
params: { command: commandName, args: args, cwd: process.cwd() },
|
|
350
|
+
params: { command: commandName, args: args, cwd: process.cwd(), env: process.env },
|
|
350
351
|
};
|
|
351
352
|
|
|
352
353
|
try {
|
|
@@ -355,13 +356,7 @@ async function main() {
|
|
|
355
356
|
process.stderr.write('Error: ' + response.error.message + '\\n');
|
|
356
357
|
process.exit(1);
|
|
357
358
|
}
|
|
358
|
-
const
|
|
359
|
-
if (!result) { process.stderr.write('Error: Empty response\\n'); process.exit(1); }
|
|
360
|
-
if (!result.success) {
|
|
361
|
-
process.stderr.write('Error: ' + (result.error?.message || 'Unknown error') + '\\n');
|
|
362
|
-
process.exit(1);
|
|
363
|
-
}
|
|
364
|
-
const data = result.data;
|
|
359
|
+
const data = response.result;
|
|
365
360
|
if (!data) process.exit(0);
|
|
366
361
|
if (data.stdout) process.stdout.write(data.stdout);
|
|
367
362
|
if (data.stderr) process.stderr.write(data.stderr);
|
|
@@ -389,8 +384,8 @@ __export(seatbelt_exports, {
|
|
|
389
384
|
installSeatbeltProfiles: () => installSeatbeltProfiles,
|
|
390
385
|
verifyProfile: () => verifyProfile
|
|
391
386
|
});
|
|
392
|
-
import * as
|
|
393
|
-
import * as
|
|
387
|
+
import * as fs9 from "node:fs/promises";
|
|
388
|
+
import * as path6 from "node:path";
|
|
394
389
|
import { exec as exec3 } from "node:child_process";
|
|
395
390
|
import { promisify as promisify3 } from "node:util";
|
|
396
391
|
function generateAgentProfile(options) {
|
|
@@ -612,14 +607,14 @@ ${binaryPath ? `(allow process-exec (literal "${binaryPath}"))` : ""}
|
|
|
612
607
|
async function installProfiles(options) {
|
|
613
608
|
const results = [];
|
|
614
609
|
try {
|
|
615
|
-
await
|
|
616
|
-
await
|
|
610
|
+
await fs9.mkdir(SEATBELT_DIR, { recursive: true });
|
|
611
|
+
await fs9.mkdir(path6.join(SEATBELT_DIR, "ops"), { recursive: true });
|
|
617
612
|
} catch {
|
|
618
613
|
}
|
|
619
614
|
const agentProfile = generateAgentProfile(options);
|
|
620
|
-
const agentPath =
|
|
615
|
+
const agentPath = path6.join(SEATBELT_DIR, "agent.sb");
|
|
621
616
|
try {
|
|
622
|
-
await
|
|
617
|
+
await fs9.writeFile(agentPath, agentProfile, { mode: 420 });
|
|
623
618
|
results.push({
|
|
624
619
|
success: true,
|
|
625
620
|
path: agentPath,
|
|
@@ -636,9 +631,9 @@ async function installProfiles(options) {
|
|
|
636
631
|
const operations = ["file_read", "file_write", "http_request", "exec"];
|
|
637
632
|
for (const op of operations) {
|
|
638
633
|
const profile = generateOperationProfile(op);
|
|
639
|
-
const profilePath =
|
|
634
|
+
const profilePath = path6.join(SEATBELT_DIR, "ops", `${op}.sb`);
|
|
640
635
|
try {
|
|
641
|
-
await
|
|
636
|
+
await fs9.writeFile(profilePath, profile, { mode: 420 });
|
|
642
637
|
results.push({
|
|
643
638
|
success: true,
|
|
644
639
|
path: profilePath,
|
|
@@ -657,7 +652,7 @@ async function installProfiles(options) {
|
|
|
657
652
|
}
|
|
658
653
|
async function verifyProfile(profilePath) {
|
|
659
654
|
try {
|
|
660
|
-
const content = await
|
|
655
|
+
const content = await fs9.readFile(profilePath, "utf-8");
|
|
661
656
|
if (!content.includes("(version 1)")) {
|
|
662
657
|
return false;
|
|
663
658
|
}
|
|
@@ -716,17 +711,17 @@ function generateAgentProfileFromConfig(config) {
|
|
|
716
711
|
async function getInstalledProfiles() {
|
|
717
712
|
const profiles = [];
|
|
718
713
|
try {
|
|
719
|
-
const mainFiles = await
|
|
714
|
+
const mainFiles = await fs9.readdir(SEATBELT_DIR);
|
|
720
715
|
for (const file of mainFiles) {
|
|
721
716
|
if (file.endsWith(".sb")) {
|
|
722
|
-
profiles.push(
|
|
717
|
+
profiles.push(path6.join(SEATBELT_DIR, file));
|
|
723
718
|
}
|
|
724
719
|
}
|
|
725
|
-
const opsDir =
|
|
726
|
-
const opsFiles = await
|
|
720
|
+
const opsDir = path6.join(SEATBELT_DIR, "ops");
|
|
721
|
+
const opsFiles = await fs9.readdir(opsDir);
|
|
727
722
|
for (const file of opsFiles) {
|
|
728
723
|
if (file.endsWith(".sb")) {
|
|
729
|
-
profiles.push(
|
|
724
|
+
profiles.push(path6.join(opsDir, file));
|
|
730
725
|
}
|
|
731
726
|
}
|
|
732
727
|
} catch {
|
|
@@ -1296,7 +1291,11 @@ function createDirectoryStructure(config) {
|
|
|
1296
1291
|
// setgid + group-writable for broker
|
|
1297
1292
|
owner: brokerUsername,
|
|
1298
1293
|
// broker needs write access
|
|
1299
|
-
group: socketGroupName
|
|
1294
|
+
group: socketGroupName,
|
|
1295
|
+
// ACL ensures broker retains write access even if openclaw resets ownership
|
|
1296
|
+
acl: [
|
|
1297
|
+
`${brokerUsername} allow read,write,append,add_subdirectory,add_file,delete_child,list,search,readattr,readextattr,writeattr,writeextattr,readsecurity,file_inherit,directory_inherit`
|
|
1298
|
+
]
|
|
1300
1299
|
},
|
|
1301
1300
|
[`${agentHome}/.openclaw/skills`]: {
|
|
1302
1301
|
mode: 1533,
|
|
@@ -1304,6 +1303,7 @@ function createDirectoryStructure(config) {
|
|
|
1304
1303
|
owner: brokerUsername,
|
|
1305
1304
|
// broker needs write access
|
|
1306
1305
|
group: socketGroupName
|
|
1306
|
+
// ACL inherited from .openclaw via file_inherit,directory_inherit
|
|
1307
1307
|
},
|
|
1308
1308
|
[`${agentHome}/workspace`]: {
|
|
1309
1309
|
mode: 1533,
|
|
@@ -1311,11 +1311,6 @@ function createDirectoryStructure(config) {
|
|
|
1311
1311
|
owner: agentUsername,
|
|
1312
1312
|
group: workspaceGroupName
|
|
1313
1313
|
},
|
|
1314
|
-
[`${agentHome}/.openclaw-pkg`]: {
|
|
1315
|
-
mode: 493,
|
|
1316
|
-
owner: agentUsername,
|
|
1317
|
-
group: socketGroupName
|
|
1318
|
-
},
|
|
1319
1314
|
[`${agentHome}/.nvm`]: {
|
|
1320
1315
|
mode: 493,
|
|
1321
1316
|
owner: agentUsername,
|
|
@@ -1347,6 +1342,12 @@ async function createDirectory(dirPath, options, verboseOptions) {
|
|
|
1347
1342
|
await execAsync2(`sudo chown ${options.owner}:${options.group} "${dirPath}"`);
|
|
1348
1343
|
log(`Running: sudo chmod ${options.mode.toString(8)} "${dirPath}"`);
|
|
1349
1344
|
await execAsync2(`sudo chmod ${options.mode.toString(8)} "${dirPath}"`);
|
|
1345
|
+
if (options.acl && process.platform === "darwin") {
|
|
1346
|
+
for (const entry of options.acl) {
|
|
1347
|
+
log(`Running: sudo chmod -R +a "${entry}" "${dirPath}"`);
|
|
1348
|
+
await execAsync2(`sudo chmod -R +a '${entry}' "${dirPath}"`);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1350
1351
|
return {
|
|
1351
1352
|
success: true,
|
|
1352
1353
|
path: dirPath,
|
|
@@ -1552,6 +1553,9 @@ function sudoCopyDir(src, dest) {
|
|
|
1552
1553
|
return sudoExec2(`cp -R "${src}/." "${dest}/"`);
|
|
1553
1554
|
}
|
|
1554
1555
|
function createOpenClawWrapper(user, dirs, method) {
|
|
1556
|
+
if (!dirs.packageDir) {
|
|
1557
|
+
return { success: false, error: "packageDir is not configured" };
|
|
1558
|
+
}
|
|
1555
1559
|
const wrapperPath = path.join(dirs.binDir, "openclaw");
|
|
1556
1560
|
let entryPath = path.join(dirs.packageDir, "dist", "entry.js");
|
|
1557
1561
|
try {
|
|
@@ -1590,21 +1594,14 @@ exec "\${AGENT_BIN}/node" "${entryPath}" "$@"
|
|
|
1590
1594
|
return { success: true };
|
|
1591
1595
|
}
|
|
1592
1596
|
function migrateNpmInstall(source, user, dirs) {
|
|
1597
|
+
if (!dirs.packageDir) {
|
|
1598
|
+
return { success: false, error: "packageDir is not configured" };
|
|
1599
|
+
}
|
|
1593
1600
|
let result = sudoCopyDir(source.packagePath, dirs.packageDir);
|
|
1594
1601
|
if (!result.success) {
|
|
1595
1602
|
return { success: false, error: `Failed to copy package: ${result.error}` };
|
|
1596
1603
|
}
|
|
1597
|
-
|
|
1598
|
-
result = sudoCopyDir(source.configPath, dirs.configDir);
|
|
1599
|
-
if (!result.success) {
|
|
1600
|
-
return { success: false, error: `Failed to copy config: ${result.error}` };
|
|
1601
|
-
}
|
|
1602
|
-
sudoExec2(`rm -f "${dirs.configDir}/secrets.json" 2>/dev/null`);
|
|
1603
|
-
sudoExec2(`rm -f "${dirs.configDir}/.env" 2>/dev/null`);
|
|
1604
|
-
sudoExec2(`rm -f "${dirs.configDir}/credentials.json" 2>/dev/null`);
|
|
1605
|
-
sudoExec2(`rm -rf "${dirs.configDir}/skills" 2>/dev/null`);
|
|
1606
|
-
injectSkillWatcherSetting(dirs.configDir);
|
|
1607
|
-
}
|
|
1604
|
+
copyConfigAndSanitize(source, dirs);
|
|
1608
1605
|
result = sudoExec2(`chown -R ${user.username}:${user.gid} "${dirs.packageDir}"`);
|
|
1609
1606
|
if (!result.success) {
|
|
1610
1607
|
return { success: false, error: `Failed to set package ownership: ${result.error}` };
|
|
@@ -1629,22 +1626,15 @@ function migrateNpmInstall(source, user, dirs) {
|
|
|
1629
1626
|
};
|
|
1630
1627
|
}
|
|
1631
1628
|
function migrateGitInstall(source, user, dirs) {
|
|
1629
|
+
if (!dirs.packageDir) {
|
|
1630
|
+
return { success: false, error: "packageDir is not configured" };
|
|
1631
|
+
}
|
|
1632
1632
|
const repoPath = source.gitRepoPath || source.packagePath;
|
|
1633
1633
|
let result = sudoCopyDir(repoPath, dirs.packageDir);
|
|
1634
1634
|
if (!result.success) {
|
|
1635
1635
|
return { success: false, error: `Failed to copy repo: ${result.error}` };
|
|
1636
1636
|
}
|
|
1637
|
-
|
|
1638
|
-
result = sudoCopyDir(source.configPath, dirs.configDir);
|
|
1639
|
-
if (!result.success) {
|
|
1640
|
-
return { success: false, error: `Failed to copy config: ${result.error}` };
|
|
1641
|
-
}
|
|
1642
|
-
sudoExec2(`rm -f "${dirs.configDir}/secrets.json" 2>/dev/null`);
|
|
1643
|
-
sudoExec2(`rm -f "${dirs.configDir}/.env" 2>/dev/null`);
|
|
1644
|
-
sudoExec2(`rm -f "${dirs.configDir}/credentials.json" 2>/dev/null`);
|
|
1645
|
-
sudoExec2(`rm -rf "${dirs.configDir}/skills" 2>/dev/null`);
|
|
1646
|
-
injectSkillWatcherSetting(dirs.configDir);
|
|
1647
|
-
}
|
|
1637
|
+
copyConfigAndSanitize(source, dirs);
|
|
1648
1638
|
result = sudoExec2(`chown -R ${user.username}:${user.gid} "${dirs.packageDir}"`);
|
|
1649
1639
|
if (!result.success) {
|
|
1650
1640
|
return { success: false, error: `Failed to set package ownership: ${result.error}` };
|
|
@@ -1675,19 +1665,38 @@ function migrateOpenClaw(source, user, dirs) {
|
|
|
1675
1665
|
return migrateGitInstall(source, user, dirs);
|
|
1676
1666
|
}
|
|
1677
1667
|
}
|
|
1678
|
-
function
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1668
|
+
function sanitizeOpenClawConfig(config) {
|
|
1669
|
+
const sanitized = JSON.parse(JSON.stringify(config));
|
|
1670
|
+
const skills = sanitized["skills"];
|
|
1671
|
+
if (skills?.entries) {
|
|
1672
|
+
for (const entry of Object.values(skills.entries)) {
|
|
1673
|
+
delete entry["env"];
|
|
1674
|
+
delete entry["apiKey"];
|
|
1684
1675
|
}
|
|
1676
|
+
}
|
|
1677
|
+
return sanitized;
|
|
1678
|
+
}
|
|
1679
|
+
function copyConfigAndSanitize(source, dirs) {
|
|
1680
|
+
sudoExec2(`mkdir -p "${dirs.configDir}"`);
|
|
1681
|
+
if (!source.configPath) {
|
|
1682
|
+
sudoExec2(`mkdir -p "${path.join(dirs.configDir, "skills")}"`);
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
sudoCopyDir(source.configPath, dirs.configDir);
|
|
1686
|
+
const sourceConfigPath = path.join(source.configPath, "openclaw.json");
|
|
1687
|
+
if (!fs3.existsSync(sourceConfigPath)) return;
|
|
1688
|
+
let config;
|
|
1689
|
+
try {
|
|
1690
|
+
config = JSON.parse(fs3.readFileSync(sourceConfigPath, "utf-8"));
|
|
1685
1691
|
} catch {
|
|
1686
|
-
|
|
1692
|
+
return;
|
|
1687
1693
|
}
|
|
1688
|
-
|
|
1694
|
+
const sanitized = sanitizeOpenClawConfig(config);
|
|
1695
|
+
const destConfigPath = path.join(dirs.configDir, "openclaw.json");
|
|
1696
|
+
const tempPath = "/tmp/openclaw-clean-config.json";
|
|
1689
1697
|
try {
|
|
1690
|
-
fs3.writeFileSync(
|
|
1698
|
+
fs3.writeFileSync(tempPath, JSON.stringify(sanitized, null, 2));
|
|
1699
|
+
sudoExec2(`mv "${tempPath}" "${destConfigPath}"`);
|
|
1691
1700
|
} catch {
|
|
1692
1701
|
}
|
|
1693
1702
|
}
|
|
@@ -1741,6 +1750,11 @@ exec "${nodePath}" "$@"
|
|
|
1741
1750
|
return { success: true };
|
|
1742
1751
|
}
|
|
1743
1752
|
|
|
1753
|
+
// libs/shield-sandbox/src/host-scanner.ts
|
|
1754
|
+
import * as fs5 from "node:fs";
|
|
1755
|
+
import * as path2 from "node:path";
|
|
1756
|
+
import * as os2 from "node:os";
|
|
1757
|
+
|
|
1744
1758
|
// libs/shield-sandbox/src/security.ts
|
|
1745
1759
|
import * as os from "node:os";
|
|
1746
1760
|
import * as fs4 from "node:fs";
|
|
@@ -1863,11 +1877,214 @@ function checkSecurityStatus(options) {
|
|
|
1863
1877
|
};
|
|
1864
1878
|
}
|
|
1865
1879
|
|
|
1866
|
-
// libs/shield-sandbox/src/
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1880
|
+
// libs/shield-sandbox/src/host-scanner.ts
|
|
1881
|
+
function scanOpenClawConfig(configJsonPath) {
|
|
1882
|
+
const skills = [];
|
|
1883
|
+
const envVars = [];
|
|
1884
|
+
const warnings = [];
|
|
1885
|
+
const configDir = path2.dirname(configJsonPath);
|
|
1886
|
+
const skillsDir = path2.join(configDir, "skills");
|
|
1887
|
+
let entries;
|
|
1888
|
+
if (fs5.existsSync(configJsonPath)) {
|
|
1889
|
+
try {
|
|
1890
|
+
const config = JSON.parse(fs5.readFileSync(configJsonPath, "utf-8"));
|
|
1891
|
+
entries = config.skills?.entries;
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
warnings.push(`Failed to parse config: ${err.message}`);
|
|
1894
|
+
}
|
|
1895
|
+
} else {
|
|
1896
|
+
warnings.push(`Config file not found: ${configJsonPath}`);
|
|
1897
|
+
}
|
|
1898
|
+
if (entries) {
|
|
1899
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
1900
|
+
const skillPath = path2.join(skillsDir, name);
|
|
1901
|
+
const skillExists = fs5.existsSync(skillPath);
|
|
1902
|
+
const skillMdPath = path2.join(skillPath, "SKILL.md");
|
|
1903
|
+
const hasSkillMd = skillExists && fs5.existsSync(skillMdPath);
|
|
1904
|
+
let description;
|
|
1905
|
+
if (hasSkillMd) {
|
|
1906
|
+
try {
|
|
1907
|
+
const content = fs5.readFileSync(skillMdPath, "utf-8");
|
|
1908
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1909
|
+
if (fmMatch) {
|
|
1910
|
+
const descMatch = fmMatch[1].match(/description:\s*(.+)/);
|
|
1911
|
+
if (descMatch) {
|
|
1912
|
+
description = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
} catch {
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
skills.push({
|
|
1919
|
+
name,
|
|
1920
|
+
enabled: entry.enabled !== false,
|
|
1921
|
+
envVars: entry.env ?? {},
|
|
1922
|
+
skillPath: skillExists ? skillPath : void 0,
|
|
1923
|
+
hasSkillMd,
|
|
1924
|
+
description
|
|
1925
|
+
});
|
|
1926
|
+
if (entry.env) {
|
|
1927
|
+
for (const [envName, envValue] of Object.entries(entry.env)) {
|
|
1928
|
+
envVars.push({
|
|
1929
|
+
name: envName,
|
|
1930
|
+
maskedValue: maskSecretValue(envValue),
|
|
1931
|
+
source: "app-config",
|
|
1932
|
+
isSecret: isSecretEnvVar(envName),
|
|
1933
|
+
associatedSkill: name
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
if (fs5.existsSync(skillsDir)) {
|
|
1940
|
+
const configSkillNames = new Set(skills.map((s) => s.name));
|
|
1941
|
+
try {
|
|
1942
|
+
const dirEntries = fs5.readdirSync(skillsDir, { withFileTypes: true });
|
|
1943
|
+
for (const dirEntry of dirEntries) {
|
|
1944
|
+
if (!dirEntry.isDirectory() || configSkillNames.has(dirEntry.name)) continue;
|
|
1945
|
+
const skillPath = path2.join(skillsDir, dirEntry.name);
|
|
1946
|
+
const skillMdPath = path2.join(skillPath, "SKILL.md");
|
|
1947
|
+
const pkgJsonPath = path2.join(skillPath, "package.json");
|
|
1948
|
+
const hasSkillMd = fs5.existsSync(skillMdPath);
|
|
1949
|
+
if (!hasSkillMd && !fs5.existsSync(pkgJsonPath)) continue;
|
|
1950
|
+
let description;
|
|
1951
|
+
if (hasSkillMd) {
|
|
1952
|
+
try {
|
|
1953
|
+
const content = fs5.readFileSync(skillMdPath, "utf-8");
|
|
1954
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1955
|
+
if (fmMatch) {
|
|
1956
|
+
const descMatch = fmMatch[1].match(/description:\s*(.+)/);
|
|
1957
|
+
if (descMatch) {
|
|
1958
|
+
description = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
} catch {
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
skills.push({
|
|
1965
|
+
name: dirEntry.name,
|
|
1966
|
+
enabled: false,
|
|
1967
|
+
envVars: {},
|
|
1968
|
+
skillPath,
|
|
1969
|
+
hasSkillMd,
|
|
1970
|
+
description
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
} catch {
|
|
1974
|
+
warnings.push(`Could not read skills directory: ${skillsDir}`);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
return { skills, envVars, warnings };
|
|
1978
|
+
}
|
|
1979
|
+
function scanProcessEnv() {
|
|
1980
|
+
const envVars = [];
|
|
1981
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1982
|
+
if (!value) continue;
|
|
1983
|
+
if (!isSecretEnvVar(key)) continue;
|
|
1984
|
+
envVars.push({
|
|
1985
|
+
name: key,
|
|
1986
|
+
maskedValue: maskSecretValue(value),
|
|
1987
|
+
source: "process-env",
|
|
1988
|
+
isSecret: true
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
return envVars;
|
|
1992
|
+
}
|
|
1993
|
+
var SHELL_PROFILES = [
|
|
1994
|
+
".zshrc",
|
|
1995
|
+
".bashrc",
|
|
1996
|
+
".profile",
|
|
1997
|
+
".bash_profile",
|
|
1998
|
+
".zprofile"
|
|
1999
|
+
];
|
|
2000
|
+
var EXPORT_REGEX = /^\s*export\s+([A-Za-z_][A-Za-z0-9_]*)=(.+)$/;
|
|
2001
|
+
function scanShellProfiles(home) {
|
|
2002
|
+
const envVars = [];
|
|
2003
|
+
const scannedProfiles = [];
|
|
2004
|
+
const warnings = [];
|
|
2005
|
+
for (const profile of SHELL_PROFILES) {
|
|
2006
|
+
const filePath = path2.join(home, profile);
|
|
2007
|
+
if (!fs5.existsSync(filePath)) continue;
|
|
2008
|
+
scannedProfiles.push(filePath);
|
|
2009
|
+
let content;
|
|
2010
|
+
try {
|
|
2011
|
+
content = fs5.readFileSync(filePath, "utf-8");
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
warnings.push(`Could not read ${filePath}: ${err.message}`);
|
|
2014
|
+
continue;
|
|
2015
|
+
}
|
|
2016
|
+
for (const line of content.split("\n")) {
|
|
2017
|
+
const match = line.match(EXPORT_REGEX);
|
|
2018
|
+
if (!match) continue;
|
|
2019
|
+
const name = match[1];
|
|
2020
|
+
let value = match[2].trim();
|
|
2021
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2022
|
+
value = value.slice(1, -1);
|
|
2023
|
+
}
|
|
2024
|
+
const commentIdx = value.indexOf(" #");
|
|
2025
|
+
if (commentIdx > 0) {
|
|
2026
|
+
value = value.slice(0, commentIdx).trim();
|
|
2027
|
+
}
|
|
2028
|
+
if (value.includes("$(") || value.includes("${") || value.startsWith("$")) {
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
2031
|
+
envVars.push({
|
|
2032
|
+
name,
|
|
2033
|
+
maskedValue: maskSecretValue(value),
|
|
2034
|
+
source: "shell-profile",
|
|
2035
|
+
profilePath: filePath,
|
|
2036
|
+
isSecret: isSecretEnvVar(name)
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
return { envVars, scannedProfiles, warnings };
|
|
2041
|
+
}
|
|
2042
|
+
function maskSecretValue(value) {
|
|
2043
|
+
if (value.length <= 8) {
|
|
2044
|
+
return "****";
|
|
2045
|
+
}
|
|
2046
|
+
return `${value.slice(0, 3)}...${value.slice(-4)}`;
|
|
2047
|
+
}
|
|
2048
|
+
function resolveEnvVarValue(name, source, profilePath, configJsonPath) {
|
|
2049
|
+
switch (source) {
|
|
2050
|
+
case "process-env":
|
|
2051
|
+
return process.env[name] ?? null;
|
|
2052
|
+
case "shell-profile": {
|
|
2053
|
+
if (!profilePath || !fs5.existsSync(profilePath)) return null;
|
|
2054
|
+
try {
|
|
2055
|
+
const content = fs5.readFileSync(profilePath, "utf-8");
|
|
2056
|
+
for (const line of content.split("\n")) {
|
|
2057
|
+
const match = line.match(EXPORT_REGEX);
|
|
2058
|
+
if (!match || match[1] !== name) continue;
|
|
2059
|
+
let value = match[2].trim();
|
|
2060
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2061
|
+
value = value.slice(1, -1);
|
|
2062
|
+
}
|
|
2063
|
+
return value;
|
|
2064
|
+
}
|
|
2065
|
+
} catch {
|
|
2066
|
+
}
|
|
2067
|
+
return null;
|
|
2068
|
+
}
|
|
2069
|
+
case "app-config": {
|
|
2070
|
+
if (!configJsonPath || !fs5.existsSync(configJsonPath)) return null;
|
|
2071
|
+
try {
|
|
2072
|
+
const config = JSON.parse(
|
|
2073
|
+
fs5.readFileSync(configJsonPath, "utf-8")
|
|
2074
|
+
);
|
|
2075
|
+
const entries = config.skills?.entries;
|
|
2076
|
+
if (!entries) return null;
|
|
2077
|
+
for (const entry of Object.values(entries)) {
|
|
2078
|
+
if (entry.env?.[name]) return entry.env[name];
|
|
2079
|
+
}
|
|
2080
|
+
} catch {
|
|
2081
|
+
}
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
default:
|
|
2085
|
+
return null;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
1871
2088
|
function getHome() {
|
|
1872
2089
|
const sudoUser = process.env["SUDO_USER"];
|
|
1873
2090
|
if (sudoUser) {
|
|
@@ -1876,7 +2093,64 @@ function getHome() {
|
|
|
1876
2093
|
}
|
|
1877
2094
|
return os2.homedir();
|
|
1878
2095
|
}
|
|
1879
|
-
|
|
2096
|
+
function scanHost(options = {}) {
|
|
2097
|
+
const home = options.home ?? getHome();
|
|
2098
|
+
const warnings = [];
|
|
2099
|
+
let skills = [];
|
|
2100
|
+
let configEnvVars = [];
|
|
2101
|
+
if (options.configPath) {
|
|
2102
|
+
const configResult = scanOpenClawConfig(options.configPath);
|
|
2103
|
+
skills = configResult.skills;
|
|
2104
|
+
configEnvVars = configResult.envVars;
|
|
2105
|
+
warnings.push(...configResult.warnings);
|
|
2106
|
+
}
|
|
2107
|
+
const profileResult = scanShellProfiles(home);
|
|
2108
|
+
warnings.push(...profileResult.warnings);
|
|
2109
|
+
const processEnvVars = scanProcessEnv();
|
|
2110
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2111
|
+
const allEnvVars = [];
|
|
2112
|
+
for (const v of configEnvVars) {
|
|
2113
|
+
if (!seen.has(v.name)) {
|
|
2114
|
+
seen.add(v.name);
|
|
2115
|
+
allEnvVars.push(v);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
for (const v of profileResult.envVars) {
|
|
2119
|
+
if (!seen.has(v.name)) {
|
|
2120
|
+
seen.add(v.name);
|
|
2121
|
+
allEnvVars.push(v);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
for (const v of processEnvVars) {
|
|
2125
|
+
if (!seen.has(v.name)) {
|
|
2126
|
+
seen.add(v.name);
|
|
2127
|
+
allEnvVars.push(v);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return {
|
|
2131
|
+
skills,
|
|
2132
|
+
envVars: allEnvVars,
|
|
2133
|
+
configPath: options.configPath,
|
|
2134
|
+
scannedProfiles: profileResult.scannedProfiles,
|
|
2135
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2136
|
+
warnings
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// libs/shield-sandbox/src/detect.ts
|
|
2141
|
+
import * as fs6 from "node:fs";
|
|
2142
|
+
import * as path3 from "node:path";
|
|
2143
|
+
import * as os3 from "node:os";
|
|
2144
|
+
import { execSync as execSync4 } from "node:child_process";
|
|
2145
|
+
function getHome2() {
|
|
2146
|
+
const sudoUser = process.env["SUDO_USER"];
|
|
2147
|
+
if (sudoUser) {
|
|
2148
|
+
const userHome = path3.join("/Users", sudoUser);
|
|
2149
|
+
if (fs6.existsSync(userHome)) return userHome;
|
|
2150
|
+
}
|
|
2151
|
+
return os3.homedir();
|
|
2152
|
+
}
|
|
2153
|
+
var HOME = getHome2();
|
|
1880
2154
|
function execSafe2(cmd) {
|
|
1881
2155
|
try {
|
|
1882
2156
|
return execSync4(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
@@ -1886,7 +2160,7 @@ function execSafe2(cmd) {
|
|
|
1886
2160
|
}
|
|
1887
2161
|
function pathExists(p) {
|
|
1888
2162
|
try {
|
|
1889
|
-
|
|
2163
|
+
fs6.accessSync(p);
|
|
1890
2164
|
return true;
|
|
1891
2165
|
} catch {
|
|
1892
2166
|
return false;
|
|
@@ -1901,19 +2175,19 @@ function getNpmGlobalBin() {
|
|
|
1901
2175
|
function detectNpmInstall() {
|
|
1902
2176
|
const npmRoot = getNpmGlobalRoot();
|
|
1903
2177
|
if (!npmRoot) return { found: false, method: "npm" };
|
|
1904
|
-
const openclawPkg =
|
|
2178
|
+
const openclawPkg = path3.join(npmRoot, "openclaw");
|
|
1905
2179
|
if (!pathExists(openclawPkg)) return { found: false, method: "npm" };
|
|
1906
2180
|
let version;
|
|
1907
|
-
const pkgJsonPath =
|
|
2181
|
+
const pkgJsonPath = path3.join(openclawPkg, "package.json");
|
|
1908
2182
|
if (pathExists(pkgJsonPath)) {
|
|
1909
2183
|
try {
|
|
1910
|
-
const pkg = JSON.parse(
|
|
2184
|
+
const pkg = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf-8"));
|
|
1911
2185
|
version = pkg.version;
|
|
1912
2186
|
} catch {
|
|
1913
2187
|
}
|
|
1914
2188
|
}
|
|
1915
2189
|
const npmBin = getNpmGlobalBin();
|
|
1916
|
-
const binaryPath = npmBin ?
|
|
2190
|
+
const binaryPath = npmBin ? path3.join(npmBin, "openclaw") : void 0;
|
|
1917
2191
|
return {
|
|
1918
2192
|
found: true,
|
|
1919
2193
|
method: "npm",
|
|
@@ -1924,20 +2198,20 @@ function detectNpmInstall() {
|
|
|
1924
2198
|
}
|
|
1925
2199
|
function detectGitInstall() {
|
|
1926
2200
|
const possiblePaths = [
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2201
|
+
path3.join(HOME, "openclaw"),
|
|
2202
|
+
path3.join(HOME, ".openclaw-src"),
|
|
2203
|
+
path3.join(HOME, "code", "openclaw"),
|
|
2204
|
+
path3.join(HOME, "src", "openclaw")
|
|
1931
2205
|
];
|
|
1932
2206
|
for (const repoPath of possiblePaths) {
|
|
1933
2207
|
if (!pathExists(repoPath)) continue;
|
|
1934
|
-
const gitDir =
|
|
1935
|
-
const pkgJson =
|
|
2208
|
+
const gitDir = path3.join(repoPath, ".git");
|
|
2209
|
+
const pkgJson = path3.join(repoPath, "package.json");
|
|
1936
2210
|
if (pathExists(gitDir) && pathExists(pkgJson)) {
|
|
1937
2211
|
try {
|
|
1938
|
-
const pkg = JSON.parse(
|
|
2212
|
+
const pkg = JSON.parse(fs6.readFileSync(pkgJson, "utf-8"));
|
|
1939
2213
|
if (pkg.name === "openclaw" || pkg.name?.includes("openclaw")) {
|
|
1940
|
-
const wrapperPath2 =
|
|
2214
|
+
const wrapperPath2 = path3.join(HOME, ".local", "bin", "openclaw");
|
|
1941
2215
|
return {
|
|
1942
2216
|
found: true,
|
|
1943
2217
|
method: "git",
|
|
@@ -1951,19 +2225,19 @@ function detectGitInstall() {
|
|
|
1951
2225
|
}
|
|
1952
2226
|
}
|
|
1953
2227
|
}
|
|
1954
|
-
const wrapperPath =
|
|
2228
|
+
const wrapperPath = path3.join(HOME, ".local", "bin", "openclaw");
|
|
1955
2229
|
if (pathExists(wrapperPath)) {
|
|
1956
2230
|
try {
|
|
1957
|
-
const content =
|
|
2231
|
+
const content = fs6.readFileSync(wrapperPath, "utf-8");
|
|
1958
2232
|
const match = content.match(/exec node "([^"]+)\/dist\/entry\.js"/);
|
|
1959
2233
|
if (match) {
|
|
1960
2234
|
const repoPath = match[1];
|
|
1961
2235
|
if (pathExists(repoPath)) {
|
|
1962
|
-
const pkgJson =
|
|
2236
|
+
const pkgJson = path3.join(repoPath, "package.json");
|
|
1963
2237
|
let version;
|
|
1964
2238
|
if (pathExists(pkgJson)) {
|
|
1965
2239
|
try {
|
|
1966
|
-
version = JSON.parse(
|
|
2240
|
+
version = JSON.parse(fs6.readFileSync(pkgJson, "utf-8")).version;
|
|
1967
2241
|
} catch {
|
|
1968
2242
|
}
|
|
1969
2243
|
}
|
|
@@ -1983,7 +2257,7 @@ function detectGitInstall() {
|
|
|
1983
2257
|
return { found: false, method: "git" };
|
|
1984
2258
|
}
|
|
1985
2259
|
function detectConfigDir() {
|
|
1986
|
-
const configPath =
|
|
2260
|
+
const configPath = path3.join(HOME, ".openclaw");
|
|
1987
2261
|
return pathExists(configPath) ? configPath : void 0;
|
|
1988
2262
|
}
|
|
1989
2263
|
function getVersionViaCli() {
|
|
@@ -2016,7 +2290,8 @@ function detectOpenClaw() {
|
|
|
2016
2290
|
} else {
|
|
2017
2291
|
installation = {
|
|
2018
2292
|
found: false,
|
|
2019
|
-
method: "unknown"
|
|
2293
|
+
method: "unknown",
|
|
2294
|
+
configPath: detectConfigDir()
|
|
2020
2295
|
};
|
|
2021
2296
|
}
|
|
2022
2297
|
if (installation.found && !installation.version) {
|
|
@@ -2055,8 +2330,8 @@ function checkPrerequisites() {
|
|
|
2055
2330
|
}
|
|
2056
2331
|
|
|
2057
2332
|
// libs/shield-sandbox/src/backup.ts
|
|
2058
|
-
import * as
|
|
2059
|
-
import * as
|
|
2333
|
+
import * as fs7 from "node:fs";
|
|
2334
|
+
import * as os4 from "node:os";
|
|
2060
2335
|
import { execSync as execSync5 } from "node:child_process";
|
|
2061
2336
|
|
|
2062
2337
|
// libs/shield-ipc/dist/index.js
|
|
@@ -2133,13 +2408,17 @@ var DaemonConfigSchema = z.object({
|
|
|
2133
2408
|
enableHostsEntry: z.boolean().default(false)
|
|
2134
2409
|
});
|
|
2135
2410
|
var PolicyConfigSchema = z.object({
|
|
2136
|
-
id: z.string().
|
|
2411
|
+
id: z.string().min(1),
|
|
2137
2412
|
name: z.string().min(1).max(100),
|
|
2138
2413
|
action: z.enum(["allow", "deny", "approval"]),
|
|
2139
2414
|
target: z.enum(["skill", "command", "url", "filesystem"]),
|
|
2140
2415
|
patterns: z.array(z.string()),
|
|
2141
2416
|
enabled: z.boolean().default(true),
|
|
2142
|
-
|
|
2417
|
+
priority: z.number().optional(),
|
|
2418
|
+
operations: z.array(z.string()).optional(),
|
|
2419
|
+
preset: z.string().optional(),
|
|
2420
|
+
scope: z.string().optional(),
|
|
2421
|
+
networkAccess: z.enum(["none", "proxy", "direct"]).optional()
|
|
2143
2422
|
});
|
|
2144
2423
|
var VaultConfigSchema = z.object({
|
|
2145
2424
|
enabled: z.boolean(),
|
|
@@ -2235,7 +2514,8 @@ var PolicyRuleSchema = z3.object({
|
|
|
2235
2514
|
operations: z3.array(OperationTypeSchema),
|
|
2236
2515
|
patterns: z3.array(z3.string()),
|
|
2237
2516
|
enabled: z3.boolean(),
|
|
2238
|
-
priority: z3.number().optional()
|
|
2517
|
+
priority: z3.number().optional(),
|
|
2518
|
+
scope: z3.string().optional()
|
|
2239
2519
|
});
|
|
2240
2520
|
var FsConstraintsSchema = z3.object({
|
|
2241
2521
|
allowedPaths: z3.array(z3.string()),
|
|
@@ -3267,22 +3547,22 @@ function saveBackup(params) {
|
|
|
3267
3547
|
const backup = {
|
|
3268
3548
|
version: "1.0",
|
|
3269
3549
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3270
|
-
originalUser:
|
|
3271
|
-
originalUserHome:
|
|
3550
|
+
originalUser: os4.userInfo().username,
|
|
3551
|
+
originalUserHome: os4.homedir(),
|
|
3272
3552
|
originalInstallation,
|
|
3273
3553
|
sandboxUser,
|
|
3274
3554
|
migratedPaths
|
|
3275
3555
|
};
|
|
3276
3556
|
const tempPath = "/tmp/agenshield-backup.json";
|
|
3277
3557
|
try {
|
|
3278
|
-
|
|
3558
|
+
fs7.writeFileSync(tempPath, JSON.stringify(backup, null, 2), { mode: 384 });
|
|
3279
3559
|
} catch (err) {
|
|
3280
3560
|
return { success: false, error: `Failed to write temp backup: ${err}` };
|
|
3281
3561
|
}
|
|
3282
3562
|
let result = sudoExec3(`mv "${tempPath}" "${BACKUP_CONFIG.backupPath}"`);
|
|
3283
3563
|
if (!result.success) {
|
|
3284
3564
|
try {
|
|
3285
|
-
|
|
3565
|
+
fs7.unlinkSync(tempPath);
|
|
3286
3566
|
} catch {
|
|
3287
3567
|
}
|
|
3288
3568
|
return { success: false, error: `Failed to install backup: ${result.error}` };
|
|
@@ -3321,32 +3601,22 @@ function deleteBackup() {
|
|
|
3321
3601
|
const result = sudoExec3(`rm -f "${BACKUP_CONFIG.backupPath}"`);
|
|
3322
3602
|
return result;
|
|
3323
3603
|
}
|
|
3324
|
-
function backupOriginalConfig(
|
|
3325
|
-
|
|
3326
|
-
return { success: true };
|
|
3327
|
-
}
|
|
3328
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3329
|
-
const backupPath = `${configPath}.backup-${timestamp}`;
|
|
3330
|
-
try {
|
|
3331
|
-
fs6.renameSync(configPath, backupPath);
|
|
3332
|
-
return { success: true, backupPath };
|
|
3333
|
-
} catch (err) {
|
|
3334
|
-
return { success: false, error: `Failed to backup config: ${err}` };
|
|
3335
|
-
}
|
|
3604
|
+
function backupOriginalConfig(_configPath) {
|
|
3605
|
+
return { success: true };
|
|
3336
3606
|
}
|
|
3337
3607
|
function restoreOriginalConfig(backupPath, targetPath) {
|
|
3338
|
-
if (!
|
|
3608
|
+
if (!fs7.existsSync(backupPath)) {
|
|
3339
3609
|
return { success: false, error: `Backup path does not exist: ${backupPath}` };
|
|
3340
3610
|
}
|
|
3341
|
-
if (
|
|
3611
|
+
if (fs7.existsSync(targetPath)) {
|
|
3342
3612
|
try {
|
|
3343
|
-
|
|
3613
|
+
fs7.rmSync(targetPath, { recursive: true });
|
|
3344
3614
|
} catch (err) {
|
|
3345
3615
|
return { success: false, error: `Failed to remove existing config: ${err}` };
|
|
3346
3616
|
}
|
|
3347
3617
|
}
|
|
3348
3618
|
try {
|
|
3349
|
-
|
|
3619
|
+
fs7.renameSync(backupPath, targetPath);
|
|
3350
3620
|
return { success: true };
|
|
3351
3621
|
} catch (err) {
|
|
3352
3622
|
return { success: false, error: `Failed to restore config: ${err}` };
|
|
@@ -3354,8 +3624,8 @@ function restoreOriginalConfig(backupPath, targetPath) {
|
|
|
3354
3624
|
}
|
|
3355
3625
|
|
|
3356
3626
|
// libs/shield-sandbox/src/restore.ts
|
|
3357
|
-
import * as
|
|
3358
|
-
import * as
|
|
3627
|
+
import * as fs8 from "node:fs";
|
|
3628
|
+
import * as path4 from "node:path";
|
|
3359
3629
|
import { execSync as execSync6 } from "node:child_process";
|
|
3360
3630
|
init_guarded_shell();
|
|
3361
3631
|
function sudoExec4(cmd) {
|
|
@@ -3410,7 +3680,7 @@ function waitForProcessExit(pid, timeoutMs = 5e3) {
|
|
|
3410
3680
|
}
|
|
3411
3681
|
function stopDaemon() {
|
|
3412
3682
|
const plistPath = "/Library/LaunchDaemons/com.agenshield.daemon.plist";
|
|
3413
|
-
if (
|
|
3683
|
+
if (fs8.existsSync(plistPath)) {
|
|
3414
3684
|
sudoExec4(`rm -f "${plistPath}"`);
|
|
3415
3685
|
sudoExec4(`launchctl unload "${plistPath}" 2>/dev/null || true`);
|
|
3416
3686
|
}
|
|
@@ -3448,7 +3718,7 @@ function stopDaemon() {
|
|
|
3448
3718
|
}
|
|
3449
3719
|
function stopBrokerDaemon() {
|
|
3450
3720
|
const plistPath = "/Library/LaunchDaemons/com.agenshield.broker.plist";
|
|
3451
|
-
if (!
|
|
3721
|
+
if (!fs8.existsSync(plistPath)) {
|
|
3452
3722
|
return {
|
|
3453
3723
|
step: "stop-broker",
|
|
3454
3724
|
success: true,
|
|
@@ -3491,14 +3761,14 @@ function restoreConfig(backup) {
|
|
|
3491
3761
|
message: "No config backup to restore"
|
|
3492
3762
|
};
|
|
3493
3763
|
}
|
|
3494
|
-
if (!
|
|
3764
|
+
if (!fs8.existsSync(configBackupPath)) {
|
|
3495
3765
|
return {
|
|
3496
3766
|
step: "restore-config",
|
|
3497
3767
|
success: true,
|
|
3498
3768
|
message: `Config backup not found at ${configBackupPath}, skipping`
|
|
3499
3769
|
};
|
|
3500
3770
|
}
|
|
3501
|
-
const targetPath = configPath ||
|
|
3771
|
+
const targetPath = configPath || path4.join(backup.originalUserHome, ".openclaw");
|
|
3502
3772
|
const result = restoreOriginalConfig(configBackupPath, targetPath);
|
|
3503
3773
|
if (!result.success) {
|
|
3504
3774
|
return {
|
|
@@ -3523,7 +3793,7 @@ function restorePackage(backup) {
|
|
|
3523
3793
|
message: "npm package was not moved (still at original location)"
|
|
3524
3794
|
};
|
|
3525
3795
|
}
|
|
3526
|
-
if (packagePath &&
|
|
3796
|
+
if (packagePath && fs8.existsSync(packagePath)) {
|
|
3527
3797
|
return {
|
|
3528
3798
|
step: "restore-package",
|
|
3529
3799
|
success: true,
|
|
@@ -3554,7 +3824,7 @@ function deleteUser2(backup) {
|
|
|
3554
3824
|
};
|
|
3555
3825
|
}
|
|
3556
3826
|
function removeGuardedShell() {
|
|
3557
|
-
if (!
|
|
3827
|
+
if (!fs8.existsSync(GUARDED_SHELL_PATH)) {
|
|
3558
3828
|
return {
|
|
3559
3829
|
step: "remove-shell",
|
|
3560
3830
|
success: true,
|
|
@@ -3590,7 +3860,7 @@ function cleanup() {
|
|
|
3590
3860
|
];
|
|
3591
3861
|
const errors = [];
|
|
3592
3862
|
for (const p of cleanupPaths) {
|
|
3593
|
-
if (
|
|
3863
|
+
if (fs8.existsSync(p)) {
|
|
3594
3864
|
const result = sudoExec4(`rm -rf "${p}"`);
|
|
3595
3865
|
if (!result.success) {
|
|
3596
3866
|
errors.push(`Failed to remove ${p}: ${result.error}`);
|
|
@@ -3614,7 +3884,7 @@ function cleanup() {
|
|
|
3614
3884
|
function verify(backup) {
|
|
3615
3885
|
const { binaryPath, method } = backup.originalInstallation;
|
|
3616
3886
|
let cmd;
|
|
3617
|
-
if (binaryPath &&
|
|
3887
|
+
if (binaryPath && fs8.existsSync(binaryPath)) {
|
|
3618
3888
|
cmd = `"${binaryPath}" --version`;
|
|
3619
3889
|
} else if (method === "npm") {
|
|
3620
3890
|
cmd = "openclaw --version";
|
|
@@ -3748,12 +4018,12 @@ function discoverSocketGroups() {
|
|
|
3748
4018
|
}
|
|
3749
4019
|
}
|
|
3750
4020
|
function isDaemonPresent() {
|
|
3751
|
-
if (
|
|
4021
|
+
if (fs8.existsSync("/Library/LaunchDaemons/com.agenshield.daemon.plist")) return true;
|
|
3752
4022
|
if (findDaemonPidByPort(DEFAULT_PORT)) return true;
|
|
3753
4023
|
return false;
|
|
3754
4024
|
}
|
|
3755
4025
|
function isBrokerPresent() {
|
|
3756
|
-
return
|
|
4026
|
+
return fs8.existsSync("/Library/LaunchDaemons/com.agenshield.broker.plist");
|
|
3757
4027
|
}
|
|
3758
4028
|
function forceUninstall(onProgress) {
|
|
3759
4029
|
const steps = [];
|
|
@@ -3861,9 +4131,9 @@ function forceUninstall(onProgress) {
|
|
|
3861
4131
|
init_shield_exec();
|
|
3862
4132
|
|
|
3863
4133
|
// libs/shield-sandbox/src/wrappers.ts
|
|
3864
|
-
import * as
|
|
3865
|
-
import * as
|
|
3866
|
-
import { exec as exec4 } from "node:child_process";
|
|
4134
|
+
import * as fs10 from "node:fs/promises";
|
|
4135
|
+
import * as path7 from "node:path";
|
|
4136
|
+
import { exec as exec4, spawn as nodeSpawn } from "node:child_process";
|
|
3867
4137
|
import { promisify as promisify4 } from "node:util";
|
|
3868
4138
|
import { createRequire } from "node:module";
|
|
3869
4139
|
var require2 = createRequire(import.meta.url);
|
|
@@ -3882,7 +4152,8 @@ function getDefaultWrapperConfig(userConfig) {
|
|
|
3882
4152
|
pythonPath: "/usr/bin/python3",
|
|
3883
4153
|
nodePath: "/usr/local/bin/node",
|
|
3884
4154
|
npmPath: "/usr/local/bin/npm",
|
|
3885
|
-
brewPath: "/opt/homebrew/bin/brew"
|
|
4155
|
+
brewPath: "/opt/homebrew/bin/brew",
|
|
4156
|
+
nvmNodeDir: `${agentHome}/.nvm/versions/node/current`
|
|
3886
4157
|
};
|
|
3887
4158
|
}
|
|
3888
4159
|
var WRAPPER_DEFINITIONS = {
|
|
@@ -3995,31 +4266,19 @@ esac
|
|
|
3995
4266
|
`
|
|
3996
4267
|
},
|
|
3997
4268
|
npm: {
|
|
3998
|
-
description: "npm wrapper
|
|
3999
|
-
usesInterceptor: true,
|
|
4269
|
+
description: "npm wrapper \u2014 delegates to NVM npm (patched node loads interceptor)",
|
|
4000
4270
|
generate: (config) => `#!/bin/bash
|
|
4001
|
-
# npm wrapper
|
|
4002
|
-
#
|
|
4271
|
+
# npm wrapper \u2014 NVM's node is patched in-place, so the interceptor loads
|
|
4272
|
+
# automatically when npm (a JS script) invokes node. No NODE_OPTIONS needed here.
|
|
4003
4273
|
|
|
4004
4274
|
# Ensure accessible working directory
|
|
4005
4275
|
if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
|
|
4006
4276
|
|
|
4007
|
-
#
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
# Set AgenShield environment
|
|
4011
|
-
export AGENSHIELD_SOCKET="${config.socketPath}"
|
|
4012
|
-
export AGENSHIELD_HTTP_PORT="${config.httpPort}"
|
|
4013
|
-
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
4014
|
-
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
4015
|
-
|
|
4016
|
-
# Find npm - prefer homebrew, then system
|
|
4017
|
-
if [ -x "/opt/homebrew/bin/npm" ]; then
|
|
4018
|
-
exec /opt/homebrew/bin/npm "$@"
|
|
4019
|
-
elif [ -x "${config.npmPath}" ]; then
|
|
4020
|
-
exec ${config.npmPath} "$@"
|
|
4277
|
+
# Only use NVM npm (patched NVM node loads interceptor automatically)
|
|
4278
|
+
if [ -x "${config.nvmNodeDir}/bin/npm" ]; then
|
|
4279
|
+
exec "${config.nvmNodeDir}/bin/npm" "$@"
|
|
4021
4280
|
else
|
|
4022
|
-
echo "npm not found" >&2
|
|
4281
|
+
echo "npm not found (expected at ${config.nvmNodeDir}/bin/npm)" >&2
|
|
4023
4282
|
exit 1
|
|
4024
4283
|
fi
|
|
4025
4284
|
`
|
|
@@ -4094,54 +4353,38 @@ exec ${config.agentHome}/bin/python "$@"
|
|
|
4094
4353
|
# Ensure accessible working directory
|
|
4095
4354
|
if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
|
|
4096
4355
|
|
|
4097
|
-
# Set up interceptor
|
|
4098
4356
|
export NODE_OPTIONS="${config.interceptorFlag} ${config.interceptorPath} \${NODE_OPTIONS:-}"
|
|
4099
|
-
|
|
4100
|
-
# Set AgenShield environment
|
|
4101
4357
|
export AGENSHIELD_SOCKET="${config.socketPath}"
|
|
4102
4358
|
export AGENSHIELD_HTTP_PORT="${config.httpPort}"
|
|
4103
|
-
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
4104
|
-
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
4105
4359
|
export AGENSHIELD_INTERCEPT_EXEC=true
|
|
4360
|
+
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
4361
|
+
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
4362
|
+
export AGENSHIELD_INTERCEPT_WS=true
|
|
4363
|
+
export AGENSHIELD_CONTEXT_TYPE=\${AGENSHIELD_CONTEXT_TYPE:-agent}
|
|
4106
4364
|
|
|
4107
|
-
#
|
|
4365
|
+
# Only use the NVM-installed node binary (copied to /opt/agenshield/bin/node-bin)
|
|
4108
4366
|
if [ -x "/opt/agenshield/bin/node-bin" ]; then
|
|
4109
4367
|
exec /opt/agenshield/bin/node-bin "$@"
|
|
4110
|
-
elif [ -x "/opt/homebrew/bin/node" ]; then
|
|
4111
|
-
exec /opt/homebrew/bin/node "$@"
|
|
4112
|
-
elif [ -x "${config.nodePath}" ]; then
|
|
4113
|
-
exec ${config.nodePath} "$@"
|
|
4114
4368
|
else
|
|
4115
|
-
echo "node not found" >&2
|
|
4369
|
+
echo "node-bin not found at /opt/agenshield/bin/node-bin" >&2
|
|
4116
4370
|
exit 1
|
|
4117
4371
|
fi
|
|
4118
4372
|
`
|
|
4119
4373
|
},
|
|
4120
4374
|
npx: {
|
|
4121
|
-
description: "npx wrapper
|
|
4122
|
-
usesInterceptor: true,
|
|
4375
|
+
description: "npx wrapper \u2014 delegates to NVM npx (patched node loads interceptor)",
|
|
4123
4376
|
generate: (config) => `#!/bin/bash
|
|
4124
|
-
# npx wrapper
|
|
4377
|
+
# npx wrapper \u2014 NVM's node is patched in-place, so the interceptor loads
|
|
4378
|
+
# automatically when npx (a JS script) invokes node. No NODE_OPTIONS needed here.
|
|
4125
4379
|
|
|
4126
4380
|
# Ensure accessible working directory
|
|
4127
4381
|
if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
|
|
4128
4382
|
|
|
4129
|
-
#
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
# Set AgenShield environment
|
|
4133
|
-
export AGENSHIELD_SOCKET="${config.socketPath}"
|
|
4134
|
-
export AGENSHIELD_HTTP_PORT="${config.httpPort}"
|
|
4135
|
-
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
4136
|
-
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
4137
|
-
|
|
4138
|
-
# Find npx
|
|
4139
|
-
if [ -x "/opt/homebrew/bin/npx" ]; then
|
|
4140
|
-
exec /opt/homebrew/bin/npx "$@"
|
|
4141
|
-
elif [ -x "/usr/local/bin/npx" ]; then
|
|
4142
|
-
exec /usr/local/bin/npx "$@"
|
|
4383
|
+
# Only use NVM npx (patched NVM node loads interceptor automatically)
|
|
4384
|
+
if [ -x "${config.nvmNodeDir}/bin/npx" ]; then
|
|
4385
|
+
exec "${config.nvmNodeDir}/bin/npx" "$@"
|
|
4143
4386
|
else
|
|
4144
|
-
echo "npx not found" >&2
|
|
4387
|
+
echo "npx not found (expected at ${config.nvmNodeDir}/bin/npx)" >&2
|
|
4145
4388
|
exit 1
|
|
4146
4389
|
fi
|
|
4147
4390
|
`
|
|
@@ -4241,9 +4484,9 @@ function generateWrapperContent(name, config) {
|
|
|
4241
4484
|
return def.generate(config || getDefaultWrapperConfig());
|
|
4242
4485
|
}
|
|
4243
4486
|
async function installWrapper(name, content, targetDir) {
|
|
4244
|
-
const wrapperPath =
|
|
4487
|
+
const wrapperPath = path7.join(targetDir, name);
|
|
4245
4488
|
try {
|
|
4246
|
-
await
|
|
4489
|
+
await fs10.writeFile(wrapperPath, content, { mode: 493 });
|
|
4247
4490
|
return {
|
|
4248
4491
|
success: true,
|
|
4249
4492
|
name,
|
|
@@ -4261,7 +4504,7 @@ async function installWrapper(name, content, targetDir) {
|
|
|
4261
4504
|
}
|
|
4262
4505
|
}
|
|
4263
4506
|
async function installWrapperWithSudo(name, content, targetDir, owner, group) {
|
|
4264
|
-
const wrapperPath =
|
|
4507
|
+
const wrapperPath = path7.join(targetDir, name);
|
|
4265
4508
|
try {
|
|
4266
4509
|
await execAsync4(`sudo tee "${wrapperPath}" > /dev/null << 'EOF'
|
|
4267
4510
|
${content}
|
|
@@ -4290,7 +4533,7 @@ async function installWrappers(targetDir = "/Users/agenshield_agent/bin", config
|
|
|
4290
4533
|
const results = [];
|
|
4291
4534
|
const wrapperConfig = config || getDefaultWrapperConfig();
|
|
4292
4535
|
try {
|
|
4293
|
-
await
|
|
4536
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
4294
4537
|
} catch {
|
|
4295
4538
|
try {
|
|
4296
4539
|
await execAsync4(`sudo mkdir -p "${targetDir}"`);
|
|
@@ -4311,7 +4554,7 @@ async function installSpecificWrappers(names, targetDir, config) {
|
|
|
4311
4554
|
const results = [];
|
|
4312
4555
|
const wrapperConfig = config || getDefaultWrapperConfig();
|
|
4313
4556
|
try {
|
|
4314
|
-
await
|
|
4557
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
4315
4558
|
} catch {
|
|
4316
4559
|
try {
|
|
4317
4560
|
await execAsync4(`sudo mkdir -p "${targetDir}"`);
|
|
@@ -4324,7 +4567,7 @@ async function installSpecificWrappers(names, targetDir, config) {
|
|
|
4324
4567
|
results.push({
|
|
4325
4568
|
success: false,
|
|
4326
4569
|
name,
|
|
4327
|
-
path:
|
|
4570
|
+
path: path7.join(targetDir, name),
|
|
4328
4571
|
message: `Unknown wrapper: ${name}`
|
|
4329
4572
|
});
|
|
4330
4573
|
continue;
|
|
@@ -4339,9 +4582,9 @@ async function installSpecificWrappers(names, targetDir, config) {
|
|
|
4339
4582
|
return results;
|
|
4340
4583
|
}
|
|
4341
4584
|
async function uninstallWrapper(name, targetDir) {
|
|
4342
|
-
const wrapperPath =
|
|
4585
|
+
const wrapperPath = path7.join(targetDir, name);
|
|
4343
4586
|
try {
|
|
4344
|
-
await
|
|
4587
|
+
await fs10.unlink(wrapperPath);
|
|
4345
4588
|
return {
|
|
4346
4589
|
success: true,
|
|
4347
4590
|
name,
|
|
@@ -4378,9 +4621,9 @@ async function verifyWrappers(targetDir = "/Users/agenshield_agent/bin") {
|
|
|
4378
4621
|
const installed = [];
|
|
4379
4622
|
const missing = [];
|
|
4380
4623
|
for (const name of Object.keys(WRAPPER_DEFINITIONS)) {
|
|
4381
|
-
const wrapperPath =
|
|
4624
|
+
const wrapperPath = path7.join(targetDir, name);
|
|
4382
4625
|
try {
|
|
4383
|
-
await
|
|
4626
|
+
await fs10.access(wrapperPath, fs10.constants.X_OK);
|
|
4384
4627
|
installed.push(name);
|
|
4385
4628
|
} catch {
|
|
4386
4629
|
missing.push(name);
|
|
@@ -4422,7 +4665,7 @@ async function installGuardedShell(userConfig, options) {
|
|
|
4422
4665
|
const shellPath = GUARDED_SHELL_PATH2;
|
|
4423
4666
|
try {
|
|
4424
4667
|
log(`Installing guarded shell launcher to ${shellPath}`);
|
|
4425
|
-
await execAsync4(`sudo mkdir -p "${
|
|
4668
|
+
await execAsync4(`sudo mkdir -p "${path7.dirname(shellPath)}"`);
|
|
4426
4669
|
await execAsync4(`sudo tee "${shellPath}" > /dev/null << 'GUARDEDEOF'
|
|
4427
4670
|
${GUARDED_SHELL_CONTENT2}
|
|
4428
4671
|
GUARDEDEOF`);
|
|
@@ -4475,7 +4718,7 @@ SHIELDEXECEOF`);
|
|
|
4475
4718
|
await execAsync4(`sudo chmod 755 "${SHIELD_EXEC_PATH2}"`);
|
|
4476
4719
|
await execAsync4(`sudo chown root:wheel "${SHIELD_EXEC_PATH2}"`);
|
|
4477
4720
|
for (const cmd of PROXIED_COMMANDS2) {
|
|
4478
|
-
const symlinkPath =
|
|
4721
|
+
const symlinkPath = path7.join(binDir, cmd);
|
|
4479
4722
|
try {
|
|
4480
4723
|
await execAsync4(`sudo rm -f "${symlinkPath}"`);
|
|
4481
4724
|
await execAsync4(`sudo ln -s "${SHIELD_EXEC_PATH2}" "${symlinkPath}"`);
|
|
@@ -4492,36 +4735,36 @@ if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
|
|
|
4492
4735
|
export NODE_OPTIONS="${wrapperConfig.interceptorFlag} ${wrapperConfig.interceptorPath} \${NODE_OPTIONS:-}"
|
|
4493
4736
|
export AGENSHIELD_SOCKET="${wrapperConfig.socketPath}"
|
|
4494
4737
|
export AGENSHIELD_HTTP_PORT="${wrapperConfig.httpPort}"
|
|
4495
|
-
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
4496
|
-
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
4497
4738
|
export AGENSHIELD_INTERCEPT_EXEC=true
|
|
4739
|
+
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
4740
|
+
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
4741
|
+
export AGENSHIELD_INTERCEPT_WS=true
|
|
4742
|
+
export AGENSHIELD_CONTEXT_TYPE=\${AGENSHIELD_CONTEXT_TYPE:-agent}
|
|
4743
|
+
|
|
4744
|
+
# Only use the NVM-installed node binary (copied to /opt/agenshield/bin/node-bin)
|
|
4498
4745
|
if [ -x "/opt/agenshield/bin/node-bin" ]; then
|
|
4499
4746
|
exec /opt/agenshield/bin/node-bin "$@"
|
|
4500
|
-
elif [ -x "/opt/homebrew/bin/node" ]; then
|
|
4501
|
-
exec /opt/homebrew/bin/node "$@"
|
|
4502
|
-
elif [ -x "/usr/local/bin/node" ]; then
|
|
4503
|
-
exec /usr/local/bin/node "$@"
|
|
4504
4747
|
else
|
|
4505
|
-
echo "node not found" >&2
|
|
4748
|
+
echo "node-bin not found at /opt/agenshield/bin/node-bin" >&2
|
|
4506
4749
|
exit 1
|
|
4507
4750
|
fi
|
|
4508
4751
|
`;
|
|
4509
4752
|
const pythonWrapper = WRAPPER_DEFINITIONS["python"]?.generate(wrapperConfig) || "";
|
|
4510
4753
|
const python3Wrapper = WRAPPER_DEFINITIONS["python3"]?.generate(wrapperConfig) || "";
|
|
4511
4754
|
const pip3Wrapper = WRAPPER_DEFINITIONS["pip"]?.generate(wrapperConfig) || "";
|
|
4512
|
-
const nodePath =
|
|
4755
|
+
const nodePath = path7.join(binDir, "node");
|
|
4513
4756
|
await execAsync4(`sudo tee "${nodePath}" > /dev/null << 'NODEEOF'
|
|
4514
4757
|
${nodeWrapper}
|
|
4515
4758
|
NODEEOF`);
|
|
4516
4759
|
await execAsync4(`sudo chmod 755 "${nodePath}"`);
|
|
4517
4760
|
installed.push("node");
|
|
4518
|
-
const pythonPath =
|
|
4761
|
+
const pythonPath = path7.join(binDir, "python");
|
|
4519
4762
|
await execAsync4(`sudo tee "${pythonPath}" > /dev/null << 'PYEOF'
|
|
4520
4763
|
${pythonWrapper}
|
|
4521
4764
|
PYEOF`);
|
|
4522
4765
|
await execAsync4(`sudo chmod 755 "${pythonPath}"`);
|
|
4523
4766
|
installed.push("python");
|
|
4524
|
-
const python3Path =
|
|
4767
|
+
const python3Path = path7.join(binDir, "python3");
|
|
4525
4768
|
await execAsync4(`sudo tee "${python3Path}" > /dev/null << 'PY3EOF'
|
|
4526
4769
|
${python3Wrapper}
|
|
4527
4770
|
PY3EOF`);
|
|
@@ -4560,12 +4803,12 @@ async function addDynamicWrapper(name, content, targetDir, useSudo = false, owne
|
|
|
4560
4803
|
return installWrapper(name, content, targetDir);
|
|
4561
4804
|
}
|
|
4562
4805
|
async function removeDynamicWrapper(name, targetDir, useSudo = false) {
|
|
4563
|
-
const wrapperPath =
|
|
4806
|
+
const wrapperPath = path7.join(targetDir, name);
|
|
4564
4807
|
try {
|
|
4565
4808
|
if (useSudo) {
|
|
4566
4809
|
await execAsync4(`sudo rm -f "${wrapperPath}"`);
|
|
4567
4810
|
} else {
|
|
4568
|
-
await
|
|
4811
|
+
await fs10.unlink(wrapperPath);
|
|
4569
4812
|
}
|
|
4570
4813
|
return {
|
|
4571
4814
|
success: true,
|
|
@@ -4597,7 +4840,7 @@ async function updateWrapper(name, targetDir, config, useSudo = false) {
|
|
|
4597
4840
|
return {
|
|
4598
4841
|
success: false,
|
|
4599
4842
|
name,
|
|
4600
|
-
path:
|
|
4843
|
+
path: path7.join(targetDir, name),
|
|
4601
4844
|
message: `Unknown wrapper: ${name}`
|
|
4602
4845
|
};
|
|
4603
4846
|
}
|
|
@@ -4612,7 +4855,7 @@ async function deployInterceptor(userConfig) {
|
|
|
4612
4855
|
const socketGroupName = userConfig?.groups?.socket?.name || "ash_socket";
|
|
4613
4856
|
try {
|
|
4614
4857
|
const srcPath = require2.resolve("@agenshield/interceptor/register");
|
|
4615
|
-
await
|
|
4858
|
+
await fs10.access(srcPath);
|
|
4616
4859
|
await execAsync4("sudo mkdir -p /opt/agenshield/lib/interceptor");
|
|
4617
4860
|
await execAsync4(`sudo cp "${srcPath}" "${targetPath}"`);
|
|
4618
4861
|
await execAsync4(`sudo chown root:${socketGroupName} "${targetPath}"`);
|
|
@@ -4638,11 +4881,11 @@ async function copyBrokerBinary(userConfig) {
|
|
|
4638
4881
|
const socketGroupName = userConfig?.groups?.socket?.name || "ash_socket";
|
|
4639
4882
|
try {
|
|
4640
4883
|
const brokerPkgPath = require2.resolve("@agenshield/broker/package.json");
|
|
4641
|
-
const brokerDir =
|
|
4642
|
-
const brokerPkg = JSON.parse(await
|
|
4884
|
+
const brokerDir = path7.dirname(brokerPkgPath);
|
|
4885
|
+
const brokerPkg = JSON.parse(await fs10.readFile(brokerPkgPath, "utf-8"));
|
|
4643
4886
|
const binEntry = typeof brokerPkg.bin === "string" ? brokerPkg.bin : brokerPkg.bin?.["agenshield-broker"] || "./dist/main.js";
|
|
4644
|
-
const srcPath =
|
|
4645
|
-
await
|
|
4887
|
+
const srcPath = path7.resolve(brokerDir, binEntry);
|
|
4888
|
+
await fs10.access(srcPath);
|
|
4646
4889
|
await execAsync4("sudo mkdir -p /opt/agenshield/bin");
|
|
4647
4890
|
await execAsync4(`sudo cp "${srcPath}" "${targetPath}"`);
|
|
4648
4891
|
await execAsync4(`sudo chmod 755 "${targetPath}"`);
|
|
@@ -4675,18 +4918,18 @@ async function copyShieldClient(userConfig) {
|
|
|
4675
4918
|
const socketGroupName = userConfig?.groups?.socket?.name || "ash_socket";
|
|
4676
4919
|
try {
|
|
4677
4920
|
const brokerPkgPath = require2.resolve("@agenshield/broker/package.json");
|
|
4678
|
-
const brokerDir =
|
|
4679
|
-
const brokerPkg = JSON.parse(await
|
|
4921
|
+
const brokerDir = path7.dirname(brokerPkgPath);
|
|
4922
|
+
const brokerPkg = JSON.parse(await fs10.readFile(brokerPkgPath, "utf-8"));
|
|
4680
4923
|
const clientEntry = typeof brokerPkg.bin === "object" ? brokerPkg.bin["shield-client"] : null;
|
|
4681
|
-
const srcPath =
|
|
4682
|
-
await
|
|
4683
|
-
let content = await
|
|
4924
|
+
const srcPath = path7.resolve(brokerDir, clientEntry || "./dist/client/shield-client.js");
|
|
4925
|
+
await fs10.access(srcPath);
|
|
4926
|
+
let content = await fs10.readFile(srcPath, "utf-8");
|
|
4684
4927
|
content = content.replace(
|
|
4685
4928
|
/^#!\/usr\/bin\/env node/,
|
|
4686
4929
|
"#!/opt/agenshield/bin/node-bin"
|
|
4687
4930
|
);
|
|
4688
4931
|
const tmpPath = "/tmp/shield-client-install";
|
|
4689
|
-
await
|
|
4932
|
+
await fs10.writeFile(tmpPath, content, { mode: 493 });
|
|
4690
4933
|
await execAsync4("sudo mkdir -p /opt/agenshield/bin");
|
|
4691
4934
|
await execAsync4(`sudo mv "${tmpPath}" "${targetPath}"`);
|
|
4692
4935
|
await execAsync4(`sudo chmod 755 "${targetPath}"`);
|
|
@@ -4723,12 +4966,12 @@ async function copyNodeDylibs(srcBinaryPath, socketGroupName) {
|
|
|
4723
4966
|
const dylibName = match[3];
|
|
4724
4967
|
let resolvedPath;
|
|
4725
4968
|
if (prefix === "@loader_path") {
|
|
4726
|
-
resolvedPath =
|
|
4969
|
+
resolvedPath = path7.resolve(path7.dirname(srcBinaryPath) + relPath + dylibName);
|
|
4727
4970
|
} else {
|
|
4728
|
-
resolvedPath =
|
|
4971
|
+
resolvedPath = path7.resolve(path7.dirname(srcBinaryPath), "..", "lib", dylibName);
|
|
4729
4972
|
}
|
|
4730
4973
|
try {
|
|
4731
|
-
await
|
|
4974
|
+
await fs10.access(resolvedPath);
|
|
4732
4975
|
} catch {
|
|
4733
4976
|
errors.push(`dylib not found on disk: ${resolvedPath}`);
|
|
4734
4977
|
continue;
|
|
@@ -4752,7 +4995,7 @@ async function copyNodeBinary(userConfig, sourcePath) {
|
|
|
4752
4995
|
const socketGroupName = userConfig?.groups?.socket?.name || "ash_socket";
|
|
4753
4996
|
try {
|
|
4754
4997
|
const srcPath = sourcePath || process.execPath;
|
|
4755
|
-
await
|
|
4998
|
+
await fs10.access(srcPath);
|
|
4756
4999
|
await execAsync4(`sudo cp "${srcPath}" "${targetPath}"`);
|
|
4757
5000
|
await execAsync4(`sudo chown root:${socketGroupName} "${targetPath}"`);
|
|
4758
5001
|
await execAsync4(`sudo chmod 755 "${targetPath}"`);
|
|
@@ -4776,11 +5019,11 @@ async function copyNodeBinary(userConfig, sourcePath) {
|
|
|
4776
5019
|
}
|
|
4777
5020
|
var NVM_INSTALL_URL = "https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh";
|
|
4778
5021
|
async function installAgentNvm(options) {
|
|
4779
|
-
const { agentHome, agentUsername, socketGroupName, verbose } = options;
|
|
5022
|
+
const { agentHome, agentUsername, socketGroupName, verbose, onLog } = options;
|
|
4780
5023
|
const nodeVersion = options.nodeVersion || "24";
|
|
4781
5024
|
const nvmDir = `${agentHome}/.nvm`;
|
|
4782
|
-
const log = (msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
4783
|
-
`);
|
|
5025
|
+
const log = onLog || ((msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
5026
|
+
`));
|
|
4784
5027
|
const empty = {
|
|
4785
5028
|
success: false,
|
|
4786
5029
|
nvmDir,
|
|
@@ -4797,9 +5040,13 @@ async function installAgentNvm(options) {
|
|
|
4797
5040
|
const installCmd = [
|
|
4798
5041
|
`export HOME="${agentHome}"`,
|
|
4799
5042
|
`export NVM_DIR="${nvmDir}"`,
|
|
4800
|
-
`/usr/bin/curl -
|
|
5043
|
+
`/usr/bin/curl -sSo- "${NVM_INSTALL_URL}" | PROFILE=/dev/null /bin/bash`
|
|
4801
5044
|
].join(" && ");
|
|
4802
|
-
await
|
|
5045
|
+
await execWithProgress(
|
|
5046
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${installCmd}'`,
|
|
5047
|
+
log,
|
|
5048
|
+
{ timeout: 6e4, cwd: "/" }
|
|
5049
|
+
);
|
|
4803
5050
|
log(`Installing Node.js v${nodeVersion} via NVM`);
|
|
4804
5051
|
const nvmInstallCmd = [
|
|
4805
5052
|
`export HOME="${agentHome}"`,
|
|
@@ -4807,7 +5054,11 @@ async function installAgentNvm(options) {
|
|
|
4807
5054
|
`source "${nvmDir}/nvm.sh"`,
|
|
4808
5055
|
`nvm install ${nodeVersion}`
|
|
4809
5056
|
].join(" && ");
|
|
4810
|
-
await
|
|
5057
|
+
await execWithProgress(
|
|
5058
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${nvmInstallCmd}'`,
|
|
5059
|
+
log,
|
|
5060
|
+
{ timeout: 12e4, cwd: "/" }
|
|
5061
|
+
);
|
|
4811
5062
|
log("Resolving installed node binary path");
|
|
4812
5063
|
const whichCmd = [
|
|
4813
5064
|
`export HOME="${agentHome}"`,
|
|
@@ -4815,14 +5066,15 @@ async function installAgentNvm(options) {
|
|
|
4815
5066
|
`source "${nvmDir}/nvm.sh"`,
|
|
4816
5067
|
`nvm which ${nodeVersion}`
|
|
4817
5068
|
].join(" && ");
|
|
4818
|
-
const { stdout } = await execAsync4(`sudo -u ${agentUsername} /bin/bash -c '${whichCmd}'
|
|
5069
|
+
const { stdout } = await execAsync4(`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${whichCmd}'`, { cwd: "/" });
|
|
4819
5070
|
const nodeBinaryPath = stdout.trim();
|
|
4820
5071
|
if (!nodeBinaryPath) {
|
|
4821
5072
|
return { ...empty, message: "NVM installed but could not resolve node binary path" };
|
|
4822
5073
|
}
|
|
4823
5074
|
log(`Verifying node binary at ${nodeBinaryPath}`);
|
|
4824
5075
|
const { stdout: versionOut } = await execAsync4(
|
|
4825
|
-
`sudo -u ${agentUsername} /bin/bash -c '"${nodeBinaryPath}" --version'
|
|
5076
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '"${nodeBinaryPath}" --version'`,
|
|
5077
|
+
{ cwd: "/" }
|
|
4826
5078
|
);
|
|
4827
5079
|
const actualVersion = versionOut.trim();
|
|
4828
5080
|
log(`Node.js ${actualVersion} installed successfully`);
|
|
@@ -4841,6 +5093,115 @@ async function installAgentNvm(options) {
|
|
|
4841
5093
|
};
|
|
4842
5094
|
}
|
|
4843
5095
|
}
|
|
5096
|
+
function isNoiseLine(line) {
|
|
5097
|
+
if (/^\s*%\s+Total/.test(line)) return true;
|
|
5098
|
+
if (/^\s*Dload\s+Upload/.test(line)) return true;
|
|
5099
|
+
if (/^[\d\s.kMG:\-/|]+$/.test(line)) return true;
|
|
5100
|
+
if (/^=>?\s*$/.test(line)) return true;
|
|
5101
|
+
return false;
|
|
5102
|
+
}
|
|
5103
|
+
async function execWithProgress(command, log, opts) {
|
|
5104
|
+
return new Promise((resolve6, reject) => {
|
|
5105
|
+
const child = nodeSpawn("/bin/bash", ["-c", command], {
|
|
5106
|
+
cwd: opts?.cwd || "/",
|
|
5107
|
+
env: { ...process.env, ...opts?.env },
|
|
5108
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5109
|
+
});
|
|
5110
|
+
let stdout = "";
|
|
5111
|
+
let stderr = "";
|
|
5112
|
+
let timer;
|
|
5113
|
+
if (opts?.timeout) {
|
|
5114
|
+
timer = setTimeout(() => {
|
|
5115
|
+
child.kill("SIGTERM");
|
|
5116
|
+
reject(new Error(`Command timed out after ${opts.timeout}ms`));
|
|
5117
|
+
}, opts.timeout);
|
|
5118
|
+
}
|
|
5119
|
+
child.stdout?.on("data", (data) => {
|
|
5120
|
+
const text = data.toString();
|
|
5121
|
+
stdout += text;
|
|
5122
|
+
for (const line of text.split("\n")) {
|
|
5123
|
+
const trimmed = line.trim();
|
|
5124
|
+
if (trimmed && !isNoiseLine(trimmed)) log(trimmed);
|
|
5125
|
+
}
|
|
5126
|
+
});
|
|
5127
|
+
child.stderr?.on("data", (data) => {
|
|
5128
|
+
const text = data.toString();
|
|
5129
|
+
stderr += text;
|
|
5130
|
+
for (const line of text.split("\n")) {
|
|
5131
|
+
const trimmed = line.trim();
|
|
5132
|
+
if (trimmed && !isNoiseLine(trimmed)) log(trimmed);
|
|
5133
|
+
}
|
|
5134
|
+
});
|
|
5135
|
+
child.on("close", (code) => {
|
|
5136
|
+
if (timer) clearTimeout(timer);
|
|
5137
|
+
if (code === 0) {
|
|
5138
|
+
resolve6({ stdout, stderr });
|
|
5139
|
+
} else {
|
|
5140
|
+
const err = new Error(`Command failed with exit code ${code}: ${stderr.slice(0, 500)}`);
|
|
5141
|
+
err.stdout = stdout;
|
|
5142
|
+
err.stderr = stderr;
|
|
5143
|
+
reject(err);
|
|
5144
|
+
}
|
|
5145
|
+
});
|
|
5146
|
+
child.on("error", (err) => {
|
|
5147
|
+
if (timer) clearTimeout(timer);
|
|
5148
|
+
reject(err);
|
|
5149
|
+
});
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
async function patchNvmNode(options) {
|
|
5153
|
+
const { nodeBinaryPath, agentUsername, socketGroupName, interceptorPath, socketPath, httpPort, verbose, onLog } = options;
|
|
5154
|
+
const log = onLog || ((msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
5155
|
+
`));
|
|
5156
|
+
try {
|
|
5157
|
+
const nodeBinTarget = "/opt/agenshield/bin/node-bin";
|
|
5158
|
+
try {
|
|
5159
|
+
await fs10.access(nodeBinTarget);
|
|
5160
|
+
} catch {
|
|
5161
|
+
return {
|
|
5162
|
+
success: false,
|
|
5163
|
+
name: "patch-nvm-node",
|
|
5164
|
+
path: nodeBinaryPath,
|
|
5165
|
+
message: `node-bin not found at ${nodeBinTarget} \u2014 copyNodeBinary must run first`
|
|
5166
|
+
};
|
|
5167
|
+
}
|
|
5168
|
+
log(`Removing NVM node binary at ${nodeBinaryPath}`);
|
|
5169
|
+
await execAsync4(`sudo rm -f "${nodeBinaryPath}"`);
|
|
5170
|
+
const wrapperContent = `#!/bin/bash
|
|
5171
|
+
# AgenShield-patched node \u2014 loads interceptor before executing
|
|
5172
|
+
export NODE_OPTIONS="--require ${interceptorPath} \${NODE_OPTIONS:-}"
|
|
5173
|
+
export AGENSHIELD_SOCKET="${socketPath}"
|
|
5174
|
+
export AGENSHIELD_HTTP_PORT="${httpPort}"
|
|
5175
|
+
export AGENSHIELD_INTERCEPT_EXEC=true
|
|
5176
|
+
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
5177
|
+
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
5178
|
+
export AGENSHIELD_INTERCEPT_WS=true
|
|
5179
|
+
export AGENSHIELD_CONTEXT_TYPE=\${AGENSHIELD_CONTEXT_TYPE:-agent}
|
|
5180
|
+
exec ${nodeBinTarget} "$@"
|
|
5181
|
+
`;
|
|
5182
|
+
log(`Writing interceptor wrapper to ${nodeBinaryPath}`);
|
|
5183
|
+
await execAsync4(`sudo tee "${nodeBinaryPath}" > /dev/null << 'PATCHEOF'
|
|
5184
|
+
${wrapperContent}
|
|
5185
|
+
PATCHEOF`);
|
|
5186
|
+
await execAsync4(`sudo chown root:${socketGroupName} "${nodeBinaryPath}"`);
|
|
5187
|
+
await execAsync4(`sudo chmod 755 "${nodeBinaryPath}"`);
|
|
5188
|
+
log(`NVM node patched in-place at ${nodeBinaryPath}`);
|
|
5189
|
+
return {
|
|
5190
|
+
success: true,
|
|
5191
|
+
name: "patch-nvm-node",
|
|
5192
|
+
path: nodeBinaryPath,
|
|
5193
|
+
message: `Patched NVM node at ${nodeBinaryPath} with interceptor wrapper`
|
|
5194
|
+
};
|
|
5195
|
+
} catch (error) {
|
|
5196
|
+
return {
|
|
5197
|
+
success: false,
|
|
5198
|
+
name: "patch-nvm-node",
|
|
5199
|
+
path: nodeBinaryPath,
|
|
5200
|
+
message: `Failed to patch NVM node: ${error.message}`,
|
|
5201
|
+
error
|
|
5202
|
+
};
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
4844
5205
|
var BASIC_SYSTEM_COMMANDS = [
|
|
4845
5206
|
"ls",
|
|
4846
5207
|
"cat",
|
|
@@ -4897,8 +5258,8 @@ async function installBasicCommands(binDir, options) {
|
|
|
4897
5258
|
let found = false;
|
|
4898
5259
|
for (const loc of locations) {
|
|
4899
5260
|
try {
|
|
4900
|
-
await
|
|
4901
|
-
const symlinkPath =
|
|
5261
|
+
await fs10.access(loc, fs10.constants.X_OK);
|
|
5262
|
+
const symlinkPath = path7.join(binDir, cmd);
|
|
4902
5263
|
log(`Creating symlink: ${cmd} -> ${loc}`);
|
|
4903
5264
|
await execAsync4(`sudo ln -sf "${loc}" "${symlinkPath}"`);
|
|
4904
5265
|
installed.push(cmd);
|
|
@@ -4919,30 +5280,36 @@ async function installPresetBinaries(options) {
|
|
|
4919
5280
|
const errors = [];
|
|
4920
5281
|
const installedWrappers = [];
|
|
4921
5282
|
let seatbeltInstalled = false;
|
|
5283
|
+
let resolvedNvmResult;
|
|
4922
5284
|
if (requiredBins.includes("node")) {
|
|
4923
5285
|
const agentHome = userConfig.agentUser.home;
|
|
4924
5286
|
const agentUsername = userConfig.agentUser.username;
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
agentUsername,
|
|
4929
|
-
socketGroupName,
|
|
4930
|
-
nodeVersion: options.nodeVersion,
|
|
4931
|
-
verbose
|
|
4932
|
-
});
|
|
4933
|
-
if (nvmResult.success) {
|
|
4934
|
-
log(`NVM installed Node.js ${nvmResult.nodeVersion} at ${nvmResult.nodeBinaryPath}`);
|
|
4935
|
-
log("Copying NVM node binary to /opt/agenshield/bin/node-bin");
|
|
4936
|
-
const nodeResult = await copyNodeBinary(userConfig, nvmResult.nodeBinaryPath);
|
|
4937
|
-
if (!nodeResult.success) {
|
|
4938
|
-
errors.push(`Node binary (from NVM): ${nodeResult.message}`);
|
|
4939
|
-
}
|
|
5287
|
+
if (options.nvmResult?.success) {
|
|
5288
|
+
log(`Using pre-installed NVM Node.js ${options.nvmResult.nodeVersion} (node-bin already copied)`);
|
|
5289
|
+
resolvedNvmResult = options.nvmResult;
|
|
4940
5290
|
} else {
|
|
4941
|
-
log(
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
5291
|
+
log("Installing NVM + Node.js for agent user");
|
|
5292
|
+
resolvedNvmResult = await installAgentNvm({
|
|
5293
|
+
agentHome,
|
|
5294
|
+
agentUsername,
|
|
5295
|
+
socketGroupName,
|
|
5296
|
+
nodeVersion: options.nodeVersion,
|
|
5297
|
+
verbose
|
|
5298
|
+
});
|
|
5299
|
+
if (resolvedNvmResult.success) {
|
|
5300
|
+
log(`NVM Node.js ${resolvedNvmResult.nodeVersion} at ${resolvedNvmResult.nodeBinaryPath}`);
|
|
5301
|
+
log("Copying NVM node binary to /opt/agenshield/bin/node-bin");
|
|
5302
|
+
const nodeResult = await copyNodeBinary(userConfig, resolvedNvmResult.nodeBinaryPath);
|
|
5303
|
+
if (!nodeResult.success) {
|
|
5304
|
+
errors.push(`Node binary (from NVM): ${nodeResult.message}`);
|
|
5305
|
+
}
|
|
5306
|
+
} else {
|
|
5307
|
+
log(`NVM install failed: ${resolvedNvmResult.message}. Falling back to host node binary.`);
|
|
5308
|
+
log("Copying node binary to /opt/agenshield/bin/node-bin");
|
|
5309
|
+
const nodeResult = await copyNodeBinary(userConfig);
|
|
5310
|
+
if (!nodeResult.success) {
|
|
5311
|
+
errors.push(`Node binary: ${nodeResult.message}`);
|
|
5312
|
+
}
|
|
4946
5313
|
}
|
|
4947
5314
|
}
|
|
4948
5315
|
}
|
|
@@ -4957,6 +5324,9 @@ async function installPresetBinaries(options) {
|
|
|
4957
5324
|
}
|
|
4958
5325
|
}
|
|
4959
5326
|
const wrapperConfig = getDefaultWrapperConfig(userConfig);
|
|
5327
|
+
if (resolvedNvmResult?.success && resolvedNvmResult.nodeBinaryPath) {
|
|
5328
|
+
wrapperConfig.nvmNodeDir = path7.dirname(path7.dirname(resolvedNvmResult.nodeBinaryPath));
|
|
5329
|
+
}
|
|
4960
5330
|
const validNames = requiredBins.filter((name) => WRAPPER_DEFINITIONS[name]);
|
|
4961
5331
|
for (const name of validNames) {
|
|
4962
5332
|
log(`Installing wrapper: ${name} -> ${binDir}/${name}`);
|
|
@@ -5013,7 +5383,7 @@ async function installPresetBinaries(options) {
|
|
|
5013
5383
|
init_seatbelt();
|
|
5014
5384
|
|
|
5015
5385
|
// libs/shield-sandbox/src/launchdaemon.ts
|
|
5016
|
-
import * as
|
|
5386
|
+
import * as fs11 from "node:fs/promises";
|
|
5017
5387
|
import { exec as exec5 } from "node:child_process";
|
|
5018
5388
|
import { promisify as promisify5 } from "node:util";
|
|
5019
5389
|
var execAsync5 = promisify5(exec5);
|
|
@@ -5101,10 +5471,10 @@ function generateBrokerPlistLegacy(options) {
|
|
|
5101
5471
|
</array>
|
|
5102
5472
|
|
|
5103
5473
|
<key>UserName</key>
|
|
5104
|
-
<string>
|
|
5474
|
+
<string>ash_default_broker</string>
|
|
5105
5475
|
|
|
5106
5476
|
<key>GroupName</key>
|
|
5107
|
-
<string>
|
|
5477
|
+
<string>ash_default</string>
|
|
5108
5478
|
|
|
5109
5479
|
<key>RunAtLoad</key>
|
|
5110
5480
|
<true/>
|
|
@@ -5237,7 +5607,7 @@ async function getDaemonStatus() {
|
|
|
5237
5607
|
running: false
|
|
5238
5608
|
};
|
|
5239
5609
|
try {
|
|
5240
|
-
await
|
|
5610
|
+
await fs11.access(PLIST_PATH);
|
|
5241
5611
|
status.installed = true;
|
|
5242
5612
|
} catch {
|
|
5243
5613
|
return status;
|
|
@@ -5291,7 +5661,7 @@ async function fixSocketPermissions(config) {
|
|
|
5291
5661
|
let socketFound = false;
|
|
5292
5662
|
for (let attempt = 0; attempt < 20; attempt++) {
|
|
5293
5663
|
try {
|
|
5294
|
-
await
|
|
5664
|
+
await fs11.access(socketPath);
|
|
5295
5665
|
socketFound = true;
|
|
5296
5666
|
break;
|
|
5297
5667
|
} catch {
|
|
@@ -5320,15 +5690,19 @@ async function fixSocketPermissions(config) {
|
|
|
5320
5690
|
}
|
|
5321
5691
|
|
|
5322
5692
|
// libs/shield-sandbox/src/presets/openclaw.ts
|
|
5693
|
+
import * as path8 from "node:path";
|
|
5323
5694
|
var openclawPreset = {
|
|
5324
5695
|
id: "openclaw",
|
|
5325
5696
|
name: "OpenClaw",
|
|
5326
5697
|
description: "AI coding agent (auto-detected via npm or git)",
|
|
5327
|
-
requiredBins: ["node", "npm", "npx", "git", "curl", "shieldctl"],
|
|
5698
|
+
requiredBins: ["node", "npm", "npx", "git", "curl", "bash", "shieldctl"],
|
|
5328
5699
|
optionalBins: ["wget", "ssh", "scp", "python3", "pip", "brew"],
|
|
5329
5700
|
async detect() {
|
|
5330
5701
|
const result = detectOpenClaw();
|
|
5331
5702
|
if (!result.installation.found) {
|
|
5703
|
+
if (result.installation.configPath) {
|
|
5704
|
+
return { found: false, configPath: result.installation.configPath };
|
|
5705
|
+
}
|
|
5332
5706
|
return null;
|
|
5333
5707
|
}
|
|
5334
5708
|
return {
|
|
@@ -5340,6 +5714,10 @@ var openclawPreset = {
|
|
|
5340
5714
|
method: result.installation.method === "unknown" ? void 0 : result.installation.method
|
|
5341
5715
|
};
|
|
5342
5716
|
},
|
|
5717
|
+
async scan(detection) {
|
|
5718
|
+
const configJsonPath = detection.configPath ? path8.join(detection.configPath, "openclaw.json") : void 0;
|
|
5719
|
+
return scanHost({ configPath: configJsonPath });
|
|
5720
|
+
},
|
|
5343
5721
|
async migrate(context) {
|
|
5344
5722
|
if (!context.detection?.packagePath) {
|
|
5345
5723
|
return { success: false, error: "OpenClaw package path not detected" };
|
|
@@ -5348,7 +5726,8 @@ var openclawPreset = {
|
|
|
5348
5726
|
method: context.detection.method || "npm",
|
|
5349
5727
|
packagePath: context.detection.packagePath,
|
|
5350
5728
|
binaryPath: context.detection.binaryPath,
|
|
5351
|
-
configPath: context.detection.configPath
|
|
5729
|
+
configPath: context.detection.configPath,
|
|
5730
|
+
selection: context.selection
|
|
5352
5731
|
};
|
|
5353
5732
|
const user = {
|
|
5354
5733
|
username: context.agentUser.username,
|
|
@@ -5377,9 +5756,9 @@ var openclawPreset = {
|
|
|
5377
5756
|
};
|
|
5378
5757
|
|
|
5379
5758
|
// libs/shield-sandbox/src/presets/dev-harness.ts
|
|
5380
|
-
import * as
|
|
5381
|
-
import * as
|
|
5382
|
-
import * as
|
|
5759
|
+
import * as fs12 from "node:fs";
|
|
5760
|
+
import * as os5 from "node:os";
|
|
5761
|
+
import * as path9 from "node:path";
|
|
5383
5762
|
import { execSync as execSync7 } from "node:child_process";
|
|
5384
5763
|
function sudoExec5(cmd) {
|
|
5385
5764
|
try {
|
|
@@ -5396,10 +5775,10 @@ function sudoCopyDir2(src, dest) {
|
|
|
5396
5775
|
function getRealUserHome() {
|
|
5397
5776
|
const sudoUser = process.env["SUDO_USER"];
|
|
5398
5777
|
if (sudoUser) {
|
|
5399
|
-
const userHome =
|
|
5400
|
-
if (
|
|
5778
|
+
const userHome = path9.join("/Users", sudoUser);
|
|
5779
|
+
if (fs12.existsSync(userHome)) return userHome;
|
|
5401
5780
|
}
|
|
5402
|
-
return
|
|
5781
|
+
return os5.homedir();
|
|
5403
5782
|
}
|
|
5404
5783
|
var devHarnessPreset = {
|
|
5405
5784
|
id: "dev-harness",
|
|
@@ -5408,14 +5787,14 @@ var devHarnessPreset = {
|
|
|
5408
5787
|
requiredBins: ["node"],
|
|
5409
5788
|
optionalBins: ["npm", "npx"],
|
|
5410
5789
|
async detect() {
|
|
5411
|
-
const dummyOpenclawPath =
|
|
5412
|
-
if (
|
|
5413
|
-
const testHarnessDir2 =
|
|
5414
|
-
const pkgPath2 =
|
|
5415
|
-
let version = "
|
|
5416
|
-
if (
|
|
5790
|
+
const dummyOpenclawPath = path9.join(process.cwd(), "tools/test-harness/bin/dummy-openclaw.js");
|
|
5791
|
+
if (fs12.existsSync(dummyOpenclawPath)) {
|
|
5792
|
+
const testHarnessDir2 = path9.join(process.cwd(), "tools/test-harness");
|
|
5793
|
+
const pkgPath2 = path9.join(testHarnessDir2, "package.json");
|
|
5794
|
+
let version = "2026.2.1";
|
|
5795
|
+
if (fs12.existsSync(pkgPath2)) {
|
|
5417
5796
|
try {
|
|
5418
|
-
const pkg = JSON.parse(
|
|
5797
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath2, "utf-8"));
|
|
5419
5798
|
if (pkg.name === "dummy-openclaw") {
|
|
5420
5799
|
version = pkg.version || version;
|
|
5421
5800
|
}
|
|
@@ -5423,29 +5802,29 @@ var devHarnessPreset = {
|
|
|
5423
5802
|
}
|
|
5424
5803
|
}
|
|
5425
5804
|
const HOME2 = getRealUserHome();
|
|
5426
|
-
const configPath =
|
|
5805
|
+
const configPath = fs12.existsSync(path9.join(HOME2, ".openclaw-dev")) ? path9.join(HOME2, ".openclaw-dev") : fs12.existsSync(path9.join(HOME2, ".openclaw")) ? path9.join(HOME2, ".openclaw") : void 0;
|
|
5427
5806
|
return {
|
|
5428
5807
|
found: true,
|
|
5429
5808
|
version,
|
|
5430
5809
|
packagePath: testHarnessDir2,
|
|
5431
|
-
binaryPath:
|
|
5810
|
+
binaryPath: path9.resolve(dummyOpenclawPath),
|
|
5432
5811
|
configPath,
|
|
5433
5812
|
method: "custom"
|
|
5434
5813
|
};
|
|
5435
5814
|
}
|
|
5436
|
-
const testHarnessDir =
|
|
5437
|
-
const pkgPath =
|
|
5438
|
-
if (
|
|
5815
|
+
const testHarnessDir = path9.join(process.cwd(), "tools/test-harness");
|
|
5816
|
+
const pkgPath = path9.join(testHarnessDir, "package.json");
|
|
5817
|
+
if (fs12.existsSync(pkgPath)) {
|
|
5439
5818
|
try {
|
|
5440
|
-
const pkg = JSON.parse(
|
|
5819
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
5441
5820
|
if (pkg.name === "dummy-openclaw") {
|
|
5442
5821
|
const HOME2 = getRealUserHome();
|
|
5443
|
-
const configPath =
|
|
5822
|
+
const configPath = fs12.existsSync(path9.join(HOME2, ".openclaw-dev")) ? path9.join(HOME2, ".openclaw-dev") : fs12.existsSync(path9.join(HOME2, ".openclaw")) ? path9.join(HOME2, ".openclaw") : void 0;
|
|
5444
5823
|
return {
|
|
5445
5824
|
found: true,
|
|
5446
|
-
version: pkg.version || "
|
|
5825
|
+
version: pkg.version || "2026.2.1",
|
|
5447
5826
|
packagePath: testHarnessDir,
|
|
5448
|
-
binaryPath:
|
|
5827
|
+
binaryPath: path9.join(testHarnessDir, "bin/dummy-openclaw.js"),
|
|
5449
5828
|
configPath,
|
|
5450
5829
|
method: "custom"
|
|
5451
5830
|
};
|
|
@@ -5462,6 +5841,9 @@ var devHarnessPreset = {
|
|
|
5462
5841
|
try {
|
|
5463
5842
|
const packagePath = context.detection.packagePath;
|
|
5464
5843
|
const packageDir = context.directories.packageDir;
|
|
5844
|
+
if (!packageDir) {
|
|
5845
|
+
return { success: false, error: "packageDir is not configured" };
|
|
5846
|
+
}
|
|
5465
5847
|
const copyResult = sudoCopyDir2(packagePath, packageDir);
|
|
5466
5848
|
if (!copyResult.success) {
|
|
5467
5849
|
return { success: false, error: `Failed to copy package: ${copyResult.error}` };
|
|
@@ -5476,8 +5858,8 @@ var devHarnessPreset = {
|
|
|
5476
5858
|
if (!ownResult.success) {
|
|
5477
5859
|
return { success: false, error: `Failed to set ownership: ${ownResult.error}` };
|
|
5478
5860
|
}
|
|
5479
|
-
const wrapperPath =
|
|
5480
|
-
const entryPath =
|
|
5861
|
+
const wrapperPath = path9.join(context.directories.binDir, "openclaw");
|
|
5862
|
+
const entryPath = path9.join(packageDir, "bin", "dummy-openclaw.js");
|
|
5481
5863
|
const wrapperContent = `#!/bin/bash
|
|
5482
5864
|
set -euo pipefail
|
|
5483
5865
|
cd ~ 2>/dev/null || cd /
|
|
@@ -5492,8 +5874,8 @@ WRAPEOF`);
|
|
|
5492
5874
|
return { success: false, error: `Failed to create wrapper: ${writeResult.error}` };
|
|
5493
5875
|
}
|
|
5494
5876
|
sudoExec5(`chmod 755 "${wrapperPath}"`);
|
|
5495
|
-
if (context.detection?.configPath &&
|
|
5496
|
-
const agentConfigDir =
|
|
5877
|
+
if (context.detection?.configPath && fs12.existsSync(context.detection.configPath)) {
|
|
5878
|
+
const agentConfigDir = path9.join(context.agentUser.home, ".openclaw");
|
|
5497
5879
|
const configResult = sudoExec5(
|
|
5498
5880
|
`cp -R "${context.detection.configPath}/." "${agentConfigDir}/"`
|
|
5499
5881
|
);
|
|
@@ -5512,7 +5894,7 @@ WRAPEOF`);
|
|
|
5512
5894
|
newPaths: {
|
|
5513
5895
|
packagePath: packageDir,
|
|
5514
5896
|
binaryPath: wrapperPath,
|
|
5515
|
-
configPath:
|
|
5897
|
+
configPath: path9.join(context.agentUser.home, ".openclaw")
|
|
5516
5898
|
}
|
|
5517
5899
|
};
|
|
5518
5900
|
} catch (err) {
|
|
@@ -5523,13 +5905,13 @@ WRAPEOF`);
|
|
|
5523
5905
|
}
|
|
5524
5906
|
},
|
|
5525
5907
|
getEntryCommand(context) {
|
|
5526
|
-
return
|
|
5908
|
+
return path9.join(context.directories.binDir, "openclaw");
|
|
5527
5909
|
}
|
|
5528
5910
|
};
|
|
5529
5911
|
|
|
5530
5912
|
// libs/shield-sandbox/src/presets/custom.ts
|
|
5531
|
-
import * as
|
|
5532
|
-
import * as
|
|
5913
|
+
import * as fs13 from "node:fs";
|
|
5914
|
+
import * as path10 from "node:path";
|
|
5533
5915
|
import { execSync as execSync8 } from "node:child_process";
|
|
5534
5916
|
function sudoExec6(cmd) {
|
|
5535
5917
|
try {
|
|
@@ -5556,12 +5938,15 @@ var customPreset = {
|
|
|
5556
5938
|
if (!context.entryPoint) {
|
|
5557
5939
|
return { success: false, error: "Entry point required for custom preset (--entry-point)" };
|
|
5558
5940
|
}
|
|
5559
|
-
const entryPath =
|
|
5560
|
-
if (!
|
|
5941
|
+
const entryPath = path10.resolve(context.entryPoint);
|
|
5942
|
+
if (!fs13.existsSync(entryPath)) {
|
|
5561
5943
|
return { success: false, error: `Entry point not found: ${entryPath}` };
|
|
5562
5944
|
}
|
|
5563
|
-
const entryDir =
|
|
5564
|
-
const entryFilename =
|
|
5945
|
+
const entryDir = path10.dirname(entryPath);
|
|
5946
|
+
const entryFilename = path10.basename(entryPath);
|
|
5947
|
+
if (!context.directories.packageDir) {
|
|
5948
|
+
return { success: false, error: "packageDir is not configured" };
|
|
5949
|
+
}
|
|
5565
5950
|
const result = sudoCopyDir3(entryDir, context.directories.packageDir);
|
|
5566
5951
|
if (!result.success) {
|
|
5567
5952
|
return { success: false, error: `Failed to copy package: ${result.error}` };
|
|
@@ -5572,8 +5957,8 @@ var customPreset = {
|
|
|
5572
5957
|
if (!ownerResult.success) {
|
|
5573
5958
|
return { success: false, error: `Failed to set ownership: ${ownerResult.error}` };
|
|
5574
5959
|
}
|
|
5575
|
-
const wrapperPath =
|
|
5576
|
-
const newEntryPath =
|
|
5960
|
+
const wrapperPath = path10.join(context.directories.binDir, "agent");
|
|
5961
|
+
const newEntryPath = path10.join(context.directories.packageDir, entryFilename);
|
|
5577
5962
|
const wrapperContent = `#!/bin/bash
|
|
5578
5963
|
set -euo pipefail
|
|
5579
5964
|
cd ~ 2>/dev/null || cd /
|
|
@@ -5583,7 +5968,7 @@ exec "\${AGENT_BIN}/node" "${newEntryPath}" "$@"
|
|
|
5583
5968
|
`;
|
|
5584
5969
|
const tempPath = "/tmp/agent-wrapper";
|
|
5585
5970
|
try {
|
|
5586
|
-
|
|
5971
|
+
fs13.writeFileSync(tempPath, wrapperContent, { mode: 493 });
|
|
5587
5972
|
} catch (err) {
|
|
5588
5973
|
return { success: false, error: `Failed to write wrapper: ${err}` };
|
|
5589
5974
|
}
|
|
@@ -5630,7 +6015,7 @@ function listAutoDetectablePresets() {
|
|
|
5630
6015
|
async function autoDetectPreset() {
|
|
5631
6016
|
for (const preset of listAutoDetectablePresets()) {
|
|
5632
6017
|
const detection = await preset.detect();
|
|
5633
|
-
if (detection?.found) {
|
|
6018
|
+
if (detection?.found || detection?.configPath) {
|
|
5634
6019
|
return { preset, detection };
|
|
5635
6020
|
}
|
|
5636
6021
|
}
|
|
@@ -5646,8 +6031,8 @@ function formatPresetList() {
|
|
|
5646
6031
|
}
|
|
5647
6032
|
|
|
5648
6033
|
// libs/shield-sandbox/src/discovery/binary-scanner.ts
|
|
5649
|
-
import * as
|
|
5650
|
-
import * as
|
|
6034
|
+
import * as fs14 from "node:fs";
|
|
6035
|
+
import * as path11 from "node:path";
|
|
5651
6036
|
import { execSync as execSync9 } from "node:child_process";
|
|
5652
6037
|
init_shield_exec();
|
|
5653
6038
|
var SYSTEM_BIN_DIRS = [
|
|
@@ -5668,8 +6053,8 @@ var allowedCommandsCache = null;
|
|
|
5668
6053
|
function loadAllowedCommands() {
|
|
5669
6054
|
if (allowedCommandsCache) return allowedCommandsCache;
|
|
5670
6055
|
try {
|
|
5671
|
-
if (
|
|
5672
|
-
const content =
|
|
6056
|
+
if (fs14.existsSync(ALLOWED_COMMANDS_PATH)) {
|
|
6057
|
+
const content = fs14.readFileSync(ALLOWED_COMMANDS_PATH, "utf-8");
|
|
5673
6058
|
const config = JSON.parse(content);
|
|
5674
6059
|
allowedCommandsCache = new Set((config.commands ?? []).map((c) => c.name));
|
|
5675
6060
|
return allowedCommandsCache;
|
|
@@ -5686,8 +6071,8 @@ function detectNpmGlobalBin() {
|
|
|
5686
6071
|
timeout: 5e3
|
|
5687
6072
|
}).trim();
|
|
5688
6073
|
if (prefix) {
|
|
5689
|
-
const bin =
|
|
5690
|
-
if (
|
|
6074
|
+
const bin = path11.join(prefix, "bin");
|
|
6075
|
+
if (fs14.existsSync(bin)) return bin;
|
|
5691
6076
|
}
|
|
5692
6077
|
} catch {
|
|
5693
6078
|
}
|
|
@@ -5699,22 +6084,22 @@ function detectYarnGlobalBin() {
|
|
|
5699
6084
|
encoding: "utf-8",
|
|
5700
6085
|
timeout: 5e3
|
|
5701
6086
|
}).trim();
|
|
5702
|
-
if (bin &&
|
|
6087
|
+
if (bin && fs14.existsSync(bin)) return bin;
|
|
5703
6088
|
} catch {
|
|
5704
6089
|
}
|
|
5705
6090
|
return null;
|
|
5706
6091
|
}
|
|
5707
6092
|
function classifyDirectory(dirPath, npmGlobalBin, yarnGlobalBin, options) {
|
|
5708
|
-
const resolved =
|
|
6093
|
+
const resolved = path11.resolve(dirPath);
|
|
5709
6094
|
if (options.agentHome) {
|
|
5710
|
-
const agentBin =
|
|
5711
|
-
if (resolved ===
|
|
6095
|
+
const agentBin = path11.join(options.agentHome, "bin");
|
|
6096
|
+
if (resolved === path11.resolve(agentBin)) return "agent-bin";
|
|
5712
6097
|
}
|
|
5713
|
-
if (options.workspaceDir && resolved.startsWith(
|
|
6098
|
+
if (options.workspaceDir && resolved.startsWith(path11.resolve(options.workspaceDir))) {
|
|
5714
6099
|
return "workspace-bin";
|
|
5715
6100
|
}
|
|
5716
|
-
if (npmGlobalBin && resolved ===
|
|
5717
|
-
if (yarnGlobalBin && resolved ===
|
|
6101
|
+
if (npmGlobalBin && resolved === path11.resolve(npmGlobalBin)) return "npm-global";
|
|
6102
|
+
if (yarnGlobalBin && resolved === path11.resolve(yarnGlobalBin)) return "yarn-global";
|
|
5718
6103
|
if (resolved.startsWith("/opt/homebrew/") || resolved === "/usr/local/bin" || resolved === "/usr/local/sbin") {
|
|
5719
6104
|
return "homebrew";
|
|
5720
6105
|
}
|
|
@@ -5745,7 +6130,7 @@ function getProtection(name) {
|
|
|
5745
6130
|
}
|
|
5746
6131
|
function isShieldExecLink(filePath) {
|
|
5747
6132
|
try {
|
|
5748
|
-
const target =
|
|
6133
|
+
const target = fs14.readlinkSync(filePath);
|
|
5749
6134
|
return target.endsWith("shield-exec") || target.endsWith("/shield-exec");
|
|
5750
6135
|
} catch {
|
|
5751
6136
|
return false;
|
|
@@ -5767,10 +6152,10 @@ function scanBinaries(options) {
|
|
|
5767
6152
|
if (npmGlobalBin) candidateDirs.push(npmGlobalBin);
|
|
5768
6153
|
if (yarnGlobalBin) candidateDirs.push(yarnGlobalBin);
|
|
5769
6154
|
if (options.agentHome) {
|
|
5770
|
-
candidateDirs.push(
|
|
6155
|
+
candidateDirs.push(path11.join(options.agentHome, "bin"));
|
|
5771
6156
|
}
|
|
5772
6157
|
if (options.workspaceDir) {
|
|
5773
|
-
candidateDirs.push(
|
|
6158
|
+
candidateDirs.push(path11.join(options.workspaceDir, "node_modules", ".bin"));
|
|
5774
6159
|
}
|
|
5775
6160
|
if (options.extraDirs) {
|
|
5776
6161
|
candidateDirs.push(...options.extraDirs);
|
|
@@ -5778,7 +6163,7 @@ function scanBinaries(options) {
|
|
|
5778
6163
|
const seenDirs = /* @__PURE__ */ new Set();
|
|
5779
6164
|
const uniqueDirs = [];
|
|
5780
6165
|
for (const dir of candidateDirs) {
|
|
5781
|
-
const resolved =
|
|
6166
|
+
const resolved = path11.resolve(dir);
|
|
5782
6167
|
if (!seenDirs.has(resolved)) {
|
|
5783
6168
|
seenDirs.add(resolved);
|
|
5784
6169
|
uniqueDirs.push(resolved);
|
|
@@ -5788,16 +6173,16 @@ function scanBinaries(options) {
|
|
|
5788
6173
|
const directories = [];
|
|
5789
6174
|
for (const dir of uniqueDirs) {
|
|
5790
6175
|
try {
|
|
5791
|
-
if (!
|
|
6176
|
+
if (!fs14.existsSync(dir)) continue;
|
|
5792
6177
|
const sourceKind = classifyDirectory(dir, npmGlobalBin, yarnGlobalBin, options);
|
|
5793
6178
|
const contexts = getContextsForDir(dir, sourceKind, options);
|
|
5794
6179
|
let count = 0;
|
|
5795
|
-
const entries =
|
|
6180
|
+
const entries = fs14.readdirSync(dir);
|
|
5796
6181
|
for (const entry of entries) {
|
|
5797
|
-
const fullPath =
|
|
6182
|
+
const fullPath = path11.join(dir, entry);
|
|
5798
6183
|
try {
|
|
5799
|
-
const stat2 =
|
|
5800
|
-
if (!stat2.isFile() && !
|
|
6184
|
+
const stat2 = fs14.statSync(fullPath);
|
|
6185
|
+
if (!stat2.isFile() && !fs14.lstatSync(fullPath).isSymbolicLink()) continue;
|
|
5801
6186
|
if ((stat2.mode & 73) === 0) continue;
|
|
5802
6187
|
count++;
|
|
5803
6188
|
if (seenBinaries.has(entry)) {
|
|
@@ -5835,23 +6220,23 @@ function scanBinaries(options) {
|
|
|
5835
6220
|
}
|
|
5836
6221
|
|
|
5837
6222
|
// libs/shield-sandbox/src/discovery/skill-scanner.ts
|
|
5838
|
-
import * as
|
|
5839
|
-
import * as
|
|
6223
|
+
import * as fs16 from "node:fs";
|
|
6224
|
+
import * as path13 from "node:path";
|
|
5840
6225
|
import { parse as parseYaml } from "yaml";
|
|
5841
6226
|
|
|
5842
6227
|
// libs/shield-sandbox/src/skill-injector.ts
|
|
5843
|
-
import * as
|
|
5844
|
-
import * as
|
|
6228
|
+
import * as fs15 from "node:fs";
|
|
6229
|
+
import * as path12 from "node:path";
|
|
5845
6230
|
import { execSync as execSync10 } from "node:child_process";
|
|
5846
6231
|
import { createRequire as createRequire2 } from "node:module";
|
|
5847
6232
|
var require3 = createRequire2(import.meta.url);
|
|
5848
6233
|
function getSkillsDir(homeDir) {
|
|
5849
6234
|
const possiblePaths = [
|
|
5850
|
-
|
|
5851
|
-
|
|
6235
|
+
path12.join(homeDir, ".openclaw", "skills"),
|
|
6236
|
+
path12.join(homeDir, ".config", "openclaw", "skills")
|
|
5852
6237
|
];
|
|
5853
6238
|
for (const p of possiblePaths) {
|
|
5854
|
-
if (
|
|
6239
|
+
if (fs15.existsSync(path12.dirname(p))) {
|
|
5855
6240
|
return p;
|
|
5856
6241
|
}
|
|
5857
6242
|
}
|
|
@@ -5861,7 +6246,7 @@ function getAgenCoSkillPath() {
|
|
|
5861
6246
|
const possiblePaths = [];
|
|
5862
6247
|
try {
|
|
5863
6248
|
const pkgPath = require3.resolve("@agenshield/skills/package.json");
|
|
5864
|
-
possiblePaths.push(
|
|
6249
|
+
possiblePaths.push(path12.join(path12.dirname(pkgPath), "skills", "agenco-secure-integrations"));
|
|
5865
6250
|
} catch {
|
|
5866
6251
|
}
|
|
5867
6252
|
possiblePaths.push(
|
|
@@ -5869,27 +6254,27 @@ function getAgenCoSkillPath() {
|
|
|
5869
6254
|
"/opt/agenshield/skills/agenco-secure-integrations"
|
|
5870
6255
|
);
|
|
5871
6256
|
for (const p of possiblePaths) {
|
|
5872
|
-
if (
|
|
6257
|
+
if (fs15.existsSync(path12.join(p, "SKILL.md"))) {
|
|
5873
6258
|
return p;
|
|
5874
6259
|
}
|
|
5875
6260
|
}
|
|
5876
6261
|
throw new Error("AgenCo skill not found. Please reinstall AgenShield.");
|
|
5877
6262
|
}
|
|
5878
6263
|
function copyDirRecursive(src, dest) {
|
|
5879
|
-
if (!
|
|
5880
|
-
|
|
6264
|
+
if (!fs15.existsSync(dest)) {
|
|
6265
|
+
fs15.mkdirSync(dest, { recursive: true });
|
|
5881
6266
|
}
|
|
5882
|
-
const entries =
|
|
6267
|
+
const entries = fs15.readdirSync(src, { withFileTypes: true });
|
|
5883
6268
|
for (const entry of entries) {
|
|
5884
|
-
const srcPath =
|
|
5885
|
-
const destPath =
|
|
6269
|
+
const srcPath = path12.join(src, entry.name);
|
|
6270
|
+
const destPath = path12.join(dest, entry.name);
|
|
5886
6271
|
if (entry.isDirectory()) {
|
|
5887
6272
|
if (entry.name === "node_modules" || entry.name === "dist") {
|
|
5888
6273
|
continue;
|
|
5889
6274
|
}
|
|
5890
6275
|
copyDirRecursive(srcPath, destPath);
|
|
5891
6276
|
} else {
|
|
5892
|
-
|
|
6277
|
+
fs15.copyFileSync(srcPath, destPath);
|
|
5893
6278
|
}
|
|
5894
6279
|
}
|
|
5895
6280
|
}
|
|
@@ -5899,23 +6284,23 @@ async function injectAgenCoSkill(config) {
|
|
|
5899
6284
|
const injectedSkills = [];
|
|
5900
6285
|
try {
|
|
5901
6286
|
const sourcePath = getAgenCoSkillPath();
|
|
5902
|
-
if (!
|
|
5903
|
-
|
|
6287
|
+
if (!fs15.existsSync(skillsDir)) {
|
|
6288
|
+
fs15.mkdirSync(skillsDir, { recursive: true, mode: 493 });
|
|
5904
6289
|
}
|
|
5905
|
-
const destPath =
|
|
6290
|
+
const destPath = path12.join(skillsDir, "agenco-secure-integrations");
|
|
5906
6291
|
copyDirRecursive(sourcePath, destPath);
|
|
5907
|
-
const packageJson =
|
|
5908
|
-
const distDir =
|
|
5909
|
-
if (
|
|
6292
|
+
const packageJson = path12.join(destPath, "package.json");
|
|
6293
|
+
const distDir = path12.join(destPath, "dist");
|
|
6294
|
+
if (fs15.existsSync(packageJson) && !fs15.existsSync(distDir)) {
|
|
5910
6295
|
console.log("Building AgenCo skill...");
|
|
5911
6296
|
execSync10("npm install && npm run build", {
|
|
5912
6297
|
cwd: destPath,
|
|
5913
6298
|
stdio: "inherit"
|
|
5914
6299
|
});
|
|
5915
6300
|
}
|
|
5916
|
-
const binPath =
|
|
5917
|
-
if (
|
|
5918
|
-
|
|
6301
|
+
const binPath = path12.join(destPath, "bin", "agenco.js");
|
|
6302
|
+
if (fs15.existsSync(binPath)) {
|
|
6303
|
+
fs15.chmodSync(binPath, 493);
|
|
5919
6304
|
}
|
|
5920
6305
|
const socketGroupName = config.groups.socket.name;
|
|
5921
6306
|
try {
|
|
@@ -5938,21 +6323,32 @@ async function injectAgenCoSkill(config) {
|
|
|
5938
6323
|
};
|
|
5939
6324
|
}
|
|
5940
6325
|
}
|
|
6326
|
+
function generateSkillWrapperScript(skillSlug, targetBinPath) {
|
|
6327
|
+
return `#!/bin/bash
|
|
6328
|
+
# AgenShield skill wrapper for: ${skillSlug}
|
|
6329
|
+
# Sets execution context so the interceptor can apply skill-scoped policies
|
|
6330
|
+
|
|
6331
|
+
export AGENSHIELD_CONTEXT_TYPE=skill
|
|
6332
|
+
export AGENSHIELD_SKILL_SLUG=${skillSlug}
|
|
6333
|
+
|
|
6334
|
+
exec "${targetBinPath}" "$@"
|
|
6335
|
+
`;
|
|
6336
|
+
}
|
|
5941
6337
|
async function createAgenCoSymlink(config, binDir) {
|
|
5942
6338
|
try {
|
|
5943
6339
|
const skillsDir = getSkillsDir(config.agentUser.home);
|
|
5944
|
-
const agencoBin =
|
|
6340
|
+
const agencoBin = path12.join(
|
|
5945
6341
|
skillsDir,
|
|
5946
6342
|
"agenco-secure-integrations",
|
|
5947
6343
|
"bin",
|
|
5948
6344
|
"agenco.js"
|
|
5949
6345
|
);
|
|
5950
|
-
const
|
|
5951
|
-
if (
|
|
5952
|
-
|
|
6346
|
+
const wrapperPath = path12.join(binDir, "agenco");
|
|
6347
|
+
if (fs15.existsSync(wrapperPath)) {
|
|
6348
|
+
fs15.unlinkSync(wrapperPath);
|
|
5953
6349
|
}
|
|
5954
|
-
|
|
5955
|
-
|
|
6350
|
+
const wrapperContent = generateSkillWrapperScript("agenco-secure-integrations", agencoBin);
|
|
6351
|
+
fs15.writeFileSync(wrapperPath, wrapperContent, { mode: 493 });
|
|
5956
6352
|
return { success: true };
|
|
5957
6353
|
} catch (err) {
|
|
5958
6354
|
return {
|
|
@@ -5964,9 +6360,9 @@ async function createAgenCoSymlink(config, binDir) {
|
|
|
5964
6360
|
async function removeInjectedSkills(homeDir) {
|
|
5965
6361
|
try {
|
|
5966
6362
|
const skillsDir = getSkillsDir(homeDir);
|
|
5967
|
-
const agencoPath =
|
|
5968
|
-
if (
|
|
5969
|
-
|
|
6363
|
+
const agencoPath = path12.join(skillsDir, "agenco-secure-integrations");
|
|
6364
|
+
if (fs15.existsSync(agencoPath)) {
|
|
6365
|
+
fs15.rmSync(agencoPath, { recursive: true, force: true });
|
|
5970
6366
|
}
|
|
5971
6367
|
return { success: true };
|
|
5972
6368
|
} catch (err) {
|
|
@@ -5979,23 +6375,23 @@ async function removeInjectedSkills(homeDir) {
|
|
|
5979
6375
|
async function updateOpenClawMcpConfig(homeDir) {
|
|
5980
6376
|
try {
|
|
5981
6377
|
const configPaths = [
|
|
5982
|
-
|
|
5983
|
-
|
|
6378
|
+
path12.join(homeDir, ".openclaw", "mcp.json"),
|
|
6379
|
+
path12.join(homeDir, ".config", "openclaw", "mcp.json")
|
|
5984
6380
|
];
|
|
5985
6381
|
let configPath = configPaths[0];
|
|
5986
6382
|
for (const p of configPaths) {
|
|
5987
|
-
if (
|
|
6383
|
+
if (fs15.existsSync(p)) {
|
|
5988
6384
|
configPath = p;
|
|
5989
6385
|
break;
|
|
5990
6386
|
}
|
|
5991
6387
|
}
|
|
5992
|
-
const configDir =
|
|
5993
|
-
if (!
|
|
5994
|
-
|
|
6388
|
+
const configDir = path12.dirname(configPath);
|
|
6389
|
+
if (!fs15.existsSync(configDir)) {
|
|
6390
|
+
fs15.mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
5995
6391
|
}
|
|
5996
6392
|
let config = {};
|
|
5997
|
-
if (
|
|
5998
|
-
config = JSON.parse(
|
|
6393
|
+
if (fs15.existsSync(configPath)) {
|
|
6394
|
+
config = JSON.parse(fs15.readFileSync(configPath, "utf-8"));
|
|
5999
6395
|
}
|
|
6000
6396
|
if (!config.mcpServers) {
|
|
6001
6397
|
config.mcpServers = {};
|
|
@@ -6018,7 +6414,7 @@ async function updateOpenClawMcpConfig(homeDir) {
|
|
|
6018
6414
|
directory: getSkillsDir(homeDir),
|
|
6019
6415
|
pollInterval: 3e4
|
|
6020
6416
|
};
|
|
6021
|
-
|
|
6417
|
+
fs15.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 420 });
|
|
6022
6418
|
return { success: true };
|
|
6023
6419
|
} catch (err) {
|
|
6024
6420
|
return {
|
|
@@ -6179,8 +6575,8 @@ function extractCommands(metadata, body, binaryLookup) {
|
|
|
6179
6575
|
}
|
|
6180
6576
|
function getApprovalStatus(skillName) {
|
|
6181
6577
|
try {
|
|
6182
|
-
if (
|
|
6183
|
-
const content =
|
|
6578
|
+
if (fs16.existsSync(APPROVED_SKILLS_PATH)) {
|
|
6579
|
+
const content = fs16.readFileSync(APPROVED_SKILLS_PATH, "utf-8");
|
|
6184
6580
|
const approved = JSON.parse(content);
|
|
6185
6581
|
if (Array.isArray(approved) && approved.some((s) => s.name === skillName)) {
|
|
6186
6582
|
return "approved";
|
|
@@ -6189,8 +6585,8 @@ function getApprovalStatus(skillName) {
|
|
|
6189
6585
|
} catch {
|
|
6190
6586
|
}
|
|
6191
6587
|
try {
|
|
6192
|
-
const quarantinePath =
|
|
6193
|
-
if (
|
|
6588
|
+
const quarantinePath = path13.join(QUARANTINE_DIR, skillName);
|
|
6589
|
+
if (fs16.existsSync(quarantinePath)) {
|
|
6194
6590
|
return "quarantined";
|
|
6195
6591
|
}
|
|
6196
6592
|
} catch {
|
|
@@ -6201,19 +6597,19 @@ function scanSkills(options, binaryLookup) {
|
|
|
6201
6597
|
if (!options.agentHome) return [];
|
|
6202
6598
|
const skillsDir = getSkillsDir(options.agentHome);
|
|
6203
6599
|
const results = [];
|
|
6204
|
-
if (
|
|
6600
|
+
if (fs16.existsSync(skillsDir)) {
|
|
6205
6601
|
try {
|
|
6206
|
-
const entries =
|
|
6602
|
+
const entries = fs16.readdirSync(skillsDir, { withFileTypes: true });
|
|
6207
6603
|
for (const entry of entries) {
|
|
6208
6604
|
if (!entry.isDirectory()) continue;
|
|
6209
|
-
const skillPath =
|
|
6210
|
-
const skillMdPath =
|
|
6211
|
-
const hasSkillMd =
|
|
6605
|
+
const skillPath = path13.join(skillsDir, entry.name);
|
|
6606
|
+
const skillMdPath = path13.join(skillPath, "SKILL.md");
|
|
6607
|
+
const hasSkillMd = fs16.existsSync(skillMdPath);
|
|
6212
6608
|
let metadata = null;
|
|
6213
6609
|
let body = "";
|
|
6214
6610
|
if (hasSkillMd) {
|
|
6215
6611
|
try {
|
|
6216
|
-
const content =
|
|
6612
|
+
const content = fs16.readFileSync(skillMdPath, "utf-8");
|
|
6217
6613
|
const parsed = parseSkillMd(content);
|
|
6218
6614
|
if (parsed) {
|
|
6219
6615
|
metadata = parsed.metadata;
|
|
@@ -6236,20 +6632,20 @@ function scanSkills(options, binaryLookup) {
|
|
|
6236
6632
|
} catch {
|
|
6237
6633
|
}
|
|
6238
6634
|
}
|
|
6239
|
-
if (
|
|
6635
|
+
if (fs16.existsSync(QUARANTINE_DIR)) {
|
|
6240
6636
|
try {
|
|
6241
|
-
const entries =
|
|
6637
|
+
const entries = fs16.readdirSync(QUARANTINE_DIR, { withFileTypes: true });
|
|
6242
6638
|
for (const entry of entries) {
|
|
6243
6639
|
if (!entry.isDirectory()) continue;
|
|
6244
6640
|
if (results.some((s) => s.name === entry.name)) continue;
|
|
6245
|
-
const skillPath =
|
|
6246
|
-
const skillMdPath =
|
|
6247
|
-
const hasSkillMd =
|
|
6641
|
+
const skillPath = path13.join(QUARANTINE_DIR, entry.name);
|
|
6642
|
+
const skillMdPath = path13.join(skillPath, "SKILL.md");
|
|
6643
|
+
const hasSkillMd = fs16.existsSync(skillMdPath);
|
|
6248
6644
|
let metadata = null;
|
|
6249
6645
|
let body = "";
|
|
6250
6646
|
if (hasSkillMd) {
|
|
6251
6647
|
try {
|
|
6252
|
-
const content =
|
|
6648
|
+
const content = fs16.readFileSync(skillMdPath, "utf-8");
|
|
6253
6649
|
const parsed = parseSkillMd(content);
|
|
6254
6650
|
if (parsed) {
|
|
6255
6651
|
metadata = parsed.metadata;
|
|
@@ -6375,6 +6771,7 @@ export {
|
|
|
6375
6771
|
deployInterceptor,
|
|
6376
6772
|
detectOpenClaw,
|
|
6377
6773
|
devHarnessPreset,
|
|
6774
|
+
execWithProgress,
|
|
6378
6775
|
extractSkillInfo,
|
|
6379
6776
|
fixSocketPermissions,
|
|
6380
6777
|
forceUninstall,
|
|
@@ -6416,20 +6813,28 @@ export {
|
|
|
6416
6813
|
listPresets,
|
|
6417
6814
|
loadBackup,
|
|
6418
6815
|
loadLaunchDaemon,
|
|
6816
|
+
maskSecretValue,
|
|
6419
6817
|
migrateGitInstall,
|
|
6420
6818
|
migrateNpmInstall,
|
|
6421
6819
|
migrateOpenClaw,
|
|
6422
6820
|
openclawPreset,
|
|
6423
6821
|
parseSkillMd,
|
|
6822
|
+
patchNvmNode,
|
|
6424
6823
|
removeAllDirectories,
|
|
6425
6824
|
removeDynamicWrapper,
|
|
6426
6825
|
removeInjectedSkills,
|
|
6826
|
+
resolveEnvVarValue,
|
|
6427
6827
|
restartDaemon,
|
|
6428
6828
|
restoreInstallation,
|
|
6429
6829
|
restoreOriginalConfig,
|
|
6830
|
+
sanitizeOpenClawConfig,
|
|
6430
6831
|
saveBackup,
|
|
6431
6832
|
scanBinaries,
|
|
6432
6833
|
scanDiscovery,
|
|
6834
|
+
scanHost,
|
|
6835
|
+
scanOpenClawConfig,
|
|
6836
|
+
scanProcessEnv,
|
|
6837
|
+
scanShellProfiles,
|
|
6433
6838
|
scanSkills,
|
|
6434
6839
|
seedConfigFiles,
|
|
6435
6840
|
setupSocketDirectory,
|