@agenshield/daemon 0.7.1 → 0.7.2

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
@@ -1293,6 +1293,41 @@ var init_crypto = __esm({
1293
1293
  }
1294
1294
  });
1295
1295
 
1296
+ // libs/shield-daemon/src/vault/installation-key.ts
1297
+ import * as crypto2 from "node:crypto";
1298
+ async function getInstallationKey() {
1299
+ if (cachedKey) return cachedKey;
1300
+ const vault = getVault();
1301
+ const contents = await vault.load();
1302
+ if (contents.installationKey) {
1303
+ cachedKey = contents.installationKey;
1304
+ return cachedKey;
1305
+ }
1306
+ const key = crypto2.randomBytes(32).toString("hex");
1307
+ await vault.set("installationKey", key);
1308
+ cachedKey = key;
1309
+ console.log("[InstallationKey] Generated new installation key");
1310
+ return key;
1311
+ }
1312
+ async function getInstallationTag() {
1313
+ const key = await getInstallationKey();
1314
+ return `${INSTALLATION_KEY_PREFIX}${key}`;
1315
+ }
1316
+ function hasValidInstallationTagSync(tags) {
1317
+ if (!cachedKey) return false;
1318
+ const fullTag = `${INSTALLATION_KEY_PREFIX}${cachedKey}`;
1319
+ return tags.some((tag) => tag === fullTag);
1320
+ }
1321
+ var INSTALLATION_KEY_PREFIX, cachedKey;
1322
+ var init_installation_key = __esm({
1323
+ "libs/shield-daemon/src/vault/installation-key.ts"() {
1324
+ "use strict";
1325
+ init_vault();
1326
+ INSTALLATION_KEY_PREFIX = "agenshield-";
1327
+ cachedKey = null;
1328
+ }
1329
+ });
1330
+
1296
1331
  // libs/shield-daemon/src/vault/index.ts
1297
1332
  import * as fs4 from "node:fs";
1298
1333
  import * as path4 from "node:path";
