@agenshield/daemon 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/main.js CHANGED
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env node
2
-
3
- // libs/shield-daemon/src/main.ts
4
- import * as fs20 from "node:fs";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
5
11
 
6
12
  // libs/shield-daemon/src/config/paths.ts
7
13
  import * as path from "node:path";
@@ -16,10 +22,14 @@ function getConfigPath() {
16
22
  function getPidPath() {
17
23
  return path.join(getConfigDir(), PID_FILE);
18
24
  }
25
+ var init_paths = __esm({
26
+ "libs/shield-daemon/src/config/paths.ts"() {
27
+ "use strict";
28
+ }
29
+ });
19
30
 
20
31
  // libs/shield-daemon/src/config/defaults.ts
21
32
  import { DEFAULT_PORT, DEFAULT_HOST } from "@agenshield/ipc";
22
- var VERSION = "0.1.0";
23
33
  function getDefaultConfig() {
24
34
  return {
25
35
  version: VERSION,
@@ -36,8 +46,22 @@ function getDefaultConfig() {
36
46
  }
37
47
  };
38
48
  }
49
+ var VERSION;
50
+ var init_defaults = __esm({
51
+ "libs/shield-daemon/src/config/defaults.ts"() {
52
+ "use strict";
53
+ VERSION = "0.1.0";
54
+ }
55
+ });
39
56
 
40
57
  // libs/shield-daemon/src/config/loader.ts
58
+ var loader_exports = {};
59
+ __export(loader_exports, {
60
+ ensureConfigDir: () => ensureConfigDir,
61
+ loadConfig: () => loadConfig,
62
+ saveConfig: () => saveConfig,
63
+ updateConfig: () => updateConfig
64
+ });
41
65
  import * as fs from "node:fs";
42
66
  import { ShieldConfigSchema, DEFAULT_PORT as DEFAULT_PORT2 } from "@agenshield/ipc";
43
67
  function ensureConfigDir() {
@@ -84,31 +108,35 @@ function updateConfig(updates) {
84
108
  saveConfig(updated);
85
109
  return updated;
86
110
  }
87
-
88
- // libs/shield-daemon/src/server.ts
89
- import * as fs19 from "node:fs";
90
- import Fastify from "fastify";
91
- import cors from "@fastify/cors";
92
- import fastifyStatic from "@fastify/static";
93
-
94
- // libs/shield-daemon/src/routes/index.ts
95
- import { API_PREFIX } from "@agenshield/ipc";
96
-
97
- // libs/shield-daemon/src/routes/health.ts
98
- async function healthRoutes(app) {
99
- app.get("/health", async () => {
100
- return {
101
- success: true,
102
- data: {
103
- ok: true,
104
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
105
- mode: "daemon"
106
- }
107
- };
108
- });
109
- }
111
+ var init_loader = __esm({
112
+ "libs/shield-daemon/src/config/loader.ts"() {
113
+ "use strict";
114
+ init_paths();
115
+ init_defaults();
116
+ }
117
+ });
110
118
 
111
119
  // libs/shield-daemon/src/state/index.ts
120
+ var state_exports = {};
121
+ __export(state_exports, {
122
+ addConnectedIntegration: () => addConnectedIntegration,
123
+ addGroupState: () => addGroupState,
124
+ addUserState: () => addUserState,
125
+ getDefaultState: () => getDefaultState,
126
+ getPasscodeProtectionState: () => getPasscodeProtectionState,
127
+ getStatePath: () => getStatePath,
128
+ initializeState: () => initializeState,
129
+ loadState: () => loadState,
130
+ removeConnectedIntegration: () => removeConnectedIntegration,
131
+ removeGroupState: () => removeGroupState,
132
+ removeUserState: () => removeUserState,
133
+ saveState: () => saveState,
134
+ updateAgenCoState: () => updateAgenCoState,
135
+ updateDaemonState: () => updateDaemonState,
136
+ updateInstallationState: () => updateInstallationState,
137
+ updatePasscodeProtectionState: () => updatePasscodeProtectionState,
138
+ updateState: () => updateState
139
+ });
112
140
  import * as fs2 from "node:fs";
113
141
  import * as path2 from "node:path";
114
142
  import { STATE_FILE, DEFAULT_PORT as DEFAULT_PORT3 } from "@agenshield/ipc";
@@ -165,18 +193,89 @@ function saveState(state) {
165
193
  }
166
194
  fs2.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 420 });
167
195
  }
196
+ function updateState(updates) {
197
+ const current = loadState();
198
+ const updated = {
199
+ ...current,
200
+ ...updates
201
+ };
202
+ if (updates.daemon) {
203
+ updated.daemon = { ...current.daemon, ...updates.daemon };
204
+ }
205
+ if (updates.agenco) {
206
+ updated.agenco = { ...current.agenco, ...updates.agenco };
207
+ }
208
+ if (updates.installation) {
209
+ updated.installation = { ...current.installation, ...updates.installation };
210
+ }
211
+ if (updates.passcodeProtection) {
212
+ updated.passcodeProtection = { ...current.passcodeProtection, ...updates.passcodeProtection };
213
+ }
214
+ saveState(updated);
215
+ return updated;
216
+ }
217
+ function updateDaemonState(updates) {
218
+ const current = loadState();
219
+ current.daemon = { ...current.daemon, ...updates };
220
+ saveState(current);
221
+ return current;
222
+ }
168
223
  function updateAgenCoState(updates) {
169
224
  const current = loadState();
170
225
  current.agenco = { ...current.agenco, ...updates };
171
226
  saveState(current);
172
227
  return current;
173
228
  }
229
+ function updateInstallationState(updates) {
230
+ const current = loadState();
231
+ current.installation = { ...current.installation, ...updates };
232
+ saveState(current);
233
+ return current;
234
+ }
174
235
  function updatePasscodeProtectionState(updates) {
175
236
  const current = loadState();
176
237
  current.passcodeProtection = { ...current.passcodeProtection, ...updates };
177
238
  saveState(current);
178
239
  return current;
179
240
  }
