@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/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 (only ~/bin \u2014 override anything that may have been added)
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 exactly
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 path4 from "node:path";
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 = path4.basename(process.argv[1] || "shield-exec");
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 result = response.result;
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 result = response.result;
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 fs8 from "node:fs/promises";
393
- import * as path5 from "node:path";
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 fs8.mkdir(SEATBELT_DIR, { recursive: true });
616
- await fs8.mkdir(path5.join(SEATBELT_DIR, "ops"), { recursive: true });
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 = path5.join(SEATBELT_DIR, "agent.sb");
615
+ const agentPath = path6.join(SEATBELT_DIR, "agent.sb");
621
616
  try {
622
- await fs8.writeFile(agentPath, agentProfile, { mode: 420 });
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 = path5.join(SEATBELT_DIR, "ops", `${op}.sb`);
634
+ const profilePath = path6.join(SEATBELT_DIR, "ops", `${op}.sb`);
640
635
  try {
641
- await fs8.writeFile(profilePath, profile, { mode: 420 });
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 fs8.readFile(profilePath, "utf-8");
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 fs8.readdir(SEATBELT_DIR);
714
+ const mainFiles = await fs9.readdir(SEATBELT_DIR);
720
715
  for (const file of mainFiles) {
721
716
  if (file.endsWith(".sb")) {
722
- profiles.push(path5.join(SEATBELT_DIR, file));
717
+ profiles.push(path6.join(SEATBELT_DIR, file));
723
718
  }
724
719
  }
725
- const opsDir = path5.join(SEATBELT_DIR, "ops");
726
- const opsFiles = await fs8.readdir(opsDir);
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(path5.join(opsDir, file));
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
- if (source.configPath && fs3.existsSync(source.configPath)) {
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
- if (source.configPath && fs3.existsSync(source.configPath)) {
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 injectSkillWatcherSetting(configDir) {
1679
- const settingsPath = path.join(configDir, "settings.json");
1680
- let settings = {};
1681
- try {
1682
- if (fs3.existsSync(settingsPath)) {
1683
- settings = JSON.parse(fs3.readFileSync(settingsPath, "utf-8"));
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
- settings = {};
1692
+ return;
1687
1693
  }
1688
- settings.skillWatcher = { enabled: true };
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(settingsPath, JSON.stringify(settings, null, 2));
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/detect.ts
1867
- import * as fs5 from "node:fs";
1868
- import * as path2 from "node:path";
1869
- import * as os2 from "node:os";
1870
- import { execSync as execSync4 } from "node:child_process";
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
- var HOME = getHome();
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
- fs5.accessSync(p);
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 = path2.join(npmRoot, "openclaw");
2178
+ const openclawPkg = path3.join(npmRoot, "openclaw");
1905
2179
  if (!pathExists(openclawPkg)) return { found: false, method: "npm" };
1906
2180
  let version;
1907
- const pkgJsonPath = path2.join(openclawPkg, "package.json");
2181
+ const pkgJsonPath = path3.join(openclawPkg, "package.json");
1908
2182
  if (pathExists(pkgJsonPath)) {
1909
2183
  try {
1910
- const pkg = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
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 ? path2.join(npmBin, "openclaw") : void 0;
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
- path2.join(HOME, "openclaw"),
1928
- path2.join(HOME, ".openclaw-src"),
1929
- path2.join(HOME, "code", "openclaw"),
1930
- path2.join(HOME, "src", "openclaw")
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 = path2.join(repoPath, ".git");
1935
- const pkgJson = path2.join(repoPath, "package.json");
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(fs5.readFileSync(pkgJson, "utf-8"));
2212
+ const pkg = JSON.parse(fs6.readFileSync(pkgJson, "utf-8"));
1939
2213
  if (pkg.name === "openclaw" || pkg.name?.includes("openclaw")) {
1940
- const wrapperPath2 = path2.join(HOME, ".local", "bin", "openclaw");
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 = path2.join(HOME, ".local", "bin", "openclaw");
2228
+ const wrapperPath = path3.join(HOME, ".local", "bin", "openclaw");
1955
2229
  if (pathExists(wrapperPath)) {
1956
2230
  try {
1957
- const content = fs5.readFileSync(wrapperPath, "utf-8");
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 = path2.join(repoPath, "package.json");
2236
+ const pkgJson = path3.join(repoPath, "package.json");
1963
2237
  let version;
1964
2238
  if (pathExists(pkgJson)) {
1965
2239
  try {
1966
- version = JSON.parse(fs5.readFileSync(pkgJson, "utf-8")).version;
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 = path2.join(HOME, ".openclaw");
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 fs6 from "node:fs";
2059
- import * as os3 from "node:os";
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().uuid(),
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
- operations: z.array(z.string()).optional()
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: os3.userInfo().username,
3271
- originalUserHome: os3.homedir(),
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
- fs6.writeFileSync(tempPath, JSON.stringify(backup, null, 2), { mode: 384 });
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
- fs6.unlinkSync(tempPath);
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(configPath) {
3325
- if (!fs6.existsSync(configPath)) {
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 (!fs6.existsSync(backupPath)) {
3608
+ if (!fs7.existsSync(backupPath)) {
3339
3609
  return { success: false, error: `Backup path does not exist: ${backupPath}` };
3340
3610
  }
3341
- if (fs6.existsSync(targetPath)) {
3611
+ if (fs7.existsSync(targetPath)) {
3342
3612
  try {
3343
- fs6.rmSync(targetPath, { recursive: true });
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
- fs6.renameSync(backupPath, targetPath);
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 fs7 from "node:fs";
3358
- import * as path3 from "node:path";
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 (fs7.existsSync(plistPath)) {
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 (!fs7.existsSync(plistPath)) {
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 (!fs7.existsSync(configBackupPath)) {
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 || path3.join(backup.originalUserHome, ".openclaw");
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 && fs7.existsSync(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 (!fs7.existsSync(GUARDED_SHELL_PATH)) {
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 (fs7.existsSync(p)) {
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 && fs7.existsSync(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 (fs7.existsSync("/Library/LaunchDaemons/com.agenshield.daemon.plist")) return true;
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 fs7.existsSync("/Library/LaunchDaemons/com.agenshield.broker.plist");
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 fs9 from "node:fs/promises";
3865
- import * as path6 from "node:path";
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 with Node.js interceptor",
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 - routes npm network requests through AgenShield interceptor
4002
- # Uses NODE_OPTIONS to load the interceptor module
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
- # Set up interceptor
4008
- export NODE_OPTIONS="${config.interceptorFlag} ${config.interceptorPath} \${NODE_OPTIONS:-}"
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
- # Find node - prefer copied binary, then homebrew, then system
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 with Node.js interceptor",
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 - runs npx with AgenShield interceptor
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
- # Set up interceptor
4130
- export NODE_OPTIONS="${config.interceptorFlag} ${config.interceptorPath} \${NODE_OPTIONS:-}"
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 = path6.join(targetDir, name);
4487
+ const wrapperPath = path7.join(targetDir, name);
4245
4488
  try {
4246
- await fs9.writeFile(wrapperPath, content, { mode: 493 });
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 = path6.join(targetDir, name);
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 fs9.mkdir(targetDir, { recursive: true });
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 fs9.mkdir(targetDir, { recursive: true });
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: path6.join(targetDir, name),
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 = path6.join(targetDir, name);
4585
+ const wrapperPath = path7.join(targetDir, name);
4343
4586
  try {
4344
- await fs9.unlink(wrapperPath);
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 = path6.join(targetDir, name);
4624
+ const wrapperPath = path7.join(targetDir, name);
4382
4625
  try {
4383
- await fs9.access(wrapperPath, fs9.constants.X_OK);
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 "${path6.dirname(shellPath)}"`);
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 = path6.join(binDir, cmd);
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 = path6.join(binDir, "node");
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 = path6.join(binDir, "python");
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 = path6.join(binDir, "python3");
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 = path6.join(targetDir, name);
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 fs9.unlink(wrapperPath);
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: path6.join(targetDir, name),
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 fs9.access(srcPath);
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 = path6.dirname(brokerPkgPath);
4642
- const brokerPkg = JSON.parse(await fs9.readFile(brokerPkgPath, "utf-8"));
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 = path6.resolve(brokerDir, binEntry);
4645
- await fs9.access(srcPath);
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 = path6.dirname(brokerPkgPath);
4679
- const brokerPkg = JSON.parse(await fs9.readFile(brokerPkgPath, "utf-8"));
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 = path6.resolve(brokerDir, clientEntry || "./dist/client/shield-client.js");
4682
- await fs9.access(srcPath);
4683
- let content = await fs9.readFile(srcPath, "utf-8");
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 fs9.writeFile(tmpPath, content, { mode: 493 });
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 = path6.resolve(path6.dirname(srcBinaryPath) + relPath + dylibName);
4969
+ resolvedPath = path7.resolve(path7.dirname(srcBinaryPath) + relPath + dylibName);
4727
4970
  } else {
4728
- resolvedPath = path6.resolve(path6.dirname(srcBinaryPath), "..", "lib", dylibName);
4971
+ resolvedPath = path7.resolve(path7.dirname(srcBinaryPath), "..", "lib", dylibName);
4729
4972
  }
4730
4973
  try {
4731
- await fs9.access(resolvedPath);
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 fs9.access(srcPath);
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 -o- "${NVM_INSTALL_URL}" | PROFILE=/dev/null /bin/bash`
5043
+ `/usr/bin/curl -sSo- "${NVM_INSTALL_URL}" | PROFILE=/dev/null /bin/bash`
4801
5044
  ].join(" && ");
4802
- await execAsync4(`sudo -u ${agentUsername} /bin/bash -c '${installCmd}'`, { timeout: 6e4 });
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 execAsync4(`sudo -u ${agentUsername} /bin/bash -c '${nvmInstallCmd}'`, { timeout: 12e4 });
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 fs9.access(loc, fs9.constants.X_OK);
4901
- const symlinkPath = path6.join(binDir, cmd);
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
- log("Installing NVM + Node.js for agent user");
4926
- const nvmResult = await installAgentNvm({
4927
- agentHome,
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(`NVM install failed: ${nvmResult.message}. Falling back to host node binary.`);
4942
- log("Copying node binary to /opt/agenshield/bin/node-bin");
4943
- const nodeResult = await copyNodeBinary(userConfig);
4944
- if (!nodeResult.success) {
4945
- errors.push(`Node binary: ${nodeResult.message}`);
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 fs10 from "node:fs/promises";
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>clawbroker</string>
5474
+ <string>ash_default_broker</string>
5105
5475
 
5106
5476
  <key>GroupName</key>
5107
- <string>clawshield</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 fs10.access(PLIST_PATH);
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 fs10.access(socketPath);
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 fs11 from "node:fs";
5381
- import * as os4 from "node:os";
5382
- import * as path7 from "node:path";
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 = path7.join("/Users", sudoUser);
5400
- if (fs11.existsSync(userHome)) return userHome;
5778
+ const userHome = path9.join("/Users", sudoUser);
5779
+ if (fs12.existsSync(userHome)) return userHome;
5401
5780
  }
5402
- return os4.homedir();
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 = path7.join(process.cwd(), "tools/test-harness/bin/dummy-openclaw.js");
5412
- if (fs11.existsSync(dummyOpenclawPath)) {
5413
- const testHarnessDir2 = path7.join(process.cwd(), "tools/test-harness");
5414
- const pkgPath2 = path7.join(testHarnessDir2, "package.json");
5415
- let version = "1.0.0-dummy";
5416
- if (fs11.existsSync(pkgPath2)) {
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(fs11.readFileSync(pkgPath2, "utf-8"));
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 = fs11.existsSync(path7.join(HOME2, ".openclaw-dev")) ? path7.join(HOME2, ".openclaw-dev") : fs11.existsSync(path7.join(HOME2, ".openclaw")) ? path7.join(HOME2, ".openclaw") : void 0;
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: path7.resolve(dummyOpenclawPath),
5810
+ binaryPath: path9.resolve(dummyOpenclawPath),
5432
5811
  configPath,
5433
5812
  method: "custom"
5434
5813
  };
5435
5814
  }
5436
- const testHarnessDir = path7.join(process.cwd(), "tools/test-harness");
5437
- const pkgPath = path7.join(testHarnessDir, "package.json");
5438
- if (fs11.existsSync(pkgPath)) {
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(fs11.readFileSync(pkgPath, "utf-8"));
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 = fs11.existsSync(path7.join(HOME2, ".openclaw-dev")) ? path7.join(HOME2, ".openclaw-dev") : fs11.existsSync(path7.join(HOME2, ".openclaw")) ? path7.join(HOME2, ".openclaw") : void 0;
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 || "1.0.0-dummy",
5825
+ version: pkg.version || "2026.2.1",
5447
5826
  packagePath: testHarnessDir,
5448
- binaryPath: path7.join(testHarnessDir, "bin/dummy-openclaw.js"),
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 = path7.join(context.directories.binDir, "openclaw");
5480
- const entryPath = path7.join(packageDir, "bin", "dummy-openclaw.js");
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 && fs11.existsSync(context.detection.configPath)) {
5496
- const agentConfigDir = path7.join(context.agentUser.home, ".openclaw");
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: path7.join(context.agentUser.home, ".openclaw")
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 path7.join(context.directories.binDir, "openclaw");
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 fs12 from "node:fs";
5532
- import * as path8 from "node:path";
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 = path8.resolve(context.entryPoint);
5560
- if (!fs12.existsSync(entryPath)) {
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 = path8.dirname(entryPath);
5564
- const entryFilename = path8.basename(entryPath);
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 = path8.join(context.directories.binDir, "agent");
5576
- const newEntryPath = path8.join(context.directories.packageDir, entryFilename);
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
- fs12.writeFileSync(tempPath, wrapperContent, { mode: 493 });
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 fs13 from "node:fs";
5650
- import * as path9 from "node:path";
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 (fs13.existsSync(ALLOWED_COMMANDS_PATH)) {
5672
- const content = fs13.readFileSync(ALLOWED_COMMANDS_PATH, "utf-8");
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 = path9.join(prefix, "bin");
5690
- if (fs13.existsSync(bin)) return bin;
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 && fs13.existsSync(bin)) return 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 = path9.resolve(dirPath);
6093
+ const resolved = path11.resolve(dirPath);
5709
6094
  if (options.agentHome) {
5710
- const agentBin = path9.join(options.agentHome, "bin");
5711
- if (resolved === path9.resolve(agentBin)) return "agent-bin";
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(path9.resolve(options.workspaceDir))) {
6098
+ if (options.workspaceDir && resolved.startsWith(path11.resolve(options.workspaceDir))) {
5714
6099
  return "workspace-bin";
5715
6100
  }
5716
- if (npmGlobalBin && resolved === path9.resolve(npmGlobalBin)) return "npm-global";
5717
- if (yarnGlobalBin && resolved === path9.resolve(yarnGlobalBin)) return "yarn-global";
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 = fs13.readlinkSync(filePath);
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(path9.join(options.agentHome, "bin"));
6155
+ candidateDirs.push(path11.join(options.agentHome, "bin"));
5771
6156
  }
5772
6157
  if (options.workspaceDir) {
5773
- candidateDirs.push(path9.join(options.workspaceDir, "node_modules", ".bin"));
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 = path9.resolve(dir);
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 (!fs13.existsSync(dir)) continue;
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 = fs13.readdirSync(dir);
6180
+ const entries = fs14.readdirSync(dir);
5796
6181
  for (const entry of entries) {
5797
- const fullPath = path9.join(dir, entry);
6182
+ const fullPath = path11.join(dir, entry);
5798
6183
  try {
5799
- const stat2 = fs13.statSync(fullPath);
5800
- if (!stat2.isFile() && !fs13.lstatSync(fullPath).isSymbolicLink()) continue;
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 fs15 from "node:fs";
5839
- import * as path11 from "node:path";
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 fs14 from "node:fs";
5844
- import * as path10 from "node:path";
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
- path10.join(homeDir, ".openclaw", "skills"),
5851
- path10.join(homeDir, ".config", "openclaw", "skills")
6235
+ path12.join(homeDir, ".openclaw", "skills"),
6236
+ path12.join(homeDir, ".config", "openclaw", "skills")
5852
6237
  ];
5853
6238
  for (const p of possiblePaths) {
5854
- if (fs14.existsSync(path10.dirname(p))) {
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(path10.join(path10.dirname(pkgPath), "skills", "agenco-secure-integrations"));
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 (fs14.existsSync(path10.join(p, "SKILL.md"))) {
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 (!fs14.existsSync(dest)) {
5880
- fs14.mkdirSync(dest, { recursive: true });
6264
+ if (!fs15.existsSync(dest)) {
6265
+ fs15.mkdirSync(dest, { recursive: true });
5881
6266
  }
5882
- const entries = fs14.readdirSync(src, { withFileTypes: true });
6267
+ const entries = fs15.readdirSync(src, { withFileTypes: true });
5883
6268
  for (const entry of entries) {
5884
- const srcPath = path10.join(src, entry.name);
5885
- const destPath = path10.join(dest, entry.name);
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
- fs14.copyFileSync(srcPath, destPath);
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 (!fs14.existsSync(skillsDir)) {
5903
- fs14.mkdirSync(skillsDir, { recursive: true, mode: 493 });
6287
+ if (!fs15.existsSync(skillsDir)) {
6288
+ fs15.mkdirSync(skillsDir, { recursive: true, mode: 493 });
5904
6289
  }
5905
- const destPath = path10.join(skillsDir, "agenco-secure-integrations");
6290
+ const destPath = path12.join(skillsDir, "agenco-secure-integrations");
5906
6291
  copyDirRecursive(sourcePath, destPath);
5907
- const packageJson = path10.join(destPath, "package.json");
5908
- const distDir = path10.join(destPath, "dist");
5909
- if (fs14.existsSync(packageJson) && !fs14.existsSync(distDir)) {
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 = path10.join(destPath, "bin", "agenco.js");
5917
- if (fs14.existsSync(binPath)) {
5918
- fs14.chmodSync(binPath, 493);
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 = path10.join(
6340
+ const agencoBin = path12.join(
5945
6341
  skillsDir,
5946
6342
  "agenco-secure-integrations",
5947
6343
  "bin",
5948
6344
  "agenco.js"
5949
6345
  );
5950
- const symlinkPath = path10.join(binDir, "agenco");
5951
- if (fs14.existsSync(symlinkPath)) {
5952
- fs14.unlinkSync(symlinkPath);
6346
+ const wrapperPath = path12.join(binDir, "agenco");
6347
+ if (fs15.existsSync(wrapperPath)) {
6348
+ fs15.unlinkSync(wrapperPath);
5953
6349
  }
5954
- fs14.symlinkSync(agencoBin, symlinkPath);
5955
- fs14.chmodSync(symlinkPath, 493);
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 = path10.join(skillsDir, "agenco-secure-integrations");
5968
- if (fs14.existsSync(agencoPath)) {
5969
- fs14.rmSync(agencoPath, { recursive: true, force: true });
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
- path10.join(homeDir, ".openclaw", "mcp.json"),
5983
- path10.join(homeDir, ".config", "openclaw", "mcp.json")
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 (fs14.existsSync(p)) {
6383
+ if (fs15.existsSync(p)) {
5988
6384
  configPath = p;
5989
6385
  break;
5990
6386
  }
5991
6387
  }
5992
- const configDir = path10.dirname(configPath);
5993
- if (!fs14.existsSync(configDir)) {
5994
- fs14.mkdirSync(configDir, { recursive: true, mode: 493 });
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 (fs14.existsSync(configPath)) {
5998
- config = JSON.parse(fs14.readFileSync(configPath, "utf-8"));
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
- fs14.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 420 });
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 (fs15.existsSync(APPROVED_SKILLS_PATH)) {
6183
- const content = fs15.readFileSync(APPROVED_SKILLS_PATH, "utf-8");
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 = path11.join(QUARANTINE_DIR, skillName);
6193
- if (fs15.existsSync(quarantinePath)) {
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 (fs15.existsSync(skillsDir)) {
6600
+ if (fs16.existsSync(skillsDir)) {
6205
6601
  try {
6206
- const entries = fs15.readdirSync(skillsDir, { withFileTypes: true });
6602
+ const entries = fs16.readdirSync(skillsDir, { withFileTypes: true });
6207
6603
  for (const entry of entries) {
6208
6604
  if (!entry.isDirectory()) continue;
6209
- const skillPath = path11.join(skillsDir, entry.name);
6210
- const skillMdPath = path11.join(skillPath, "SKILL.md");
6211
- const hasSkillMd = fs15.existsSync(skillMdPath);
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 = fs15.readFileSync(skillMdPath, "utf-8");
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 (fs15.existsSync(QUARANTINE_DIR)) {
6635
+ if (fs16.existsSync(QUARANTINE_DIR)) {
6240
6636
  try {
6241
- const entries = fs15.readdirSync(QUARANTINE_DIR, { withFileTypes: true });
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 = path11.join(QUARANTINE_DIR, entry.name);
6246
- const skillMdPath = path11.join(skillPath, "SKILL.md");
6247
- const hasSkillMd = fs15.existsSync(skillMdPath);
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 = fs15.readFileSync(skillMdPath, "utf-8");
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,