@@ -1310,6 +1345,7 @@ var init_vault = __esm({
1310
1345
  init_crypto();
1311
1346
  init_paths();
1312
1347
  init_crypto();
1348
+ init_installation_key();
1313
1349
  Vault = class {
1314
1350
  key;
1315
1351
  vaultPath;
@@ -1847,7 +1883,7 @@ init_state();
1847
1883
  init_vault();
1848
1884
 
1849
1885
  // libs/shield-daemon/src/auth/session.ts
1850
- import * as crypto2 from "node:crypto";
1886
+ import * as crypto3 from "node:crypto";
1851
1887
  import { DEFAULT_AUTH_CONFIG } from "@agenshield/ipc";
1852
1888
  var SessionManager = class {
1853
1889
  sessions = /* @__PURE__ */ new Map();
@@ -1861,7 +1897,7 @@ var SessionManager = class {
1861
1897
  * Generate a secure random token
1862
1898
  */
1863
1899
  generateToken() {
1864
- return crypto2.randomBytes(32).toString("base64url");
1900
+ return crypto3.randomBytes(32).toString("base64url");
1865
1901
  }
1866
1902
  /**
1867
1903
  * Create a new session
@@ -2431,7 +2467,7 @@ function generatePolicyMarkdown(policies, knownSkills) {
2431
2467
  // libs/shield-daemon/src/watchers/skills.ts
2432
2468
  import * as fs11 from "node:fs";
2433
2469
  import * as path11 from "node:path";
2434
- import * as crypto3 from "node:crypto";
2470
+ import * as crypto4 from "node:crypto";
2435
2471
  import { parseSkillMd } from "@agenshield/sandbox";
2436
2472
 
2437
2473
  // libs/shield-daemon/src/services/marketplace.ts
@@ -2620,6 +2656,17 @@ function updateDownloadedAnalysis(slug, analysis) {
2620
2656
  } catch {
2621
2657
  }
2622
2658
  }
2659
+ function markDownloadedAsInstalled(slug) {
2660
+ const metaPath = path9.join(getMarketplaceDir(), slug, "metadata.json");
2661
+ try {
2662
+ if (!fs9.existsSync(metaPath)) return;
2663
+ const meta = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
2664
+ if (meta.wasInstalled) return;
2665
+ meta.wasInstalled = true;
2666
+ fs9.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
2667
+ } catch {
2668
+ }
2669
+ }
2623
2670
  function listDownloadedSkills() {
2624
2671
  const baseDir = getMarketplaceDir();
2625
2672
  if (!fs9.existsSync(baseDir)) return [];
@@ -2640,7 +2687,8 @@ function listDownloadedSkills() {
2640
2687
  tags: meta.tags ?? [],
2641
2688
  hasAnalysis: !!meta.analysis,
2642
2689
  source: meta.source,
2643
- analysis: meta.analysis
2690
+ analysis: meta.analysis,
2691
+ wasInstalled: meta.wasInstalled ?? false
2644
2692
  });
2645
2693
  } catch {
2646
2694
  }
@@ -3237,6 +3285,9 @@ function emitSkillUntrustedDetected(name, reason) {
3237
3285
  function emitSkillApproved(skillName) {
3238
3286
  daemonEvents.broadcast("skills:approved", { name: skillName });
3239
3287
  }
3288
+ function emitExecDenied(command, reason) {
3289
+ daemonEvents.broadcast("exec:denied", { command, reason });
3290
+ }
3240
3291
  function emitAgenCoAuthRequired(authUrl, integration) {
3241
3292
  daemonEvents.broadcast("agenco:auth_required", { authUrl, integration });
3242
3293
  }
@@ -3276,6 +3327,56 @@ function emitEvent(type, data) {
3276
3327
 
3277
3328
  // libs/shield-daemon/src/watchers/skills.ts
3278
3329
  init_paths();
3330
+
3331
+ // libs/shield-daemon/src/services/skill-tag-injector.ts
3332
+ init_installation_key();
3333
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
3334
+ var FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
3335
+ async function injectInstallationTag(content) {
3336
+ const tag = await getInstallationTag();
3337
+ const match = content.match(FRONTMATTER_RE);
3338
+ if (!match) {
3339
+ return `---
3340
+ tags:
3341
+ - ${tag}
3342
+ ---
3343
+ ${content}`;
3344
+ }
3345
+ try {
3346
+ const metadata = parseYaml(match[1]);
3347
+ if (!metadata || typeof metadata !== "object") {
3348
+ return content;
3349
+ }
3350
+ if (!Array.isArray(metadata.tags)) {
3351
+ metadata.tags = [];
3352
+ }
3353
+ metadata.tags = metadata.tags.filter(
3354
+ (t) => typeof t !== "string" || !t.startsWith("agenshield-")
3355
+ );
3356
+ metadata.tags.push(tag);
3357
+ return `---
3358
+ ${stringifyYaml(metadata).trimEnd()}
3359
+ ---
3360
+ ${match[2]}`;
3361
+ } catch {
3362
+ return content;
3363
+ }
3364
+ }
3365
+ function extractTagsFromSkillMd(content) {
3366
+ const match = content.match(FRONTMATTER_RE);
3367
+ if (!match) return [];
3368
+ try {
3369
+ const metadata = parseYaml(match[1]);
3370
+ if (!metadata || typeof metadata !== "object") return [];
3371
+ if (!Array.isArray(metadata.tags)) return [];
3372
+ return metadata.tags.filter((t) => typeof t === "string");
3373
+ } catch {
3374
+ return [];
3375
+ }
3376
+ }
3377
+
3378
+ // libs/shield-daemon/src/watchers/skills.ts
3379
+ init_installation_key();
3279
3380
  function getApprovedSkillsPath() {
3280
3381
  return path11.join(getSystemConfigDir(), "approved-skills.json");
3281
3382
  }
@@ -3414,7 +3515,7 @@ function computeSkillHash(skillDir) {
3414
3515
  const files = readSkillFiles(skillDir);
3415
3516
  if (files.length === 0) return null;
3416
3517
  files.sort((a, b) => a.name.localeCompare(b.name));
3417
- const hash = crypto3.createHash("sha256");
3518
+ const hash = crypto4.createHash("sha256");
3418
3519
  for (const file of files) {
3419
3520
  hash.update(file.name);
3420
3521
  hash.update(file.content);
@@ -3443,10 +3544,33 @@ function scanSkills() {
3443
3544
  const approvedEntry = approvedMap.get(skillName);
3444
3545
  if (!approvedEntry) {
3445
3546
  const fullPath = path11.join(skillsDir, skillName);
3446
- const slug = moveToMarketplace(skillName, fullPath);
3447
- if (slug) {
3448
- if (callbacks.onUntrustedDetected) {
3449
- callbacks.onUntrustedDetected({ name: skillName, reason: "Skill not in approved list" });
3547
+ let autoApproved = false;
3548
+ for (const mdName of ["SKILL.md", "skill.md"]) {
3549
+ const mdPath = path11.join(fullPath, mdName);
3550
+ try {
3551
+ if (fs11.existsSync(mdPath)) {
3552
+ const content = fs11.readFileSync(mdPath, "utf-8");
3553
+ const tags = extractTagsFromSkillMd(content);
3554
+ if (hasValidInstallationTagSync(tags)) {
3555
+ console.log(`[SkillsWatcher] Auto-approving skill with valid installation tag: ${skillName}`);
3556
+ const hash = computeSkillHash(fullPath);
3557
+ addToApprovedList(skillName, void 0, hash ?? void 0);
3558
+ if (callbacks.onApproved) {
3559
+ callbacks.onApproved(skillName);
3560
+ }
3561
+ autoApproved = true;
3562
+ break;
3563
+ }
3564
+ }
3565
+ } catch {
3566
+ }
3567
+ }
3568
+ if (!autoApproved) {
3569
+ const slug = moveToMarketplace(skillName, fullPath);
3570
+ if (slug) {
3571
+ if (callbacks.onUntrustedDetected) {
3572
+ callbacks.onUntrustedDetected({ name: skillName, reason: "Skill not in approved list" });
3573
+ }
3450
3574
  }
3451
3575
  }
3452
3576
  } else if (approvedEntry.hash) {
@@ -3552,7 +3676,7 @@ function approveSkill(skillName) {
3552
3676
  const cachedFiles = getDownloadedSkillFiles(slug);
3553
3677
  let hash;
3554
3678
  if (cachedFiles.length > 0) {
3555
- const h = crypto3.createHash("sha256");
3679
+ const h = crypto4.createHash("sha256");
3556
3680
  const sorted = [...cachedFiles].sort((a, b) => a.name.localeCompare(b.name));
3557
3681
  for (const file of sorted) {
3558
3682
  h.update(file.name);
@@ -3621,14 +3745,15 @@ function listApproved() {
3621
3745
  function getSkillsDir() {
3622
3746
  return skillsDir;
3623
3747
  }
3624
- function addToApprovedList(skillName, publisher, hash) {
3748
+ function addToApprovedList(skillName, publisher, hash, slug) {
3625
3749
  const approved = loadApprovedSkills();
3626
3750
  if (!approved.some((s) => s.name === skillName)) {
3627
3751
  approved.push({
3628
3752
  name: skillName,
3629
3753
  approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
3630
3754
  ...publisher ? { publisher } : {},
3631
- ...hash ? { hash } : {}
3755
+ ...hash ? { hash } : {},
3756
+ ...slug ? { slug } : {}
3632
3757
  });
3633
3758
  saveApprovedSkills(approved);
3634
3759
  }
@@ -3875,7 +4000,7 @@ async function securityRoutes(app) {
3875
4000
  // libs/shield-daemon/src/auth/passcode.ts
3876
4001
  init_vault();
3877
4002
  init_state();
3878
- import * as crypto4 from "node:crypto";
4003
+ import * as crypto5 from "node:crypto";
3879
4004
  import { DEFAULT_AUTH_CONFIG as DEFAULT_AUTH_CONFIG2 } from "@agenshield/ipc";
3880
4005
  var ITERATIONS = 1e5;
3881
4006
  var KEY_LENGTH = 64;
@@ -3883,8 +4008,8 @@ var DIGEST = "sha512";
3883
4008
  var SALT_LENGTH = 16;
3884
4009
  function hashPasscode(passcode) {
3885
4010
  return new Promise((resolve3, reject) => {
3886
- const salt = crypto4.randomBytes(SALT_LENGTH);
3887
- crypto4.pbkdf2(passcode, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => {
4011
+ const salt = crypto5.randomBytes(SALT_LENGTH);
4012
+ crypto5.pbkdf2(passcode, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => {
3888
4013
  if (err) {
3889
4014
  reject(err);
3890
4015
  return;
@@ -3904,12 +4029,12 @@ function verifyPasscode(passcode, storedHash) {
3904
4029
  const iterations = parseInt(parts[0], 10);
3905
4030
  const salt = Buffer.from(parts[1], "base64");
3906
4031
  const hash = Buffer.from(parts[2], "base64");
3907
- crypto4.pbkdf2(passcode, salt, iterations, hash.length, DIGEST, (err, derivedKey) => {
4032
+ crypto5.pbkdf2(passcode, salt, iterations, hash.length, DIGEST, (err, derivedKey) => {
3908
4033
  if (err) {
3909
4034
  reject(err);
3910
4035
  return;
3911
4036
  }
3912
- resolve3(crypto4.timingSafeEqual(hash, derivedKey));
4037
+ resolve3(crypto5.timingSafeEqual(hash, derivedKey));
3913
4038
  });
3914
4039
  });
3915
4040
  }
@@ -4081,6 +4206,12 @@ data: ${data}
4081
4206
 
4082
4207
  `;
4083
4208
  }
4209
+ var ALWAYS_FULL_PREFIXES = ["skills:", "exec:", "interceptor:", "security:", "wrappers:", "process:", "config:"];
4210
+ function shouldSendFull(event, authenticated) {
4211
+ if (authenticated) return true;
4212
+ if (event.type === "heartbeat" || event.type === "daemon:status") return true;
4213
+ return ALWAYS_FULL_PREFIXES.some((p) => event.type.startsWith(p));
4214
+ }
4084
4215
  async function sseRoutes(app) {
4085
4216
  app.get("/sse/events", async (request2, reply) => {
4086
4217
  const authenticated = isAuthenticated(request2);
@@ -4106,7 +4237,7 @@ async function sseRoutes(app) {
4106
4237
  reply.raw.write(authenticated ? formatSSE(statusEvent) : formatStrippedSSE(statusEvent));
4107
4238
  const unsubscribe = daemonEvents.subscribe((event) => {
4108
4239
  try {
4109
- if (event.type === "heartbeat" || event.type === "daemon:status" || event.type.startsWith("skills:") || authenticated) {
4240
+ if (shouldSendFull(event, authenticated)) {
4110
4241
  reply.raw.write(formatSSE(event));
4111
4242
  } else {
4112
4243
  reply.raw.write(formatStrippedSSE(event));
@@ -4154,7 +4285,7 @@ async function sseRoutes(app) {
4154
4285
  const unsubscribe = daemonEvents.subscribe((event) => {
4155
4286
  if (event.type.startsWith(filter) || event.type === "heartbeat" || event.type === "daemon:status") {
4156
4287
  try {
4157
- if (event.type === "heartbeat" || event.type === "daemon:status" || authenticated) {
4288
+ if (shouldSendFull(event, authenticated)) {
4158
4289
  reply.raw.write(formatSSE(event));
4159
4290
  } else {
4160
4291
  reply.raw.write(formatStrippedSSE(event));
@@ -7932,6 +8063,7 @@ import { parseSkillMd as parseSkillMd2, extractSkillInfo } from "@agenshield/san
7932
8063
  import { execWithProgress as execWithProgress3 } from "@agenshield/sandbox";
7933
8064
  var SUPPORTED_KINDS = /* @__PURE__ */ new Set(["brew", "npm", "pip"]);
7934
8065
  var SAFE_PACKAGE_RE = /^[a-zA-Z0-9@/_.\-]+$/;
8066
+ var SYSTEM_PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
7935
8067
  function findSkillMdRecursive(dir, depth = 0) {
7936
8068
  if (depth > 3) return null;
7937
8069
  try {
@@ -7995,7 +8127,7 @@ async function executeSkillInstallSteps(options) {
7995
8127
  onLog(`Installing brew formula: ${formula}`);
7996
8128
  const brewCmd = [
7997
8129
  `export HOME="${agentHome}"`,
7998
- `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:$PATH"`,
8130
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
7999
8131
  `brew install ${formula}`
8000
8132
  ].join(" && ");
8001
8133
  await execWithProgress3(
@@ -8019,7 +8151,7 @@ async function executeSkillInstallSteps(options) {
8019
8151
  onLog(`Installing npm package: ${pkg}`);
8020
8152
  const npmCmd = [
8021
8153
  `export HOME="${agentHome}"`,
8022
- `export PATH="${agentHome}/bin:$PATH"`,
8154
+ `export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
8023
8155
  `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8024
8156
  `npm install -g ${pkg}`
8025
8157
  ].join(" && ");
@@ -8044,7 +8176,7 @@ async function executeSkillInstallSteps(options) {
8044
8176
  onLog(`Installing pip package: ${pkg}`);
8045
8177
  const pipCmd = [
8046
8178
  `export HOME="${agentHome}"`,
8047
- `export PATH="${agentHome}/bin:$PATH"`,
8179
+ `export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
8048
8180
  `pip install ${pkg}`
8049
8181
  ].join(" && ");
8050
8182
  await execWithProgress3(
@@ -8069,7 +8201,7 @@ async function executeSkillInstallSteps(options) {
8069
8201
  try {
8070
8202
  const checkCmd = [
8071
8203
  `export HOME="${agentHome}"`,
8072
- `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:$PATH"`,
8204
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
8073
8205
  `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8074
8206
  `which ${bin}`
8075
8207
  ].join(" && ");
@@ -8369,18 +8501,22 @@ async function marketplaceRoutes(app) {
8369
8501
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8370
8502
  skillDir = path16.join(skillsDir2, slug);
8371
8503
  emitSkillInstallProgress(slug, "approve", "Pre-approving skill");
8372
- addToApprovedList(slug, publisher);
8504
+ addToApprovedList(slug, publisher, void 0, slug);
8373
8505
  logs.push("Skill pre-approved");
8374
8506
  emitSkillInstallProgress(slug, "copy", "Writing skill files");
8507
+ const taggedFiles = await Promise.all(files.map(async (f) => {
8508
+ let content = f.content;
8509
+ if (/SKILL\.md$/i.test(f.name)) {
8510
+ content = stripEnvFromSkillMd(content);
8511
+ content = await injectInstallationTag(content);
8512
+ }
8513
+ return { ...f, content };
8514
+ }));
8375
8515
  const brokerAvailable = await isBrokerAvailable();
8376
8516
  if (brokerAvailable) {
8377
- const sanitizedFiles = files.map((f) => ({
8378
- name: f.name,
8379
- content: /SKILL\.md$/i.test(f.name) ? stripEnvFromSkillMd(f.content) : f.content
8380
- }));
8381
8517
  const brokerResult = await installSkillViaBroker(
8382
8518
  slug,
8383
- sanitizedFiles,
8519
+ taggedFiles.map((f) => ({ name: f.name, content: f.content })),
8384
8520
  { createWrapper: true, agentHome, socketGroup }
8385
8521
  );
8386
8522
  if (!brokerResult.installed) {
@@ -8400,29 +8536,49 @@ async function marketplaceRoutes(app) {
8400
8536
  } else {
8401
8537
  console.log(`[Marketplace] Broker unavailable, installing ${slug} directly`);
8402
8538
  sudoMkdir(skillDir, agentUsername);
8403
- for (const file of files) {
8539
+ for (const file of taggedFiles) {
8404
8540
  const filePath = path16.join(skillDir, file.name);
8405
8541
  const fileDir = path16.dirname(filePath);
8406
8542
  if (fileDir !== skillDir) {
8407
8543
  sudoMkdir(fileDir, agentUsername);
8408
8544
  }
8409
- const content = /SKILL\.md$/i.test(file.name) ? stripEnvFromSkillMd(file.content) : file.content;
8410
- sudoWriteFile(filePath, content, agentUsername);
8545
+ sudoWriteFile(filePath, file.content, agentUsername);
8411
8546
  }
8412
8547
  createSkillWrapper(slug, binDir);
8413
- logs.push(`Files written directly: ${files.length} files`);
8548
+ logs.push(`Files written directly: ${taggedFiles.length} files`);
8414
8549
  logs.push(`Wrapper created: ${path16.join(binDir, slug)}`);
8415
8550
  }
8416
8551
  let depsSuccess = true;
8417
8552
  emitSkillInstallProgress(slug, "deps", "Installing skill dependencies");
8418
8553
  try {
8554
+ let depsLineCount = 0;
8555
+ let depsLastEmit = Date.now();
8556
+ let depsLastLine = "";
8557
+ const DEPS_DEBOUNCE_MS = 3e3;
8558
+ const depsOnLog = (msg) => {
8559
+ depsLineCount++;
8560
+ depsLastLine = msg;
8561
+ if (/^(Installing|Found|Verifying)\s/.test(msg)) {
8562
+ emitSkillInstallProgress(slug, "deps", msg);
8563
+ depsLastEmit = Date.now();
8564
+ return;
8565
+ }
8566
+ const now = Date.now();
8567
+ if (now - depsLastEmit >= DEPS_DEBOUNCE_MS) {
8568
+ emitSkillInstallProgress(slug, "deps", `Installing... (${depsLineCount} lines)`);
8569
+ depsLastEmit = now;
8570
+ }
8571
+ };
8419
8572
  const depsResult = await executeSkillInstallSteps({
8420
8573
  slug,
8421
8574
  skillDir,
8422
8575
  agentHome,
8423
8576
  agentUsername,
8424
- onLog: (msg) => emitSkillInstallProgress(slug, "deps", msg)
8577
+ onLog: depsOnLog
8425
8578
  });
8579
+ if (depsLineCount > 0) {
8580
+ emitSkillInstallProgress(slug, "deps", `Dependency install complete (${depsLineCount} lines processed)`);
8581
+ }
8426
8582
  if (depsResult.installed.length > 0) {
8427
8583
  logs.push(`Dependencies installed: ${depsResult.installed.join(", ")}`);
8428
8584
  }
@@ -8448,6 +8604,10 @@ async function marketplaceRoutes(app) {
8448
8604
  updateApprovedHash(slug, hash);
8449
8605
  logs.push("Integrity hash recorded");
8450
8606
  }
8607
+ try {
8608
+ markDownloadedAsInstalled(slug);
8609
+ } catch {
8610
+ }
8451
8611
  installInProgress.delete(slug);
8452
8612
  const depsWarnings = depsSuccess ? void 0 : logs.filter((l) => l.startsWith("Dependency"));
8453
8613
  daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult, depsWarnings });
@@ -8625,7 +8785,30 @@ async function skillsRoutes(app) {
8625
8785
  tags: meta.tags,
8626
8786
  analysis: buildAnalysisSummary(name, getCachedAnalysis2(name))
8627
8787
  };
8628
- })
8788
+ }),
8789
+ // Disabled: previously installed marketplace skills that are no longer active
8790
+ ...(() => {
8791
+ const allKnown = /* @__PURE__ */ new Set([
8792
+ ...approvedNames,
8793
+ ...untrustedNames,
8794
+ ...workspaceNames
8795
+ ]);
8796
+ return listDownloadedSkills().filter((d) => d.wasInstalled && !allKnown.has(d.slug) && !allKnown.has(d.name)).map((d) => {
8797
+ const cached = getCachedAnalysis2(d.slug) || getCachedAnalysis2(d.name);
8798
+ return {
8799
+ name: d.slug,
8800
+ source: "marketplace",
8801
+ status: "disabled",
8802
+ path: "",
8803
+ publisher: d.author,
8804
+ description: d.description,
8805
+ version: d.version,
8806
+ author: d.author,
8807
+ tags: d.tags,
8808
+ analysis: buildAnalysisSummary(d.slug, d.analysis || cached)
8809
+ };
8810
+ });
8811
+ })()
8629
8812
  ];
8630
8813
  return reply.send({ data });
8631
8814
  });
@@ -8699,7 +8882,7 @@ async function skillsRoutes(app) {
8699
8882
  summary = {
8700
8883
  name: dlMeta.slug ?? name,
8701
8884
  source: "marketplace",
8702
- status: "downloaded",
8885
+ status: dlMeta.wasInstalled ? "disabled" : "downloaded",
8703
8886
  description: dlMeta.description,
8704
8887
  path: "",
8705
8888
  publisher: dlMeta.author,
@@ -8966,7 +9149,7 @@ async function skillsRoutes(app) {
8966
9149
  syncOpenClawFromPolicies(loadConfig().policies);
8967
9150
  removeFromApprovedList(name);
8968
9151
  try {
8969
- deleteDownloadedSkill(name);
9152
+ markDownloadedAsInstalled(name);
8970
9153
  } catch {
8971
9154
  }
8972
9155
  console.log(`[Skills] Disabled marketplace skill: ${name}`);
@@ -8986,12 +9169,20 @@ async function skillsRoutes(app) {
8986
9169
  return reply.code(404).send({ error: "No files in download cache for this skill" });
8987
9170
  }
8988
9171
  try {
8989
- addToApprovedList(name, meta.author);
9172
+ addToApprovedList(name, meta.author, void 0, meta.slug);
9173
+ const taggedFiles = await Promise.all(files.map(async (f) => {
9174
+ let content = f.content;
9175
+ if (/SKILL\.md$/i.test(f.name)) {
9176
+ content = stripEnvFromSkillMd2(content);
9177
+ content = await injectInstallationTag(content);
9178
+ }
9179
+ return { name: f.name, content, type: f.type };
9180
+ }));
8990
9181
  const brokerAvailable = await isBrokerAvailable();
8991
9182
  if (brokerAvailable) {
8992
9183
  const brokerResult = await installSkillViaBroker(
8993
9184
  name,
8994
- files.map((f) => ({ name: f.name, content: f.content })),
9185
+ taggedFiles.map((f) => ({ name: f.name, content: f.content })),
8995
9186
  { createWrapper: true, agentHome, socketGroup }
8996
9187
  );
8997
9188
  if (!brokerResult.installed) {
@@ -9004,7 +9195,7 @@ async function skillsRoutes(app) {
9004
9195
  }
9005
9196
  } else {
9006
9197
  fs17.mkdirSync(skillDir, { recursive: true });
9007
- for (const file of files) {
9198
+ for (const file of taggedFiles) {
9008
9199
  const filePath = path17.join(skillDir, file.name);
9009
9200
  fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9010
9201
  fs17.writeFileSync(filePath, file.content, "utf-8");
@@ -9021,6 +9212,10 @@ async function skillsRoutes(app) {
9021
9212
  syncOpenClawFromPolicies(loadConfig().policies);
9022
9213
  const hash = computeSkillHash(skillDir);
9023
9214
  if (hash) updateApprovedHash(name, hash);
9215
+ try {
9216
+ markDownloadedAsInstalled(name);
9217
+ } catch {
9218
+ }
9024
9219
  console.log(`[Skills] Enabled marketplace skill: ${name}`);
9025
9220
  return reply.send({ success: true, action: "enabled", name });
9026
9221
  } catch (err) {
@@ -9070,12 +9265,19 @@ async function skillsRoutes(app) {
9070
9265
  const skillDir = path17.join(skillsDir2, name);
9071
9266
  try {
9072
9267
  addToApprovedList(name, publisher);
9268
+ const taggedFiles = await Promise.all(files.map(async (f) => {
9269
+ let content = f.content;
9270
+ if (/SKILL\.md$/i.test(f.name)) {
9271
+ content = stripEnvFromSkillMd2(content);
9272
+ content = await injectInstallationTag(content);
9273
+ }
9274
+ return { name: f.name, content };
9275
+ }));
9073
9276
  sudoMkdir(skillDir, agentUsername);
9074
- for (const file of files) {
9277
+ for (const file of taggedFiles) {
9075
9278
  const filePath = path17.join(skillDir, file.name);
9076
9279
  sudoMkdir(path17.dirname(filePath), agentUsername);
9077
- const content = /SKILL\.md$/i.test(file.name) ? stripEnvFromSkillMd2(file.content) : file.content;
9078
- sudoWriteFile(filePath, content, agentUsername);
9280
+ sudoWriteFile(filePath, file.content, agentUsername);
9079
9281
  }
9080
9282
  try {
9081
9283
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -9127,12 +9329,20 @@ async function skillsRoutes(app) {
9127
9329
  const binDir = path17.join(agentHome, "bin");
9128
9330
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
9129
9331
  const skillDir = path17.join(getSkillsDir(), name);
9130
- addToApprovedList(name, meta.author);
9332
+ addToApprovedList(name, meta.author, void 0, meta.slug);
9333
+ const taggedFiles = await Promise.all(files.map(async (f) => {
9334
+ let content = f.content;
9335
+ if (/SKILL\.md$/i.test(f.name)) {
9336
+ content = stripEnvFromSkillMd2(content);
9337
+ content = await injectInstallationTag(content);
9338
+ }
9339
+ return { name: f.name, content, type: f.type };
9340
+ }));
9131
9341
  const brokerAvailable = await isBrokerAvailable();
9132
9342
  if (brokerAvailable) {
9133
9343
  const brokerResult = await installSkillViaBroker(
9134
9344
  name,
9135
- files.map((f) => ({ name: f.name, content: f.content })),
9345
+ taggedFiles.map((f) => ({ name: f.name, content: f.content })),
9136
9346
  { createWrapper: true, agentHome, socketGroup }
9137
9347
  );
9138
9348
  if (!brokerResult.installed) {
@@ -9145,7 +9355,7 @@ async function skillsRoutes(app) {
9145
9355
  }
9146
9356
  } else {
9147
9357
  fs17.mkdirSync(skillDir, { recursive: true });
9148
- for (const file of files) {
9358
+ for (const file of taggedFiles) {
9149
9359
  const filePath = path17.join(skillDir, file.name);
9150
9360
  fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9151
9361
  fs17.writeFileSync(filePath, file.content, "utf-8");
@@ -9162,6 +9372,10 @@ async function skillsRoutes(app) {
9162
9372
  syncOpenClawFromPolicies(loadConfig().policies);
9163
9373
  const unblockHash = computeSkillHash(path17.join(getSkillsDir(), name));
9164
9374
  if (unblockHash) updateApprovedHash(name, unblockHash);
9375
+ try {
9376
+ markDownloadedAsInstalled(name);
9377
+ } catch {
9378
+ }
9165
9379
  console.log(`[Skills] Unblocked and installed skill: ${name}`);
9166
9380
  return reply.send({ success: true, message: `Skill "${name}" approved and installed` });
9167
9381
  } catch (err) {
@@ -9661,7 +9875,7 @@ async function authRoutes(app) {
9661
9875
  init_vault();
9662
9876
  import { isSecretEnvVar } from "@agenshield/sandbox";
9663
9877
  init_secret_sync();
9664
- import crypto5 from "node:crypto";
9878
+ import crypto6 from "node:crypto";
9665
9879
  function maskValue(value) {
9666
9880
  if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
9667
9881
  return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
@@ -9756,7 +9970,7 @@ async function secretsRoutes(app) {
9756
9970
  const secrets = await vault.get("secrets") ?? [];
9757
9971
  const resolvedScope = scope ?? (policyIds?.length > 0 ? "policed" : "global");
9758
9972
  const newSecret = {
9759
- id: crypto5.randomUUID(),
9973
+ id: crypto6.randomUUID(),
9760
9974
  name: name.trim(),
9761
9975
  value,
9762
9976
  policyIds: resolvedScope === "standalone" ? [] : policyIds ?? [],
@@ -10057,7 +10271,7 @@ async function openclawRoutes(app) {
10057
10271
  }
10058
10272
 
10059
10273
  // libs/shield-daemon/src/routes/rpc.ts
10060
- import * as crypto6 from "node:crypto";
10274
+ import * as crypto7 from "node:crypto";
10061
10275
  import * as nodefs from "node:fs";
10062
10276
 
10063
10277
  // libs/shield-daemon/src/policy/url-matcher.ts
@@ -10171,7 +10385,7 @@ function checkUrlPolicy(policies, url) {
10171
10385
  // libs/shield-daemon/src/proxy/server.ts
10172
10386
  import * as http from "node:http";
10173
10387
  import * as net from "node:net";
10174
- function createPerRunProxy(urlPolicies, onActivity, logger) {
10388
+ function createPerRunProxy(urlPolicies, onActivity, logger, onBlock) {
10175
10389
  const server = http.createServer((req, res) => {
10176
10390
  onActivity();
10177
10391
  const url = req.url;
@@ -10183,6 +10397,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
10183
10397
  const allowed = checkUrlPolicy(urlPolicies, url);
10184
10398
  if (!allowed) {
10185
10399
  logger(`BLOCKED HTTP ${req.method} ${url}`);
10400
+ onBlock?.(req.method || "GET", url, "http");
10186
10401
  res.writeHead(403, { "Content-Type": "text/plain" });
10187
10402
  res.end("Blocked by AgenShield URL policy");
10188
10403
  return;
@@ -10226,6 +10441,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
10226
10441
  const allowed = checkUrlPolicy(urlPolicies, `https://${hostname2}`);
10227
10442
  if (!allowed) {
10228
10443
  logger(`BLOCKED CONNECT ${hostname2}:${port}`);
10444
+ onBlock?.("CONNECT", `${hostname2}:${port}`, "https");
10229
10445
  clientSocket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
10230
10446
  clientSocket.destroy();
10231
10447
  return;
@@ -10288,7 +10504,16 @@ var ProxyPool = class {
10288
10504
  const logger = (msg) => {
10289
10505
  console.log(`[proxy:${execId.slice(0, 8)}] ${msg}`);
10290
10506
  };
10291
- const server = createPerRunProxy(urlPolicies, onActivity, logger);
10507
+ const onBlock = (method, target, protocol) => {
10508
+ emitInterceptorEvent({
10509
+ type: "denied",
10510
+ operation: "http_request",
10511
+ target: protocol === "https" ? `https://${target}` : target,
10512
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10513
+ error: `Blocked by URL policy (${method})`
10514
+ });
10515
+ };
10516
+ const server = createPerRunProxy(urlPolicies, onActivity, logger, onBlock);
10292
10517
  const port = await new Promise((resolve3, reject) => {
10293
10518
  server.listen(0, "127.0.0.1", () => {
10294
10519
  const addr = server.address();
@@ -10371,12 +10596,19 @@ function matchCommandPattern(pattern, target) {
10371
10596
  normalizedTarget = firstSpace >= 0 ? basename6 + target.slice(firstSpace) : basename6;
10372
10597
  }
10373
10598
  if (trimmed.endsWith(":*")) {
10374
- const prefix = trimmed.slice(0, -2);
10599
+ let prefix = trimmed.slice(0, -2);
10600
+ if (prefix.includes("/")) {
10601
+ prefix = prefix.split("/").pop() || prefix;
10602
+ }
10375
10603
  const lowerTarget = normalizedTarget.toLowerCase();
10376
10604
  const lowerPrefix = prefix.toLowerCase();
10377
10605
  return lowerTarget === lowerPrefix || lowerTarget.startsWith(lowerPrefix + " ");
10378
10606
  }
10379
- return normalizedTarget.toLowerCase() === trimmed.toLowerCase();
10607
+ let normalizedPattern = trimmed;
10608
+ if (trimmed.includes("/")) {
10609
+ normalizedPattern = trimmed.split("/").pop() || trimmed;
10610
+ }
10611
+ return normalizedTarget.toLowerCase() === normalizedPattern.toLowerCase();
10380
10612
  }
10381
10613
  function operationToTarget(operation) {
10382
10614
  switch (operation) {
@@ -10469,7 +10701,7 @@ async function buildSandboxConfig(config, matchedPolicy, _context, target) {
10469
10701
  console.log(`[sandbox] direct network access (no proxy)`);
10470
10702
  sandbox.networkAllowed = true;
10471
10703
  } else if (networkMode === "proxy") {
10472
- const execId = crypto6.randomUUID();
10704
+ const execId = crypto7.randomUUID();
10473
10705
  const commandBasename = extractCommandBasename(target || "");
10474
10706
  const urlPolicies = filterUrlPoliciesForCommand(config.policies || [], commandBasename);
10475
10707
  const pool = getProxyPool();
@@ -10534,7 +10766,11 @@ async function evaluatePolicyCheck(operation, target, context) {
10534
10766
  } else if (targetType === "command") {
10535
10767
  matches = matchCommandPattern(pattern, effectiveTarget);
10536
10768
  } else {
10537
- const regex = globToRegex(pattern);
10769
+ let fsPattern = pattern;
10770
+ if (targetType === "filesystem" && fsPattern.endsWith("/")) {
10771
+ fsPattern = fsPattern + "**";
10772
+ }
10773
+ const regex = globToRegex(fsPattern);
10538
10774
  matches = regex.test(effectiveTarget);
10539
10775
  }
10540
10776
  console.log("[policy_check] pattern:", pattern, "-> base:", targetType === "url" ? normalizeUrlBase(pattern) : pattern, "| target:", effectiveTarget, "| matches:", matches);
@@ -10600,12 +10836,29 @@ async function handleHttpRequest(params) {
10600
10836
  body: responseBody
10601
10837
  };
10602
10838
  }
10839
+ async function handlePolicyCheck(params) {
10840
+ const operation = String(params["operation"] ?? "");
10841
+ const target = String(params["target"] ?? "");
10842
+ const context = params["context"];
10843
+ const result = await evaluatePolicyCheck(operation, target, context);
10844
+ if (!result.allowed) {
10845
+ if (operation === "exec") {
10846
+ emitExecDenied(target, result.reason || "Denied by policy");
10847
+ } else {
10848
+ emitInterceptorEvent({
10849
+ type: "denied",
10850
+ operation,
10851
+ target,
10852
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10853
+ policyId: result.policyId,
10854
+ error: result.reason || "Denied by policy"
10855
+ });
10856
+ }
10857
+ }
10858
+ return result;
10859
+ }
10603
10860
  var handlers = {
10604
- policy_check: (params) => evaluatePolicyCheck(
10605
- String(params["operation"] ?? ""),
10606
- String(params["target"] ?? ""),
10607
- params["context"]
10608
- ),
10861
+ policy_check: (params) => handlePolicyCheck(params),
10609
10862
  events_batch: (params) => handleEventsBatch(params),
10610
10863
  http_request: (params) => handleHttpRequest(params),
10611
10864
  ping: () => ({ status: "ok" })
@@ -10830,6 +11083,12 @@ async function startServer(config) {
10830
11083
  fs23.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
10831
11084
  console.log(`[Daemon] Created skills directory: ${skillsDir2}`);
10832
11085
  }
11086
+ try {
11087
+ await getInstallationKey();
11088
+ console.log("[Daemon] Installation key ready");
11089
+ } catch (err) {
11090
+ console.warn("[Daemon] Failed to initialize installation key:", err.message);
11091
+ }
10833
11092
  startSkillsWatcher(skillsDir2, {
10834
11093
  onUntrustedDetected: (info) => emitSkillUntrustedDetected(info.name, info.reason),
10835
11094
  onApproved: (name) => emitSkillApproved(name)