241
+ function getPasscodeProtectionState() {
242
+ const current = loadState();
243
+ return current.passcodeProtection;
244
+ }
245
+ function addUserState(user) {
246
+ const current = loadState();
247
+ const existingIndex = current.users.findIndex((u) => u.username === user.username);
248
+ if (existingIndex >= 0) {
249
+ current.users[existingIndex] = user;
250
+ } else {
251
+ current.users.push(user);
252
+ }
253
+ saveState(current);
254
+ return current;
255
+ }
256
+ function removeUserState(username) {
257
+ const current = loadState();
258
+ current.users = current.users.filter((u) => u.username !== username);
259
+ saveState(current);
260
+ return current;
261
+ }
262
+ function addGroupState(group) {
263
+ const current = loadState();
264
+ const existingIndex = current.groups.findIndex((g) => g.name === group.name);
265
+ if (existingIndex >= 0) {
266
+ current.groups[existingIndex] = group;
267
+ } else {
268
+ current.groups.push(group);
269
+ }
270
+ saveState(current);
271
+ return current;
272
+ }
273
+ function removeGroupState(name) {
274
+ const current = loadState();
275
+ current.groups = current.groups.filter((g) => g.name !== name);
276
+ saveState(current);
277
+ return current;
278
+ }
180
279
  function addConnectedIntegration(integrationId) {
181
280
  const current = loadState();
182
281
  if (!current.agenco.connectedIntegrations.includes(integrationId)) {
@@ -185,8 +284,308 @@ function addConnectedIntegration(integrationId) {
185
284
  }
186
285
  return current;
187
286
  }
287
+ function removeConnectedIntegration(integrationId) {
288
+ const current = loadState();
289
+ current.agenco.connectedIntegrations = current.agenco.connectedIntegrations.filter(
290
+ (id) => id !== integrationId
291
+ );
292
+ saveState(current);
293
+ return current;
294
+ }
295
+ function initializeState() {
296
+ const statePath = getStatePath();
297
+ if (!fs2.existsSync(statePath)) {
298
+ const state = getDefaultState();
299
+ saveState(state);
300
+ return state;
301
+ }
302
+ return loadState();
303
+ }
304
+ var init_state = __esm({
305
+ "libs/shield-daemon/src/state/index.ts"() {
306
+ "use strict";
307
+ init_paths();
308
+ }
309
+ });
310
+
311
+ // libs/shield-daemon/src/command-sync.ts
312
+ var command_sync_exports = {};
313
+ __export(command_sync_exports, {
314
+ ensureWrappersInstalled: () => ensureWrappersInstalled,
315
+ syncCommandPolicies: () => syncCommandPolicies,
316
+ syncCommandPoliciesAndWrappers: () => syncCommandPoliciesAndWrappers
317
+ });
318
+ import * as fs5 from "node:fs";
319
+ import * as path5 from "node:path";
320
+ import { execSync as execSync3 } from "node:child_process";
321
+ import { PROXIED_COMMANDS, BASIC_SYSTEM_COMMANDS } from "@agenshield/sandbox";
322
+ function resolveCommandPaths(name) {
323
+ const paths = [];
324
+ for (const dir of BIN_SEARCH_DIRS) {
325
+ const candidate = path5.join(dir, name);
326
+ try {
327
+ if (fs5.existsSync(candidate)) {
328
+ const stat = fs5.statSync(candidate);
329
+ if (stat.isFile() && (stat.mode & 73) !== 0) {
330
+ paths.push(candidate);
331
+ }
332
+ }
333
+ } catch {
334
+ }
335
+ }
336
+ if (paths.length === 0) {
337
+ try {
338
+ const result = execSync3(`which ${name} 2>/dev/null`, { encoding: "utf-8" }).trim();
339
+ if (result && path5.isAbsolute(result)) {
340
+ paths.push(result);
341
+ }
342
+ } catch {
343
+ }
344
+ }
345
+ return paths;
346
+ }
347
+ function extractCommandName(pattern) {
348
+ return pattern.trim().split(/\s+/)[0];
349
+ }
350
+ function extractPolicyCommandNames(policies) {
351
+ const names = /* @__PURE__ */ new Set();
352
+ for (const p of policies) {
353
+ if (p.target === "command" && p.action === "allow" && p.enabled) {
354
+ for (const pattern of p.patterns) {
355
+ const name = extractCommandName(pattern);
356
+ if (name) names.add(name);
357
+ }
358
+ }
359
+ }
360
+ return names;
361
+ }
362
+ function syncCommandPolicies(policies, logger) {
363
+ const log = logger ?? noop2;
364
+ const commandPolicies = policies.filter(
365
+ (p) => p.target === "command" && p.action === "allow" && p.enabled
366
+ );
367
+ const commandNames = /* @__PURE__ */ new Set();
368
+ for (const policy of commandPolicies) {
369
+ for (const pattern of policy.patterns) {
370
+ const name = extractCommandName(pattern);
371
+ if (name) commandNames.add(name);
372
+ }
373
+ }
374
+ const commands = [];
375
+ const now = (/* @__PURE__ */ new Date()).toISOString();
376
+ for (const name of commandNames) {
377
+ const paths = resolveCommandPaths(name);
378
+ if (paths.length === 0) {
379
+ log.warn(`[command-sync] command '${name}' not found on system, adding without paths`);
380
+ }
381
+ commands.push({
382
+ name,
383
+ paths,
384
+ addedAt: now,
385
+ addedBy: "policy",
386
+ category: "policy-managed"
387
+ });
388
+ }
389
+ const config = {
390
+ version: "1.0.0",
391
+ commands
392
+ };
393
+ const json = JSON.stringify(config, null, 2) + "\n";
394
+ try {
395
+ const dir = path5.dirname(ALLOWED_COMMANDS_PATH);
396
+ if (!fs5.existsSync(dir)) {
397
+ fs5.mkdirSync(dir, { recursive: true });
398
+ }
399
+ fs5.writeFileSync(ALLOWED_COMMANDS_PATH, json, "utf-8");
400
+ log.info(`[command-sync] wrote ${commands.length} commands to allowlist`);
401
+ } catch {
402
+ log.warn(`[command-sync] cannot write to ${ALLOWED_COMMANDS_PATH} (broker forwards to daemon for policy checks)`);
403
+ }
404
+ }
405
+ function generateFallbackWrapper(cmd) {
406
+ return [
407
+ "#!/bin/bash",
408
+ `# ${cmd} - AgenShield proxy (auto-generated)`,
409
+ "if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi",
410
+ `exec /opt/agenshield/bin/shield-client exec ${cmd} "$@"`,
411
+ ""
412
+ ].join("\n");
413
+ }
414
+ function installWrappersInDir(binDir, log, policyCommands) {
415
+ const shieldExecPath = "/opt/agenshield/bin/shield-exec";
416
+ if (!fs5.existsSync(binDir)) {
417
+ try {
418
+ fs5.mkdirSync(binDir, { recursive: true, mode: 493 });
419
+ } catch {
420
+ log.warn(`[command-sync] cannot create bin dir ${binDir}`);
421
+ return;
422
+ }
423
+ }
424
+ const hasShieldExec = fs5.existsSync(shieldExecPath);
425
+ if (!hasShieldExec) {
426
+ log.warn("[command-sync] shield-exec not found, using bash wrapper fallback");
427
+ }
428
+ for (const cmd of ALL_PROXIED_COMMANDS) {
429
+ const wrapperPath = path5.join(binDir, cmd);
430
+ if (fs5.existsSync(wrapperPath)) {
431
+ continue;
432
+ }
433
+ if (hasShieldExec) {
434
+ try {
435
+ fs5.symlinkSync(shieldExecPath, wrapperPath);
436
+ continue;
437
+ } catch {
438
+ log.warn(`[command-sync] cannot symlink ${cmd}, falling back to bash wrapper`);
439
+ }
440
+ }
441
+ try {
442
+ fs5.writeFileSync(wrapperPath, generateFallbackWrapper(cmd), { mode: 493 });
443
+ } catch {
444
+ log.warn(`[command-sync] cannot write wrapper for ${cmd}`);
445
+ }
446
+ }
447
+ if (policyCommands) {
448
+ for (const cmd of policyCommands) {
449
+ if (PROXIED_COMMANDS_SET.has(cmd)) continue;
450
+ if (BASIC_SYSTEM_COMMANDS_SET.has(cmd)) continue;
451
+ if (SPECIALIZED_WRAPPER_COMMANDS.has(cmd)) continue;
452
+ const wrapperPath = path5.join(binDir, cmd);
453
+ if (fs5.existsSync(wrapperPath)) continue;
454
+ if (hasShieldExec) {
455
+ try {
456
+ fs5.symlinkSync(shieldExecPath, wrapperPath);
457
+ log.info(`[command-sync] installed dynamic wrapper (symlink): ${cmd}`);
458
+ continue;
459
+ } catch {
460
+ log.warn(`[command-sync] cannot symlink ${cmd}, falling back to bash wrapper`);
461
+ }
462
+ }
463
+ try {
464
+ fs5.writeFileSync(wrapperPath, generateFallbackWrapper(cmd), { mode: 493 });
465
+ log.info(`[command-sync] installed dynamic wrapper (bash): ${cmd}`);
466
+ } catch {
467
+ log.warn(`[command-sync] cannot write dynamic wrapper for ${cmd}`);
468
+ }
469
+ }
470
+ }
471
+ }
472
+ function cleanupStaleWrappers(binDir, policyCommands, log) {
473
+ const shieldExecPath = "/opt/agenshield/bin/shield-exec";
474
+ let entries;
475
+ try {
476
+ entries = fs5.readdirSync(binDir);
477
+ } catch {
478
+ return;
479
+ }
480
+ for (const entry of entries) {
481
+ if (PROXIED_COMMANDS_SET.has(entry)) continue;
482
+ if (SPECIALIZED_WRAPPER_COMMANDS.has(entry)) continue;
483
+ if (BASIC_SYSTEM_COMMANDS_SET.has(entry)) continue;
484
+ if (policyCommands.has(entry)) continue;
485
+ const wrapperPath = path5.join(binDir, entry);
486
+ try {
487
+ const stat = fs5.lstatSync(wrapperPath);
488
+ if (stat.isSymbolicLink()) {
489
+ const target = fs5.readlinkSync(wrapperPath);
490
+ if (target === shieldExecPath) {
491
+ fs5.unlinkSync(wrapperPath);
492
+ log.info(`[command-sync] removed stale dynamic wrapper (symlink): ${entry}`);
493
+ }
494
+ } else if (stat.isFile()) {
495
+ const content = fs5.readFileSync(wrapperPath, "utf-8");
496
+ if (content.includes("shield-client exec") && content.includes("AgenShield proxy (auto-generated)")) {
497
+ fs5.unlinkSync(wrapperPath);
498
+ log.info(`[command-sync] removed stale dynamic wrapper (bash): ${entry}`);
499
+ }
500
+ }
501
+ } catch {
502
+ }
503
+ }
504
+ }
505
+ function ensureWrappersInstalled(state, logger, policyCommands) {
506
+ const log = logger ?? noop2;
507
+ const agentUser = state.users.find((u) => u.type === "agent");
508
+ if (!agentUser) {
509
+ log.warn("[command-sync] no agent user in state, skipping wrapper installation");
510
+ return;
511
+ }
512
+ const agentBinDir = path5.join(agentUser.homeDir, "bin");
513
+ log.info(`[command-sync] ensuring wrappers in agent bin: ${agentBinDir}`);
514
+ installWrappersInDir(agentBinDir, log, policyCommands);
515
+ if (policyCommands) {
516
+ cleanupStaleWrappers(agentBinDir, policyCommands, log);
517
+ }
518
+ const agentHomeEnv = process.env["AGENSHIELD_AGENT_HOME"];
519
+ if (agentHomeEnv && agentHomeEnv !== agentUser.homeDir) {
520
+ const envBinDir = path5.join(agentHomeEnv, "bin");
521
+ log.info(`[command-sync] ensuring wrappers in env agent bin: ${envBinDir}`);
522
+ installWrappersInDir(envBinDir, log, policyCommands);
523
+ if (policyCommands) {
524
+ cleanupStaleWrappers(envBinDir, policyCommands, log);
525
+ }
526
+ }
527
+ }
528
+ function syncCommandPoliciesAndWrappers(policies, state, logger) {
529
+ const policyCommands = extractPolicyCommandNames(policies);
530
+ syncCommandPolicies(policies, logger);
531
+ ensureWrappersInstalled(state, logger, policyCommands);
532
+ }
533
+ var noop2, ALLOWED_COMMANDS_PATH, BIN_SEARCH_DIRS, ALL_PROXIED_COMMANDS, BASIC_SYSTEM_COMMANDS_SET, SPECIALIZED_WRAPPER_COMMANDS, PROXIED_COMMANDS_SET;
534
+ var init_command_sync = __esm({
535
+ "libs/shield-daemon/src/command-sync.ts"() {
536
+ "use strict";
537
+ noop2 = { warn() {
538
+ }, info() {
539
+ } };
540
+ ALLOWED_COMMANDS_PATH = "/opt/agenshield/config/allowed-commands.json";
541
+ BIN_SEARCH_DIRS = [
542
+ "/usr/bin",
543
+ "/usr/local/bin",
544
+ "/opt/homebrew/bin",
545
+ "/usr/sbin",
546
+ "/usr/local/sbin",
547
+ "/opt/homebrew/sbin"
548
+ ];
549
+ ALL_PROXIED_COMMANDS = [...PROXIED_COMMANDS];
550
+ BASIC_SYSTEM_COMMANDS_SET = new Set(BASIC_SYSTEM_COMMANDS);
551
+ SPECIALIZED_WRAPPER_COMMANDS = /* @__PURE__ */ new Set(["node", "python", "python3"]);
552
+ PROXIED_COMMANDS_SET = new Set(ALL_PROXIED_COMMANDS);
553
+ }
554
+ });
555
+
556
+ // libs/shield-daemon/src/main.ts
557
+ import * as fs20 from "node:fs";
558
+
559
+ // libs/shield-daemon/src/config/index.ts
560
+ init_paths();
561
+ init_defaults();
562
+ init_loader();
563
+
564
+ // libs/shield-daemon/src/server.ts
565
+ import * as fs19 from "node:fs";
566
+ import Fastify from "fastify";
567
+ import cors from "@fastify/cors";
568
+ import fastifyStatic from "@fastify/static";
569
+
570
+ // libs/shield-daemon/src/routes/index.ts
571
+ import { API_PREFIX } from "@agenshield/ipc";
572
+
573
+ // libs/shield-daemon/src/routes/health.ts
574
+ async function healthRoutes(app) {
575
+ app.get("/health", async () => {
576
+ return {
577
+ success: true,
578
+ data: {
579
+ ok: true,
580
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
581
+ mode: "daemon"
582
+ }
583
+ };
584
+ });
585
+ }
188
586
 
189
587
  // libs/shield-daemon/src/routes/status.ts
588
+ init_state();
190
589
  var startedAt = /* @__PURE__ */ new Date();
191
590
  async function statusRoutes(app) {
192
591
  app.get("/status", async () => {
@@ -214,7 +613,8 @@ async function statusRoutes(app) {
214
613
 
215
614
  // libs/shield-daemon/src/routes/config.ts
216
615
  import * as fs6 from "node:fs";
217
- import * as path5 from "node:path";
616
+ import * as path6 from "node:path";
617
+ init_state();
218
618
 
219
619
  // libs/shield-daemon/src/vault/index.ts
220
620
  import * as fs3 from "node:fs";
@@ -275,6 +675,7 @@ function decrypt(encryptedData, key) {
275
675
  }
276
676
 
277
677
  // libs/shield-daemon/src/vault/index.ts
678
+ init_paths();
278
679
  var Vault = class {
279
680
  key;
280
681
  vaultPath;
@@ -518,8 +919,30 @@ function getSessionManager() {
518
919
  import { execSync as execSync2 } from "node:child_process";
519
920
  import * as os3 from "node:os";
520
921
  import * as fs4 from "node:fs";
922
+ import * as path4 from "node:path";
521
923
  var noop = { warn() {
522
924
  } };
925
+ var TRAVERSAL_PERMS = "search";
926
+ var WORLD_TRAVERSABLE_PATHS = /* @__PURE__ */ new Set([
927
+ "/",
928
+ "/Users",
929
+ "/tmp",
930
+ "/private",
931
+ "/private/tmp",
932
+ "/private/var",
933
+ "/var",
934
+ "/opt",
935
+ "/usr",
936
+ "/usr/local",
937
+ "/Applications",
938
+ "/Library",
939
+ "/System",
940
+ "/Volumes"
941
+ ]);
942
+ function normalizePath(p) {
943
+ if (p === "/") return p;
944
+ return p.replace(/\/+$/, "") || "/";
945
+ }
523
946
  function stripGlobToBasePath(pattern) {
524
947
  let p = pattern;
525
948
  if (p.startsWith("~")) {
@@ -531,7 +954,7 @@ function stripGlobToBasePath(pattern) {
531
954
  if (/[*?[]/.test(seg)) break;
532
955
  base.push(seg);
533
956
  }
534
- return base.length === 0 ? "/" : base.join("/") || "/";
957
+ return normalizePath(base.length === 0 ? "/" : base.join("/") || "/");
535
958
  }
536
959
  function operationsToAclPerms(operations) {
537
960
  const perms = [];
@@ -589,197 +1012,69 @@ function removeGroupAcl(targetPath, groupName, log = noop) {
589
1012
  log.warn(`[acl] failed to read ACLs on ${targetPath}: ${err.message}`);
590
1013
  }
591
1014
  }
592
- function syncFilesystemPolicyAcls(oldPolicies, newPolicies, groupName, logger) {
593
- const log = logger ?? noop;
594
- const oldFs = oldPolicies.filter((p) => p.target === "filesystem");
595
- const newFs = newPolicies.filter((p) => p.target === "filesystem");
596
- const oldMap = new Map(oldFs.map((p) => [p.id, p]));
597
- const newMap = new Map(newFs.map((p) => [p.id, p]));
598
- for (const [id, oldP] of oldMap) {
599
- if (!newMap.has(id)) {
600
- for (const pattern of oldP.patterns) {
601
- removeGroupAcl(stripGlobToBasePath(pattern), groupName, log);
602
- }
603
- }
604
- }
605
- for (const [id, newP] of newMap) {
606
- if (!oldMap.has(id)) {
607
- const perms = operationsToAclPerms(newP.operations ?? []);
608
- if (!perms) continue;
609
- for (const pattern of newP.patterns) {
610
- addGroupAcl(stripGlobToBasePath(pattern), groupName, perms, log);
611
- }
612
- }
613
- }
614
- for (const [id, newP] of newMap) {
615
- const oldP = oldMap.get(id);
616
- if (!oldP) continue;
617
- const patternsChanged = JSON.stringify(oldP.patterns) !== JSON.stringify(newP.patterns);
618
- const opsChanged = JSON.stringify(oldP.operations ?? []) !== JSON.stringify(newP.operations ?? []);
619
- if (patternsChanged || opsChanged) {
620
- for (const pattern of oldP.patterns) {
621
- removeGroupAcl(stripGlobToBasePath(pattern), groupName, log);
622
- }
623
- const perms = operationsToAclPerms(newP.operations ?? []);
624
- if (!perms) continue;
625
- for (const pattern of newP.patterns) {
626
- addGroupAcl(stripGlobToBasePath(pattern), groupName, perms, log);
627
- }
628
- }
629
- }
630
- }
631
-
632
- // libs/shield-daemon/src/command-sync.ts
633
- import * as fs5 from "node:fs";
634
- import * as path4 from "node:path";
635
- import { execSync as execSync3 } from "node:child_process";
636
- var noop2 = { warn() {
637
- }, info() {
638
- } };
639
- var ALLOWED_COMMANDS_PATH = "/opt/agenshield/config/allowed-commands.json";
640
- var BIN_SEARCH_DIRS = [
641
- "/usr/bin",
642
- "/usr/local/bin",
643
- "/opt/homebrew/bin",
644
- "/usr/sbin",
645
- "/usr/local/sbin",
646
- "/opt/homebrew/sbin"
647
- ];
648
- var ALL_PROXIED_COMMANDS = [
649
- "curl",
650
- "wget",
651
- "git",
652
- "ssh",
653
- "scp",
654
- "rsync",
655
- "brew",
656
- "npm",
657
- "npx",
658
- "pip",
659
- "pip3",
660
- "open-url",
661
- "shieldctl",
662
- "agenco"
663
- ];
664
- function resolveCommandPaths(name) {
665
- const paths = [];
666
- for (const dir of BIN_SEARCH_DIRS) {
667
- const candidate = path4.join(dir, name);
668
- try {
669
- if (fs5.existsSync(candidate)) {
670
- const stat = fs5.statSync(candidate);
671
- if (stat.isFile() && (stat.mode & 73) !== 0) {
672
- paths.push(candidate);
673
- }
674
- }
675
- } catch {
1015
+ function getAncestorsNeedingTraversal(targetPath) {
1016
+ const ancestors = [];
1017
+ let dir = path4.dirname(targetPath);
1018
+ while (dir !== targetPath && dir !== "/") {
1019
+ if (!WORLD_TRAVERSABLE_PATHS.has(dir)) {
1020
+ ancestors.push(dir);
676
1021
  }
1022
+ targetPath = dir;
1023
+ dir = path4.dirname(dir);
677
1024
  }
678
- if (paths.length === 0) {
679
- try {
680
- const result = execSync3(`which ${name} 2>/dev/null`, { encoding: "utf-8" }).trim();
681
- if (result && path4.isAbsolute(result)) {
682
- paths.push(result);
683
- }
684
- } catch {
685
- }
686
- }
687
- return paths;
1025
+ return ancestors;
688
1026
  }
689
- function extractCommandName(pattern) {
690
- return pattern.trim().split(/\s+/)[0];
1027
+ function mergePerms(a, b) {
1028
+ const set = /* @__PURE__ */ new Set([
1029
+ ...a.split(",").filter(Boolean),
1030
+ ...b.split(",").filter(Boolean)
1031
+ ]);
1032
+ return [...set].join(",");
691
1033
  }
692
- function syncCommandPolicies(policies, logger) {
693
- const log = logger ?? noop2;
694
- const commandPolicies = policies.filter(
695
- (p) => p.target === "command" && p.action === "allow" && p.enabled
696
- );
697
- const commandNames = /* @__PURE__ */ new Set();
698
- for (const policy of commandPolicies) {
1034
+ function computeAclMap(policies) {
1035
+ const aclMap = /* @__PURE__ */ new Map();
1036
+ for (const policy of policies.filter((p) => p.action === "allow")) {
1037
+ const perms = operationsToAclPerms(policy.operations ?? []);
1038
+ if (!perms) continue;
699
1039
  for (const pattern of policy.patterns) {
700
- const name = extractCommandName(pattern);
701
- if (name) commandNames.add(name);
702
- }
703
- }
704
- const commands = [];
705
- const now = (/* @__PURE__ */ new Date()).toISOString();
706
- for (const name of commandNames) {
707
- const paths = resolveCommandPaths(name);
708
- if (paths.length === 0) {
709
- log.warn(`[command-sync] command '${name}' not found on system, adding without paths`);
710
- }
711
- commands.push({
712
- name,
713
- paths,
714
- addedAt: now,
715
- addedBy: "policy",
716
- category: "policy-managed"
717
- });
718
- }
719
- const config = {
720
- version: "1.0.0",
721
- commands
722
- };
723
- try {
724
- const dir = path4.dirname(ALLOWED_COMMANDS_PATH);
725
- if (!fs5.existsSync(dir)) {
726
- fs5.mkdirSync(dir, { recursive: true });
727
- }
728
- fs5.writeFileSync(ALLOWED_COMMANDS_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
729
- log.info(`[command-sync] wrote ${commands.length} commands to allowlist`);
730
- } catch (err) {
731
- log.warn(`[command-sync] failed to write allowlist: ${err.message}`);
732
- }
733
- }
734
- function installWrappersInDir(binDir, log) {
735
- const shieldExecPath = "/opt/agenshield/bin/shield-exec";
736
- if (!fs5.existsSync(binDir)) {
737
- try {
738
- fs5.mkdirSync(binDir, { recursive: true, mode: 493 });
739
- } catch (err) {
740
- log.warn(`[command-sync] failed to create bin dir ${binDir}: ${err.message}`);
741
- return;
1040
+ const target = stripGlobToBasePath(pattern);
1041
+ const existing = aclMap.get(target);
1042
+ aclMap.set(target, existing ? mergePerms(existing, perms) : perms);
742
1043
  }
743
1044
  }
744
- for (const cmd of ALL_PROXIED_COMMANDS) {
745
- const wrapperPath = path4.join(binDir, cmd);
746
- if (fs5.existsSync(wrapperPath)) {
747
- continue;
748
- }
749
- try {
750
- if (fs5.existsSync(shieldExecPath)) {
751
- fs5.symlinkSync(shieldExecPath, wrapperPath);
752
- } else {
753
- log.warn(`[command-sync] shield-exec not found at ${shieldExecPath}, skipping ${cmd} wrapper`);
1045
+ for (const policy of policies.filter((p) => p.action === "allow")) {
1046
+ const perms = operationsToAclPerms(policy.operations ?? []);
1047
+ if (!perms) continue;
1048
+ for (const pattern of policy.patterns) {
1049
+ const target = stripGlobToBasePath(pattern);
1050
+ for (const ancestor of getAncestorsNeedingTraversal(target)) {
1051
+ if (!aclMap.has(ancestor)) {
1052
+ aclMap.set(ancestor, TRAVERSAL_PERMS);
1053
+ }
754
1054
  }
755
- } catch (err) {
756
- log.warn(`[command-sync] failed to create wrapper ${wrapperPath}: ${err.message}`);
757
1055
  }
758
1056
  }
1057
+ return aclMap;
759
1058
  }
760
- function ensureWrappersInstalled(state, logger) {
761
- const log = logger ?? noop2;
762
- const agentUser = state.users.find((u) => u.type === "agent");
763
- if (!agentUser) {
764
- log.warn("[command-sync] no agent user in state, skipping wrapper installation");
765
- return;
1059
+ function syncFilesystemPolicyAcls(oldPolicies, newPolicies, groupName, logger) {
1060
+ const log = logger ?? noop;
1061
+ const oldFs = oldPolicies.filter((p) => p.target === "filesystem");
1062
+ const newFs = newPolicies.filter((p) => p.target === "filesystem");
1063
+ const oldAclMap = computeAclMap(oldFs);
1064
+ const newAclMap = computeAclMap(newFs);
1065
+ for (const oldPath of oldAclMap.keys()) {
1066
+ if (!newAclMap.has(oldPath)) {
1067
+ removeGroupAcl(oldPath, groupName, log);
1068
+ }
766
1069
  }
767
- const agentBinDir = path4.join(agentUser.homeDir, "bin");
768
- log.info(`[command-sync] ensuring wrappers in agent bin: ${agentBinDir}`);
769
- installWrappersInDir(agentBinDir, log);
770
- const agentHomeEnv = process.env["AGENSHIELD_AGENT_HOME"];
771
- if (agentHomeEnv && agentHomeEnv !== agentUser.homeDir) {
772
- const envBinDir = path4.join(agentHomeEnv, "bin");
773
- log.info(`[command-sync] ensuring wrappers in env agent bin: ${envBinDir}`);
774
- installWrappersInDir(envBinDir, log);
1070
+ for (const [targetPath, perms] of newAclMap) {
1071
+ addGroupAcl(targetPath, groupName, perms, log);
775
1072
  }
776
1073
  }
777
- function syncCommandPoliciesAndWrappers(policies, state, logger) {
778
- syncCommandPolicies(policies, logger);
779
- ensureWrappersInstalled(state, logger);
780
- }
781
1074
 
782
1075
  // libs/shield-daemon/src/routes/config.ts
1076
+ init_command_sync();
1077
+ import { installShieldExec, createUserConfig } from "@agenshield/sandbox";
783
1078
  async function configRoutes(app) {
784
1079
  app.get("/config", async () => {
785
1080
  const config = loadConfig();
@@ -841,9 +1136,31 @@ async function configRoutes(app) {
841
1136
  };
842
1137
  }
843
1138
  });
1139
+ app.post("/config/install-wrappers", async () => {
1140
+ try {
1141
+ const state = loadState();
1142
+ const agentUser = state.users.find((u) => u.type === "agent");
1143
+ if (!agentUser) {
1144
+ return { success: false, error: "No agent user found in state" };
1145
+ }
1146
+ const userConfig = createUserConfig();
1147
+ const binDir = path6.join(agentUser.homeDir, "bin");
1148
+ const result = await installShieldExec(userConfig, binDir);
1149
+ return {
1150
+ success: result.success,
1151
+ installed: result.installed,
1152
+ error: result.error
1153
+ };
1154
+ } catch (error) {
1155
+ return {
1156
+ success: false,
1157
+ error: error instanceof Error ? error.message : "Failed to install wrappers"
1158
+ };
1159
+ }
1160
+ });
844
1161
  app.get("/config/openclaw", async (_request, reply) => {
845
1162
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
846
- const configDir = path5.join(agentHome, ".openclaw");
1163
+ const configDir = path6.join(agentHome, ".openclaw");
847
1164
  const configFiles = readConfigDir(configDir);
848
1165
  return reply.send({ configDir, files: configFiles });
849
1166
  });
@@ -855,7 +1172,7 @@ async function configRoutes(app) {
855
1172
  return reply.code(400).send({ error: "original query param required" });
856
1173
  }
857
1174
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
858
- const agentConfigDir = path5.join(agentHome, ".openclaw");
1175
+ const agentConfigDir = path6.join(agentHome, ".openclaw");
859
1176
  const diff = diffConfigDirs(original, agentConfigDir);
860
1177
  return reply.send({ diff });
861
1178
  }
@@ -877,13 +1194,13 @@ function readConfigDir(dir, base) {
877
1194
  for (const entry of entries) {
878
1195
  if (entry.isDirectory()) {
879
1196
  if (SKIP_DIRS.has(entry.name)) continue;
880
- const sub = readConfigDir(path5.join(dir, entry.name), root);
1197
+ const sub = readConfigDir(path6.join(dir, entry.name), root);
881
1198
  Object.assign(result, sub);
882
1199
  } else if (entry.isFile()) {
883
- const ext = path5.extname(entry.name).toLowerCase();
1200
+ const ext = path6.extname(entry.name).toLowerCase();
884
1201
  if ([".json", ".yaml", ".yml", ".toml", ".txt", ".md", ".conf", ""].includes(ext)) {
885
- const filePath = path5.join(dir, entry.name);
886
- const relPath = path5.relative(root, filePath);
1202
+ const filePath = path6.join(dir, entry.name);
1203
+ const relPath = path6.relative(root, filePath);
887
1204
  try {
888
1205
  result[relPath] = fs6.readFileSync(filePath, "utf-8");
889
1206
  } catch {
@@ -1005,10 +1322,10 @@ function emitSecurityWarning(warning) {
1005
1322
  function emitSecurityCritical(issue) {
1006
1323
  daemonEvents.broadcast("security:critical", { message: issue });
1007
1324
  }
1008
- function emitApiRequest(method, path18, statusCode, duration, requestBody, responseBody) {
1325
+ function emitApiRequest(method, path19, statusCode, duration, requestBody, responseBody) {
1009
1326
  daemonEvents.broadcast("api:request", {
1010
1327
  method,
1011
- path: path18,
1328
+ path: path19,
1012
1329
  statusCode,
1013
1330
  duration,
1014
1331
  ...requestBody !== void 0 && { requestBody },
@@ -1051,6 +1368,7 @@ function emitEvent(type, data) {
1051
1368
 
1052
1369
  // libs/shield-daemon/src/auth/passcode.ts
1053
1370
  import * as crypto3 from "node:crypto";
1371
+ init_state();
1054
1372
  import { DEFAULT_AUTH_CONFIG as DEFAULT_AUTH_CONFIG2 } from "@agenshield/ipc";
1055
1373
  var ITERATIONS = 1e5;
1056
1374
  var KEY_LENGTH = 64;
@@ -1215,16 +1533,16 @@ var PROTECTED_ROUTES = [
1215
1533
  { method: "POST", path: "/api/config/factory-reset" },
1216
1534
  { method: "POST", path: "/api/skills/install" }
1217
1535
  ];
1218
- function isProtectedRoute(method, path18) {
1536
+ function isProtectedRoute(method, path19) {
1219
1537
  return PROTECTED_ROUTES.some(
1220
- (route) => route.method === method && path18.startsWith(route.path)
1538
+ (route) => route.method === method && path19.startsWith(route.path)
1221
1539
  );
1222
1540
  }
1223
1541
  function createAuthHook() {
1224
1542
  return async (request, reply) => {
1225
1543
  const method = request.method;
1226
- const path18 = request.url.split("?")[0];
1227
- if (!isProtectedRoute(method, path18)) {
1544
+ const path19 = request.url.split("?")[0];
1545
+ if (!isProtectedRoute(method, path19)) {
1228
1546
  return;
1229
1547
  }
1230
1548
  if (!isAuthenticated(request)) {
@@ -1731,6 +2049,9 @@ async function loggedFetch(url, init, context) {
1731
2049
  }
1732
2050
  }
1733
2051
 
2052
+ // libs/shield-daemon/src/routes/agenco.ts
2053
+ init_state();
2054
+
1734
2055
  // libs/shield-daemon/src/mcp/client.ts
1735
2056
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1736
2057
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -2057,7 +2378,7 @@ function getMCPState() {
2057
2378
 
2058
2379
  // libs/shield-daemon/src/services/integration-skills.ts
2059
2380
  import * as fs8 from "node:fs";
2060
- import * as path7 from "node:path";
2381
+ import * as path8 from "node:path";
2061
2382
 
2062
2383
  // libs/shield-skills/dist/index.js
2063
2384
  import * as path22 from "node:path";
@@ -2123,7 +2444,7 @@ var BUILTIN_SKILLS_DIR = path22.resolve(__skills_dirname, "..", "skills");
2123
2444
 
2124
2445
  // libs/shield-daemon/src/watchers/skills.ts
2125
2446
  import * as fs7 from "node:fs";
2126
- import * as path6 from "node:path";
2447
+ import * as path7 from "node:path";
2127
2448
  import { execSync as execSync4 } from "node:child_process";
2128
2449
  var APPROVED_SKILLS_PATH = "/opt/agenshield/config/approved-skills.json";
2129
2450
  var QUARANTINE_DIR = "/opt/agenshield/quarantine/skills";
@@ -2145,7 +2466,7 @@ function loadApprovedSkills() {
2145
2466
  }
2146
2467
  function saveApprovedSkills(skills) {
2147
2468
  try {
2148
- const dir = path6.dirname(APPROVED_SKILLS_PATH);
2469
+ const dir = path7.dirname(APPROVED_SKILLS_PATH);
2149
2470
  fs7.mkdirSync(dir, { recursive: true });
2150
2471
  const content = JSON.stringify(skills, null, 2);
2151
2472
  fs7.writeFileSync(APPROVED_SKILLS_PATH, content, "utf-8");
@@ -2159,7 +2480,7 @@ function isApproved(skillName) {
2159
2480
  }
2160
2481
  function quarantineSkill(skillName, skillPath) {
2161
2482
  try {
2162
- const quarantinePath = path6.join(QUARANTINE_DIR, skillName);
2483
+ const quarantinePath = path7.join(QUARANTINE_DIR, skillName);
2163
2484
  if (!fs7.existsSync(QUARANTINE_DIR)) {
2164
2485
  fs7.mkdirSync(QUARANTINE_DIR, { recursive: true, mode: 448 });
2165
2486
  }
@@ -2192,7 +2513,7 @@ function scanSkills() {
2192
2513
  if (!entry.isDirectory()) continue;
2193
2514
  const skillName = entry.name;
2194
2515
  if (!isApproved(skillName)) {
2195
- const fullPath = path6.join(skillsDir, skillName);
2516
+ const fullPath = path7.join(skillsDir, skillName);
2196
2517
  const info = quarantineSkill(skillName, fullPath);
2197
2518
  if (info && callbacks.onQuarantined) {
2198
2519
  callbacks.onQuarantined(info);
@@ -2258,8 +2579,8 @@ function stopSkillsWatcher() {
2258
2579
  }
2259
2580
  function approveSkill(skillName) {
2260
2581
  try {
2261
- const quarantinedPath = path6.join(QUARANTINE_DIR, skillName);
2262
- const destPath = path6.join(skillsDir, skillName);
2582
+ const quarantinedPath = path7.join(QUARANTINE_DIR, skillName);
2583
+ const destPath = path7.join(skillsDir, skillName);
2263
2584
  if (!fs7.existsSync(quarantinedPath)) {
2264
2585
  return { success: false, error: `Skill "${skillName}" not found in quarantine` };
2265
2586
  }
@@ -2285,7 +2606,7 @@ function approveSkill(skillName) {
2285
2606
  }
2286
2607
  function rejectSkill(skillName) {
2287
2608
  try {
2288
- const quarantinedPath = path6.join(QUARANTINE_DIR, skillName);
2609
+ const quarantinedPath = path7.join(QUARANTINE_DIR, skillName);
2289
2610
  if (!fs7.existsSync(quarantinedPath)) {
2290
2611
  return { success: false, error: `Skill "${skillName}" not found in quarantine` };
2291
2612
  }
@@ -2301,7 +2622,7 @@ function revokeSkill(skillName) {
2301
2622
  const approved = loadApprovedSkills();
2302
2623
  const filtered = approved.filter((s) => s.name !== skillName);
2303
2624
  saveApprovedSkills(filtered);
2304
- const skillPath = path6.join(skillsDir, skillName);
2625
+ const skillPath = path7.join(skillsDir, skillName);
2305
2626
  if (fs7.existsSync(skillPath)) {
2306
2627
  quarantineSkill(skillName, skillPath);
2307
2628
  }
@@ -2320,11 +2641,11 @@ function listQuarantined() {
2320
2641
  const entries = fs7.readdirSync(QUARANTINE_DIR, { withFileTypes: true });
2321
2642
  for (const entry of entries) {
2322
2643
  if (entry.isDirectory()) {
2323
- const stat = fs7.statSync(path6.join(QUARANTINE_DIR, entry.name));
2644
+ const stat = fs7.statSync(path7.join(QUARANTINE_DIR, entry.name));
2324
2645
  results.push({
2325
2646
  name: entry.name,
2326
2647
  quarantinedAt: stat.mtime.toISOString(),
2327
- originalPath: path6.join(skillsDir, entry.name),
2648
+ originalPath: path7.join(skillsDir, entry.name),
2328
2649
  reason: "Skill not in approved list"
2329
2650
  });
2330
2651
  }
@@ -2364,8 +2685,8 @@ function copyDirSync(src, dest) {
2364
2685
  fs8.mkdirSync(dest, { recursive: true });
2365
2686
  const entries = fs8.readdirSync(src, { withFileTypes: true });
2366
2687
  for (const entry of entries) {
2367
- const srcPath = path7.join(src, entry.name);
2368
- const destPath = path7.join(dest, entry.name);
2688
+ const srcPath = path8.join(src, entry.name);
2689
+ const destPath = path8.join(dest, entry.name);
2369
2690
  if (entry.isDirectory()) {
2370
2691
  copyDirSync(srcPath, destPath);
2371
2692
  } else {
@@ -2379,8 +2700,8 @@ async function provisionAgenCoSkill() {
2379
2700
  console.warn("[IntegrationSkills] Skills directory not configured \u2014 skipping provision");
2380
2701
  return { installed: false };
2381
2702
  }
2382
- const destDir = path7.join(skillsDir2, AGENCO_SKILL_NAME);
2383
- const srcDir = path7.join(BUILTIN_SKILLS_DIR, AGENCO_SKILL_NAME);
2703
+ const destDir = path8.join(skillsDir2, AGENCO_SKILL_NAME);
2704
+ const srcDir = path8.join(BUILTIN_SKILLS_DIR, AGENCO_SKILL_NAME);
2384
2705
  if (fs8.existsSync(destDir)) {
2385
2706
  return { installed: false };
2386
2707
  }
@@ -2408,8 +2729,8 @@ async function provisionIntegrationSkill(integrationSlug) {
2408
2729
  return { installed: false };
2409
2730
  }
2410
2731
  const skillName = `integration-${integrationSlug}`;
2411
- const destDir = path7.join(skillsDir2, skillName);
2412
- const srcDir = path7.join(BUILTIN_SKILLS_DIR, skillName);
2732
+ const destDir = path8.join(skillsDir2, skillName);
2733
+ const srcDir = path8.join(BUILTIN_SKILLS_DIR, skillName);
2413
2734
  if (fs8.existsSync(destDir)) {
2414
2735
  return { installed: false };
2415
2736
  }
@@ -4949,13 +5270,13 @@ async function agencoRoutes(app) {
4949
5270
 
4950
5271
  // libs/shield-daemon/src/routes/skills.ts
4951
5272
  import * as fs13 from "node:fs";
4952
- import * as path12 from "node:path";
5273
+ import * as path13 from "node:path";
4953
5274
  import { execSync as execSync8 } from "node:child_process";
4954
5275
  import { parseSkillMd } from "@agenshield/sandbox";
4955
5276
 
4956
5277
  // libs/shield-daemon/src/services/skill-analyzer.ts
4957
5278
  import * as fs9 from "node:fs";
4958
- import * as path8 from "node:path";
5279
+ import * as path9 from "node:path";
4959
5280
  import { execSync as execSync5 } from "node:child_process";
4960
5281
  var ANALYSIS_CACHE_PATH = "/opt/agenshield/config/skill-analyses.json";
4961
5282
  var COMMAND_PATTERNS = [
@@ -5026,7 +5347,7 @@ function loadCache() {
5026
5347
  }
5027
5348
  function saveCache(cache3) {
5028
5349
  try {
5029
- const dir = path8.dirname(ANALYSIS_CACHE_PATH);
5350
+ const dir = path9.dirname(ANALYSIS_CACHE_PATH);
5030
5351
  if (!fs9.existsSync(dir)) {
5031
5352
  fs9.mkdirSync(dir, { recursive: true });
5032
5353
  }
@@ -5173,13 +5494,13 @@ function clearCachedAnalysis(skillName) {
5173
5494
 
5174
5495
  // libs/shield-daemon/src/services/skill-lifecycle.ts
5175
5496
  import * as fs10 from "node:fs";
5176
- import * as path9 from "node:path";
5497
+ import * as path10 from "node:path";
5177
5498
  import { execSync as execSync6 } from "node:child_process";
5178
5499
  function createSkillWrapper(name, binDir) {
5179
5500
  if (!fs10.existsSync(binDir)) {
5180
5501
  fs10.mkdirSync(binDir, { recursive: true });
5181
5502
  }
5182
- const wrapperPath = path9.join(binDir, name);
5503
+ const wrapperPath = path10.join(binDir, name);
5183
5504
  const wrapperContent = `#!/bin/bash
5184
5505
  # ${name} skill wrapper - policy-enforced execution
5185
5506
  # Ensure accessible working directory
@@ -5195,7 +5516,7 @@ exec /opt/agenshield/bin/shield-client skill run "${name}" "$@"
5195
5516
  }
5196
5517
  }
5197
5518
  function removeSkillWrapper(name, binDir) {
5198
- const wrapperPath = path9.join(binDir, name);
5519
+ const wrapperPath = path10.join(binDir, name);
5199
5520
  try {
5200
5521
  if (fs10.existsSync(wrapperPath)) {
5201
5522
  fs10.unlinkSync(wrapperPath);
@@ -5232,11 +5553,11 @@ function removeSkillPolicy(name) {
5232
5553
 
5233
5554
  // libs/shield-daemon/src/services/openclaw-config.ts
5234
5555
  import * as fs11 from "node:fs";
5235
- import * as path10 from "node:path";
5556
+ import * as path11 from "node:path";
5236
5557
  import { execSync as execSync7 } from "node:child_process";
5237
5558
  function getOpenClawConfigPath() {
5238
5559
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
5239
- return path10.join(agentHome, ".openclaw", "openclaw.json");
5560
+ return path11.join(agentHome, ".openclaw", "openclaw.json");
5240
5561
  }
5241
5562
  function readConfig() {
5242
5563
  const configPath = getOpenClawConfigPath();
@@ -5251,10 +5572,10 @@ function readConfig() {
5251
5572
  }
5252
5573
  function writeConfig(config) {
5253
5574
  const configPath = getOpenClawConfigPath();
5254
- fs11.mkdirSync(path10.dirname(configPath), { recursive: true });
5575
+ fs11.mkdirSync(path11.dirname(configPath), { recursive: true });
5255
5576
  fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
5256
5577
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
5257
- const brokerUser = path10.basename(agentHome) + "_broker";
5578
+ const brokerUser = path11.basename(agentHome) + "_broker";
5258
5579
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield";
5259
5580
  try {
5260
5581
  execSync7(`chown ${brokerUser}:${socketGroup} "${configPath}"`, { stdio: "pipe" });
@@ -5288,7 +5609,7 @@ function removeSkillEntry(slug) {
5288
5609
 
5289
5610
  // libs/shield-daemon/src/services/marketplace.ts
5290
5611
  import * as fs12 from "node:fs";
5291
- import * as path11 from "node:path";
5612
+ import * as path12 from "node:path";
5292
5613
  import * as os4 from "node:os";
5293
5614
  import { CONFIG_DIR as CONFIG_DIR2, MARKETPLACE_DIR } from "@agenshield/ipc";
5294
5615
  var cache = /* @__PURE__ */ new Map();
@@ -5312,35 +5633,35 @@ var ANALYSIS_TIMEOUT = 4 * 6e4;
5312
5633
  var SEARCH_CACHE_TTL = 6e4;
5313
5634
  var DETAIL_CACHE_TTL = 5 * 6e4;
5314
5635
  var SHORT_TIMEOUT = 1e4;
5315
- async function convexAction(path18, args, timeout) {
5636
+ async function convexAction(path19, args, timeout) {
5316
5637
  const res = await fetch(`${CONVEX_BASE}/api/action`, {
5317
5638
  method: "POST",
5318
5639
  signal: AbortSignal.timeout(timeout),
5319
5640
  headers: { "Content-Type": "application/json" },
5320
- body: JSON.stringify({ path: path18, args, format: "json" })
5641
+ body: JSON.stringify({ path: path19, args, format: "json" })
5321
5642
  });
5322
5643
  if (!res.ok) {
5323
- throw new Error(`Convex action ${path18} returned ${res.status}`);
5644
+ throw new Error(`Convex action ${path19} returned ${res.status}`);
5324
5645
  }
5325
5646
  const body = await res.json();
5326
5647
  if (body.status === "error") {
5327
- throw new Error(`Convex action ${path18}: ${body.errorMessage ?? "unknown error"}`);
5648
+ throw new Error(`Convex action ${path19}: ${body.errorMessage ?? "unknown error"}`);
5328
5649
  }
5329
5650
  return body.value;
5330
5651
  }
5331
- async function convexQuery(path18, args, timeout) {
5652
+ async function convexQuery(path19, args, timeout) {
5332
5653
  const res = await fetch(`${CONVEX_BASE}/api/query`, {
5333
5654
  method: "POST",
5334
5655
  signal: AbortSignal.timeout(timeout),
5335
5656
  headers: { "Content-Type": "application/json" },
5336
- body: JSON.stringify({ path: path18, args, format: "json" })
5657
+ body: JSON.stringify({ path: path19, args, format: "json" })
5337
5658
  });
5338
5659
  if (!res.ok) {
5339
- throw new Error(`Convex query ${path18} returned ${res.status}`);
5660
+ throw new Error(`Convex query ${path19} returned ${res.status}`);
5340
5661
  }
5341
5662
  const body = await res.json();
5342
5663
  if (body.status === "error") {
5343
- throw new Error(`Convex query ${path18}: ${body.errorMessage ?? "unknown error"}`);
5664
+ throw new Error(`Convex query ${path19}: ${body.errorMessage ?? "unknown error"}`);
5344
5665
  }
5345
5666
  return body.value;
5346
5667
  }
@@ -5401,7 +5722,7 @@ function isImageExt(filePath) {
5401
5722
  }
5402
5723
  var MAX_IMAGE_SIZE = 5e5;
5403
5724
  function getMarketplaceDir() {
5404
- return path11.join(os4.homedir(), CONFIG_DIR2, MARKETPLACE_DIR);
5725
+ return path12.join(os4.homedir(), CONFIG_DIR2, MARKETPLACE_DIR);
5405
5726
  }
5406
5727
  async function downloadAndExtractZip(slug) {
5407
5728
  const url = `${CLAWHUB_DOWNLOAD_BASE}/download?slug=${encodeURIComponent(slug)}`;
@@ -5439,20 +5760,20 @@ async function downloadAndExtractZip(slug) {
5439
5760
  return files;
5440
5761
  }
5441
5762
  function storeDownloadedSkill(slug, meta, files) {
5442
- const dir = path11.join(getMarketplaceDir(), slug);
5443
- const filesDir = path11.join(dir, "files");
5763
+ const dir = path12.join(getMarketplaceDir(), slug);
5764
+ const filesDir = path12.join(dir, "files");
5444
5765
  fs12.mkdirSync(filesDir, { recursive: true });
5445
5766
  const fullMeta = { ...meta, downloadedAt: (/* @__PURE__ */ new Date()).toISOString() };
5446
- fs12.writeFileSync(path11.join(dir, "metadata.json"), JSON.stringify(fullMeta, null, 2), "utf-8");
5767
+ fs12.writeFileSync(path12.join(dir, "metadata.json"), JSON.stringify(fullMeta, null, 2), "utf-8");
5447
5768
  for (const file of files) {
5448
- const filePath = path11.join(filesDir, file.name);
5449
- fs12.mkdirSync(path11.dirname(filePath), { recursive: true });
5769
+ const filePath = path12.join(filesDir, file.name);
5770
+ fs12.mkdirSync(path12.dirname(filePath), { recursive: true });
5450
5771
  fs12.writeFileSync(filePath, file.content, "utf-8");
5451
5772
  }
5452
5773
  console.log(`[Marketplace] Stored ${files.length} files for ${slug}`);
5453
5774
  }
5454
5775
  function updateDownloadedAnalysis(slug, analysis) {
5455
- const metaPath = path11.join(getMarketplaceDir(), slug, "metadata.json");
5776
+ const metaPath = path12.join(getMarketplaceDir(), slug, "metadata.json");
5456
5777
  try {
5457
5778
  if (!fs12.existsSync(metaPath)) return;
5458
5779
  const meta = JSON.parse(fs12.readFileSync(metaPath, "utf-8"));
@@ -5469,7 +5790,7 @@ function listDownloadedSkills() {
5469
5790
  const entries = fs12.readdirSync(baseDir, { withFileTypes: true });
5470
5791
  for (const entry of entries) {
5471
5792
  if (!entry.isDirectory()) continue;
5472
- const metaPath = path11.join(baseDir, entry.name, "metadata.json");
5793
+ const metaPath = path12.join(baseDir, entry.name, "metadata.json");
5473
5794
  try {
5474
5795
  const meta = JSON.parse(fs12.readFileSync(metaPath, "utf-8"));
5475
5796
  results.push({
@@ -5488,7 +5809,7 @@ function listDownloadedSkills() {
5488
5809
  return results;
5489
5810
  }
5490
5811
  function getDownloadedSkillFiles(slug) {
5491
- const filesDir = path11.join(getMarketplaceDir(), slug, "files");
5812
+ const filesDir = path12.join(getMarketplaceDir(), slug, "files");
5492
5813
  if (!fs12.existsSync(filesDir)) return [];
5493
5814
  const files = [];
5494
5815
  function walk(dir, prefix) {
@@ -5496,9 +5817,9 @@ function getDownloadedSkillFiles(slug) {
5496
5817
  for (const entry of entries) {
5497
5818
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
5498
5819
  if (entry.isDirectory()) {
5499
- walk(path11.join(dir, entry.name), rel);
5820
+ walk(path12.join(dir, entry.name), rel);
5500
5821
  } else {
5501
- const content = fs12.readFileSync(path11.join(dir, entry.name), "utf-8");
5822
+ const content = fs12.readFileSync(path12.join(dir, entry.name), "utf-8");
5502
5823
  files.push({ name: rel, type: guessContentType(entry.name), content });
5503
5824
  }
5504
5825
  }
@@ -5507,7 +5828,7 @@ function getDownloadedSkillFiles(slug) {
5507
5828
  return files;
5508
5829
  }
5509
5830
  function getDownloadedSkillMeta(slug) {
5510
- const metaPath = path11.join(getMarketplaceDir(), slug, "metadata.json");
5831
+ const metaPath = path12.join(getMarketplaceDir(), slug, "metadata.json");
5511
5832
  try {
5512
5833
  if (fs12.existsSync(metaPath)) {
5513
5834
  return JSON.parse(fs12.readFileSync(metaPath, "utf-8"));
@@ -6047,13 +6368,13 @@ function findSkillMdRecursive(dir, depth = 0) {
6047
6368
  if (depth > 3) return null;
6048
6369
  try {
6049
6370
  for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
6050
- const candidate = path12.join(dir, name);
6371
+ const candidate = path13.join(dir, name);
6051
6372
  if (fs13.existsSync(candidate)) return candidate;
6052
6373
  }
6053
6374
  const entries = fs13.readdirSync(dir, { withFileTypes: true });
6054
6375
  for (const entry of entries) {
6055
6376
  if (!entry.isDirectory()) continue;
6056
- const found = findSkillMdRecursive(path12.join(dir, entry.name), depth + 1);
6377
+ const found = findSkillMdRecursive(path13.join(dir, entry.name), depth + 1);
6057
6378
  if (found) return found;
6058
6379
  }
6059
6380
  } catch {
@@ -6096,9 +6417,9 @@ async function skillsRoutes(app) {
6096
6417
  name: a.name,
6097
6418
  source: "user",
6098
6419
  status: "active",
6099
- path: path12.join(skillsDir2 ?? "", a.name),
6420
+ path: path13.join(skillsDir2 ?? "", a.name),
6100
6421
  publisher: a.publisher,
6101
- description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, a.name)) : void 0
6422
+ description: skillsDir2 ? readSkillDescription(path13.join(skillsDir2, a.name)) : void 0
6102
6423
  })),
6103
6424
  // Quarantined
6104
6425
  ...quarantined.map((q) => ({
@@ -6113,8 +6434,8 @@ async function skillsRoutes(app) {
6113
6434
  name,
6114
6435
  source: "workspace",
6115
6436
  status: "workspace",
6116
- path: path12.join(skillsDir2 ?? "", name),
6117
- description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, name)) : void 0
6437
+ path: path13.join(skillsDir2 ?? "", name),
6438
+ description: skillsDir2 ? readSkillDescription(path13.join(skillsDir2, name)) : void 0
6118
6439
  })),
6119
6440
  // Downloaded (not installed) → available
6120
6441
  ...availableDownloads.map((d) => ({
@@ -6155,15 +6476,15 @@ async function skillsRoutes(app) {
6155
6476
  } else if (entry) {
6156
6477
  source = "user";
6157
6478
  status = "active";
6158
- skillPath = skillsDir2 ? path12.join(skillsDir2, name) : "";
6479
+ skillPath = skillsDir2 ? path13.join(skillsDir2, name) : "";
6159
6480
  } else if (skillsDir2) {
6160
6481
  source = "workspace";
6161
6482
  status = "workspace";
6162
- skillPath = path12.join(skillsDir2, name);
6483
+ skillPath = path13.join(skillsDir2, name);
6163
6484
  }
6164
6485
  let content = "";
6165
6486
  let metadata;
6166
- const dirToRead = skillPath || (skillsDir2 ? path12.join(skillsDir2, name) : "");
6487
+ const dirToRead = skillPath || (skillsDir2 ? path13.join(skillsDir2, name) : "");
6167
6488
  if (dirToRead) {
6168
6489
  try {
6169
6490
  const mdPath = findSkillMdRecursive(dirToRead);
@@ -6297,9 +6618,9 @@ async function skillsRoutes(app) {
6297
6618
  return reply.code(500).send({ error: "Skills directory not configured" });
6298
6619
  }
6299
6620
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
6300
- const binDir = path12.join(agentHome, "bin");
6621
+ const binDir = path13.join(agentHome, "bin");
6301
6622
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield";
6302
- const skillDir = path12.join(skillsDir2, name);
6623
+ const skillDir = path13.join(skillsDir2, name);
6303
6624
  const isInstalled = fs13.existsSync(skillDir);
6304
6625
  if (isInstalled) {
6305
6626
  try {
@@ -6346,8 +6667,8 @@ async function skillsRoutes(app) {
6346
6667
  } else {
6347
6668
  fs13.mkdirSync(skillDir, { recursive: true });
6348
6669
  for (const file of files) {
6349
- const filePath = path12.join(skillDir, file.name);
6350
- fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
6670
+ const filePath = path13.join(skillDir, file.name);
6671
+ fs13.mkdirSync(path13.dirname(filePath), { recursive: true });
6351
6672
  fs13.writeFileSync(filePath, file.content, "utf-8");
6352
6673
  }
6353
6674
  try {
@@ -6402,15 +6723,15 @@ async function skillsRoutes(app) {
6402
6723
  return reply.code(500).send({ error: "Skills directory not configured" });
6403
6724
  }
6404
6725
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
6405
- const binDir = path12.join(agentHome, "bin");
6726
+ const binDir = path13.join(agentHome, "bin");
6406
6727
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield";
6407
- const skillDir = path12.join(skillsDir2, name);
6728
+ const skillDir = path13.join(skillsDir2, name);
6408
6729
  try {
6409
6730
  addToApprovedList(name, publisher);
6410
6731
  fs13.mkdirSync(skillDir, { recursive: true });
6411
6732
  for (const file of files) {
6412
- const filePath = path12.join(skillDir, file.name);
6413
- fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
6733
+ const filePath = path13.join(skillDir, file.name);
6734
+ fs13.mkdirSync(path13.dirname(filePath), { recursive: true });
6414
6735
  fs13.writeFileSync(filePath, file.content, "utf-8");
6415
6736
  }
6416
6737
  try {
@@ -6440,7 +6761,7 @@ async function skillsRoutes(app) {
6440
6761
 
6441
6762
  // libs/shield-daemon/src/routes/exec.ts
6442
6763
  import * as fs14 from "node:fs";
6443
- import * as path13 from "node:path";
6764
+ import * as path14 from "node:path";
6444
6765
  var ALLOWED_COMMANDS_PATH2 = "/opt/agenshield/config/allowed-commands.json";
6445
6766
  var BIN_DIRS = [
6446
6767
  "/usr/bin",
@@ -6464,7 +6785,7 @@ function loadConfig2() {
6464
6785
  }
6465
6786
  }
6466
6787
  function saveConfig2(config) {
6467
- const dir = path13.dirname(ALLOWED_COMMANDS_PATH2);
6788
+ const dir = path14.dirname(ALLOWED_COMMANDS_PATH2);
6468
6789
  if (!fs14.existsSync(dir)) {
6469
6790
  fs14.mkdirSync(dir, { recursive: true });
6470
6791
  }
@@ -6481,7 +6802,7 @@ function scanSystemBins() {
6481
6802
  const entries = fs14.readdirSync(dir);
6482
6803
  for (const entry of entries) {
6483
6804
  if (seen.has(entry)) continue;
6484
- const fullPath = path13.join(dir, entry);
6805
+ const fullPath = path14.join(dir, entry);
6485
6806
  try {
6486
6807
  const stat = fs14.statSync(fullPath);
6487
6808
  if (stat.isFile() && (stat.mode & 73) !== 0) {
@@ -6536,7 +6857,7 @@ async function execRoutes(app) {
6536
6857
  };
6537
6858
  }
6538
6859
  for (const p of paths) {
6539
- if (!path13.isAbsolute(p)) {
6860
+ if (!path14.isAbsolute(p)) {
6540
6861
  return {
6541
6862
  success: false,
6542
6863
  error: {
@@ -6964,7 +7285,7 @@ async function secretsRoutes(app) {
6964
7285
 
6965
7286
  // libs/shield-daemon/src/routes/marketplace.ts
6966
7287
  import * as fs15 from "node:fs";
6967
- import * as path14 from "node:path";
7288
+ import * as path15 from "node:path";
6968
7289
  async function marketplaceRoutes(app) {
6969
7290
  app.get(
6970
7291
  "/marketplace/search",
@@ -7160,9 +7481,9 @@ async function marketplaceRoutes(app) {
7160
7481
  return reply.code(500).send({ error: "Skills directory not configured" });
7161
7482
  }
7162
7483
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
7163
- const binDir = path14.join(agentHome, "bin");
7484
+ const binDir = path15.join(agentHome, "bin");
7164
7485
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield";
7165
- skillDir = path14.join(skillsDir2, slug);
7486
+ skillDir = path15.join(skillsDir2, slug);
7166
7487
  emitSkillInstallProgress(slug, "approve", "Pre-approving skill");
7167
7488
  addToApprovedList(slug, publisher);
7168
7489
  logs.push("Skill pre-approved");
@@ -7230,14 +7551,14 @@ async function marketplaceRoutes(app) {
7230
7551
 
7231
7552
  // libs/shield-daemon/src/routes/fs.ts
7232
7553
  import * as fs16 from "node:fs";
7233
- import * as path15 from "node:path";
7554
+ import * as path16 from "node:path";
7234
7555
  import * as os5 from "node:os";
7235
7556
  var MAX_ENTRIES = 200;
7236
7557
  async function fsRoutes(app) {
7237
7558
  app.get("/fs/browse", async (request) => {
7238
7559
  const dirPath = request.query.path || os5.homedir();
7239
7560
  const showHidden = request.query.showHidden === "true";
7240
- const resolvedPath = path15.resolve(dirPath);
7561
+ const resolvedPath = path16.resolve(dirPath);
7241
7562
  let dirents;
7242
7563
  try {
7243
7564
  dirents = fs16.readdirSync(resolvedPath, { withFileTypes: true });
@@ -7249,7 +7570,7 @@ async function fsRoutes(app) {
7249
7570
  if (!showHidden && dirent.name.startsWith(".")) continue;
7250
7571
  entries.push({
7251
7572
  name: dirent.name,
7252
- path: path15.join(resolvedPath, dirent.name),
7573
+ path: path16.join(resolvedPath, dirent.name),
7253
7574
  type: dirent.isDirectory() ? "directory" : "file"
7254
7575
  });
7255
7576
  if (entries.length >= MAX_ENTRIES) break;
@@ -7264,7 +7585,8 @@ async function fsRoutes(app) {
7264
7585
 
7265
7586
  // libs/shield-daemon/src/services/activity-log.ts
7266
7587
  import * as fs17 from "node:fs";
7267
- import * as path16 from "node:path";
7588
+ import * as path17 from "node:path";
7589
+ init_paths();
7268
7590
  var ACTIVITY_FILE = "activity.jsonl";
7269
7591
  var MAX_SIZE_BYTES = 100 * 1024 * 1024;
7270
7592
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
@@ -7281,7 +7603,7 @@ var ActivityLog = class {
7281
7603
  writeCount = 0;
7282
7604
  unsubscribe;
7283
7605
  constructor() {
7284
- this.filePath = path16.join(getConfigDir(), ACTIVITY_FILE);
7606
+ this.filePath = path17.join(getConfigDir(), ACTIVITY_FILE);
7285
7607
  }
7286
7608
  /** Read historical events from the JSONL file, newest first */
7287
7609
  getHistory(limit = 500) {
@@ -7381,26 +7703,31 @@ function globToRegex(pattern) {
7381
7703
  const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
7382
7704
  return new RegExp(`^${regexPattern}$`, "i");
7383
7705
  }
7384
- function normalizeUrlPattern(pattern) {
7706
+ function normalizeUrlBase(pattern) {
7385
7707
  let p = pattern.trim();
7386
7708
  p = p.replace(/\/+$/, "");
7387
7709
  if (!p.match(/^(\*|https?):\/\//i)) {
7388
7710
  p = `https://${p}`;
7389
7711
  }
7390
- if (!p.endsWith("*")) {
7391
- p = `${p}/**`;
7392
- }
7393
7712
  return p;
7394
7713
  }
7714
+ function matchUrlPattern(pattern, target) {
7715
+ const base = normalizeUrlBase(pattern);
7716
+ const trimmed = pattern.trim().replace(/\/+$/, "");
7717
+ if (trimmed.endsWith("*")) {
7718
+ return globToRegex(base).test(target);
7719
+ }
7720
+ return globToRegex(base).test(target) || globToRegex(`${base}/**`).test(target);
7721
+ }
7395
7722
  function normalizeUrlTarget(url) {
7396
7723
  const trimmed = url.trim();
7397
7724
  try {
7398
7725
  const parsed = new URL(trimmed);
7399
- let path18 = parsed.pathname;
7400
- if (path18.length > 1) {
7401
- path18 = path18.replace(/\/+$/, "");
7726
+ let path19 = parsed.pathname;
7727
+ if (path19.length > 1) {
7728
+ path19 = path19.replace(/\/+$/, "");
7402
7729
  }
7403
- return `${parsed.protocol}//${parsed.host}${path18}${parsed.search}`;
7730
+ return `${parsed.protocol}//${parsed.host}${path19}${parsed.search}`;
7404
7731
  } catch {
7405
7732
  return trimmed.replace(/\/+$/, "");
7406
7733
  }
@@ -7432,10 +7759,8 @@ function evaluatePolicyCheck(operation, target) {
7432
7759
  if (policy.target !== "url" || policy.action !== "allow") continue;
7433
7760
  for (const pattern of policy.patterns) {
7434
7761
  if (!pattern.match(/^http:\/\//i)) continue;
7435
- const effectivePattern = normalizeUrlPattern(pattern);
7436
7762
  const effectiveTarget = normalizeUrlTarget(target);
7437
- const regex = globToRegex(effectivePattern);
7438
- if (regex.test(effectiveTarget)) {
7763
+ if (matchUrlPattern(pattern, effectiveTarget)) {
7439
7764
  explicitHttpAllow = true;
7440
7765
  break;
7441
7766
  }
@@ -7457,11 +7782,15 @@ function evaluatePolicyCheck(operation, target) {
7457
7782
  }
7458
7783
  console.log("[policy_check] checking policy:", policy.name, "target:", policy.target, "action:", policy.action);
7459
7784
  for (const pattern of policy.patterns) {
7460
- const effectivePattern = targetType === "url" ? normalizeUrlPattern(pattern) : pattern;
7461
7785
  const effectiveTarget = targetType === "url" ? normalizeUrlTarget(target) : target;
7462
- const regex = globToRegex(effectivePattern);
7463
- const matches = regex.test(effectiveTarget);
7464
- console.log("[policy_check] pattern:", pattern, "-> normalized:", effectivePattern, "| target:", effectiveTarget, "| matches:", matches);
7786
+ let matches;
7787
+ if (targetType === "url") {
7788
+ matches = matchUrlPattern(pattern, effectiveTarget);
7789
+ } else {
7790
+ const regex = globToRegex(pattern);
7791
+ matches = regex.test(effectiveTarget);
7792
+ }
7793
+ console.log("[policy_check] pattern:", pattern, "-> base:", targetType === "url" ? normalizeUrlBase(pattern) : pattern, "| target:", effectiveTarget, "| matches:", matches);
7465
7794
  if (matches) {
7466
7795
  console.log("[policy_check] MATCHED policy:", policy.name, "action:", policy.action);
7467
7796
  return {
@@ -7635,20 +7964,20 @@ async function registerRoutes(app) {
7635
7964
 
7636
7965
  // libs/shield-daemon/src/static.ts
7637
7966
  import * as fs18 from "node:fs";
7638
- import * as path17 from "node:path";
7967
+ import * as path18 from "node:path";
7639
7968
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7640
7969
  var __filename = fileURLToPath2(import.meta.url);
7641
- var __dirname = path17.dirname(__filename);
7970
+ var __dirname = path18.dirname(__filename);
7642
7971
  function getUiAssetsPath() {
7643
- const pkgRootPath = path17.join(__dirname, "..", "ui-assets");
7972
+ const pkgRootPath = path18.join(__dirname, "..", "ui-assets");
7644
7973
  if (fs18.existsSync(pkgRootPath)) {
7645
7974
  return pkgRootPath;
7646
7975
  }
7647
- const bundledPath = path17.join(__dirname, "ui-assets");
7976
+ const bundledPath = path18.join(__dirname, "ui-assets");
7648
7977
  if (fs18.existsSync(bundledPath)) {
7649
7978
  return bundledPath;
7650
7979
  }
7651
- const devPath = path17.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7980
+ const devPath = path18.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7652
7981
  if (fs18.existsSync(devPath)) {
7653
7982
  return devPath;
7654
7983
  }
@@ -7748,6 +8077,17 @@ async function startServer(config) {
7748
8077
  }, 3e4);
7749
8078
  const activityLog = getActivityLog();
7750
8079
  activityLog.start();
8080
+ try {
8081
+ const { loadConfig: loadDaemonConfig } = await Promise.resolve().then(() => (init_loader(), loader_exports));
8082
+ const { loadState: loadState2 } = await Promise.resolve().then(() => (init_state(), state_exports));
8083
+ const { syncCommandPoliciesAndWrappers: syncCommandPoliciesAndWrappers2 } = await Promise.resolve().then(() => (init_command_sync(), command_sync_exports));
8084
+ const cfg = loadDaemonConfig();
8085
+ const st = loadState2();
8086
+ syncCommandPoliciesAndWrappers2(cfg.policies, st, app.log);
8087
+ app.log.info("[startup] boot-time command-sync completed");
8088
+ } catch (err) {
8089
+ app.log.warn(`[startup] boot-time command-sync failed: ${err.message}`);
8090
+ }
7751
8091
  try {
7752
8092
  const vault = getVault();
7753
8093
  const agenco = await vault.get("agenco");