@agenshield/daemon 0.7.0 → 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/index.js +742 -237
- package/main.js +746 -241
- package/package.json +5 -5
- package/proxy/pool.d.ts.map +1 -1
- package/proxy/server.d.ts +1 -1
- package/proxy/server.d.ts.map +1 -1
- package/routes/marketplace.d.ts.map +1 -1
- package/routes/rpc.d.ts.map +1 -1
- package/routes/skills.d.ts.map +1 -1
- package/routes/sse.d.ts.map +1 -1
- package/server.d.ts.map +1 -1
- package/services/marketplace.d.ts +9 -0
- package/services/marketplace.d.ts.map +1 -1
- package/services/openclaw-config.d.ts +19 -0
- package/services/openclaw-config.d.ts.map +1 -1
- package/services/skill-deps.d.ts +29 -0
- package/services/skill-deps.d.ts.map +1 -0
- package/services/skill-lifecycle.d.ts +8 -0
- package/services/skill-lifecycle.d.ts.map +1 -1
- package/services/skill-tag-injector.d.ts +26 -0
- package/services/skill-tag-injector.d.ts.map +1 -0
- package/ui-assets/assets/index-xi3PdcyO.js +964 -0
- package/ui-assets/index.html +1 -1
- package/vault/index.d.ts +1 -0
- package/vault/index.d.ts.map +1 -1
- package/vault/installation-key.d.ts +30 -0
- package/vault/installation-key.d.ts.map +1 -0
- package/watchers/skills.d.ts +3 -1
- package/watchers/skills.d.ts.map +1 -1
- package/ui-assets/assets/index-aFHqLzyo.js +0 -964
package/index.js
CHANGED
|
@@ -1046,10 +1046,7 @@ PLISTEOF`);
|
|
|
1046
1046
|
}
|
|
1047
1047
|
async function startOpenClawServices() {
|
|
1048
1048
|
try {
|
|
1049
|
-
|
|
1050
|
-
await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1051
|
-
} catch {
|
|
1052
|
-
}
|
|
1049
|
+
await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1053
1050
|
return {
|
|
1054
1051
|
success: true,
|
|
1055
1052
|
message: "OpenClaw gateway started"
|
|
@@ -1064,10 +1061,7 @@ async function startOpenClawServices() {
|
|
|
1064
1061
|
}
|
|
1065
1062
|
async function stopOpenClawServices() {
|
|
1066
1063
|
try {
|
|
1067
|
-
|
|
1068
|
-
await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1069
|
-
} catch {
|
|
1070
|
-
}
|
|
1064
|
+
await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1071
1065
|
return {
|
|
1072
1066
|
success: true,
|
|
1073
1067
|
message: "OpenClaw gateway stopped"
|
|
@@ -1082,10 +1076,7 @@ async function stopOpenClawServices() {
|
|
|
1082
1076
|
}
|
|
1083
1077
|
async function restartOpenClawServices() {
|
|
1084
1078
|
try {
|
|
1085
|
-
|
|
1086
|
-
await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1087
|
-
} catch {
|
|
1088
|
-
}
|
|
1079
|
+
await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1089
1080
|
return {
|
|
1090
1081
|
success: true,
|
|
1091
1082
|
message: "OpenClaw gateway restarted"
|
|
@@ -1168,15 +1159,29 @@ function getOpenClawStatusSync() {
|
|
|
1168
1159
|
async function getOpenClawDashboardUrl() {
|
|
1169
1160
|
try {
|
|
1170
1161
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
)
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1162
|
+
const configPath = path22.join(agentHome, ".openclaw", "openclaw.json");
|
|
1163
|
+
let raw;
|
|
1164
|
+
try {
|
|
1165
|
+
raw = await fs32.readFile(configPath, "utf-8");
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
if (err.code === "EACCES") {
|
|
1168
|
+
const agentUsername = path22.basename(agentHome);
|
|
1169
|
+
const { stdout } = await execAsync3(
|
|
1170
|
+
`sudo -H -u ${agentUsername} cat "${configPath}"`,
|
|
1171
|
+
{ cwd: "/" }
|
|
1172
|
+
);
|
|
1173
|
+
raw = stdout;
|
|
1174
|
+
} else {
|
|
1175
|
+
return { success: false, error: `Cannot read openclaw.json: ${err.message}` };
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const config = JSON.parse(raw);
|
|
1179
|
+
const port = config.gateway?.port;
|
|
1180
|
+
const token = config.gateway?.auth?.token;
|
|
1181
|
+
if (!port || !token) {
|
|
1182
|
+
return { success: false, error: "Gateway port or auth token not found in openclaw.json" };
|
|
1179
1183
|
}
|
|
1184
|
+
const url = `http://127.0.0.1:${port}/?token=${token}`;
|
|
1180
1185
|
return { success: true, url };
|
|
1181
1186
|
} catch (error) {
|
|
1182
1187
|
return { success: false, error: `Failed to get dashboard URL: ${error.message}` };
|
|
@@ -1290,6 +1295,41 @@ var init_crypto = __esm({
|
|
|
1290
1295
|
}
|
|
1291
1296
|
});
|
|
1292
1297
|
|
|
1298
|
+
// libs/shield-daemon/src/vault/installation-key.ts
|
|
1299
|
+
import * as crypto2 from "node:crypto";
|
|
1300
|
+
async function getInstallationKey() {
|
|
1301
|
+
if (cachedKey) return cachedKey;
|
|
1302
|
+
const vault = getVault();
|
|
1303
|
+
const contents = await vault.load();
|
|
1304
|
+
if (contents.installationKey) {
|
|
1305
|
+
cachedKey = contents.installationKey;
|
|
1306
|
+
return cachedKey;
|
|
1307
|
+
}
|
|
1308
|
+
const key = crypto2.randomBytes(32).toString("hex");
|
|
1309
|
+
await vault.set("installationKey", key);
|
|
1310
|
+
cachedKey = key;
|
|
1311
|
+
console.log("[InstallationKey] Generated new installation key");
|
|
1312
|
+
return key;
|
|
1313
|
+
}
|
|
1314
|
+
async function getInstallationTag() {
|
|
1315
|
+
const key = await getInstallationKey();
|
|
1316
|
+
return `${INSTALLATION_KEY_PREFIX}${key}`;
|
|
1317
|
+
}
|
|
1318
|
+
function hasValidInstallationTagSync(tags) {
|
|
1319
|
+
if (!cachedKey) return false;
|
|
1320
|
+
const fullTag = `${INSTALLATION_KEY_PREFIX}${cachedKey}`;
|
|
1321
|
+
return tags.some((tag) => tag === fullTag);
|
|
1322
|
+
}
|
|
1323
|
+
var INSTALLATION_KEY_PREFIX, cachedKey;
|
|
1324
|
+
var init_installation_key = __esm({
|
|
1325
|
+
"libs/shield-daemon/src/vault/installation-key.ts"() {
|
|
1326
|
+
"use strict";
|
|
1327
|
+
init_vault();
|
|
1328
|
+
INSTALLATION_KEY_PREFIX = "agenshield-";
|
|
1329
|
+
cachedKey = null;
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1293
1333
|
// libs/shield-daemon/src/vault/index.ts
|
|
1294
1334
|
import * as fs4 from "node:fs";
|
|
1295
1335
|
import * as path4 from "node:path";
|
|
@@ -1310,6 +1350,7 @@ var init_vault = __esm({
|
|
|
1310
1350
|
init_crypto();
|
|
1311
1351
|
init_paths();
|
|
1312
1352
|
init_crypto();
|
|
1353
|
+
init_installation_key();
|
|
1313
1354
|
Vault = class {
|
|
1314
1355
|
key;
|
|
1315
1356
|
vaultPath;
|
|
@@ -1766,7 +1807,7 @@ var init_secret_sync = __esm({
|
|
|
1766
1807
|
});
|
|
1767
1808
|
|
|
1768
1809
|
// libs/shield-daemon/src/server.ts
|
|
1769
|
-
import * as
|
|
1810
|
+
import * as fs23 from "node:fs";
|
|
1770
1811
|
import Fastify from "fastify";
|
|
1771
1812
|
import cors from "@fastify/cors";
|
|
1772
1813
|
import fastifyStatic from "@fastify/static";
|
|
@@ -1844,7 +1885,7 @@ init_state();
|
|
|
1844
1885
|
init_vault();
|
|
1845
1886
|
|
|
1846
1887
|
// libs/shield-daemon/src/auth/session.ts
|
|
1847
|
-
import * as
|
|
1888
|
+
import * as crypto3 from "node:crypto";
|
|
1848
1889
|
import { DEFAULT_AUTH_CONFIG } from "@agenshield/ipc";
|
|
1849
1890
|
var SessionManager = class {
|
|
1850
1891
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -1858,7 +1899,7 @@ var SessionManager = class {
|
|
|
1858
1899
|
* Generate a secure random token
|
|
1859
1900
|
*/
|
|
1860
1901
|
generateToken() {
|
|
1861
|
-
return
|
|
1902
|
+
return crypto3.randomBytes(32).toString("base64url");
|
|
1862
1903
|
}
|
|
1863
1904
|
/**
|
|
1864
1905
|
* Create a new session
|
|
@@ -2150,11 +2191,24 @@ function getOpenClawConfigPath() {
|
|
|
2150
2191
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
2151
2192
|
return path8.join(agentHome, ".openclaw", "openclaw.json");
|
|
2152
2193
|
}
|
|
2153
|
-
function
|
|
2194
|
+
function readOpenClawConfig() {
|
|
2154
2195
|
const configPath = getOpenClawConfigPath();
|
|
2155
2196
|
try {
|
|
2156
2197
|
if (fs8.existsSync(configPath)) {
|
|
2157
|
-
|
|
2198
|
+
try {
|
|
2199
|
+
return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
if (err.code === "EACCES") {
|
|
2202
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
2203
|
+
const agentUsername = path8.basename(agentHome);
|
|
2204
|
+
const raw = execSync6(
|
|
2205
|
+
`sudo -H -u ${agentUsername} cat "${configPath}"`,
|
|
2206
|
+
{ encoding: "utf-8", cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
|
|
2207
|
+
);
|
|
2208
|
+
return JSON.parse(raw);
|
|
2209
|
+
}
|
|
2210
|
+
throw err;
|
|
2211
|
+
}
|
|
2158
2212
|
}
|
|
2159
2213
|
} catch {
|
|
2160
2214
|
console.warn("[OpenClawConfig] Failed to read openclaw.json, starting fresh");
|
|
@@ -2172,7 +2226,7 @@ function writeConfig(config) {
|
|
|
2172
2226
|
const agentUsername = path8.basename(agentHome);
|
|
2173
2227
|
execSync6(
|
|
2174
2228
|
`sudo -H -u ${agentUsername} tee "${configPath}" > /dev/null`,
|
|
2175
|
-
{ input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"] }
|
|
2229
|
+
{ input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"], cwd: "/" }
|
|
2176
2230
|
);
|
|
2177
2231
|
} else {
|
|
2178
2232
|
throw err;
|
|
@@ -2180,27 +2234,30 @@ function writeConfig(config) {
|
|
|
2180
2234
|
}
|
|
2181
2235
|
}
|
|
2182
2236
|
function addSkillEntry(slug) {
|
|
2183
|
-
const config =
|
|
2237
|
+
const config = readOpenClawConfig();
|
|
2184
2238
|
if (!config.skills) {
|
|
2185
2239
|
config.skills = {};
|
|
2186
2240
|
}
|
|
2187
2241
|
if (!config.skills.entries) {
|
|
2188
2242
|
config.skills.entries = {};
|
|
2189
2243
|
}
|
|
2190
|
-
config.skills.entries[slug]
|
|
2244
|
+
const existing = config.skills.entries[slug] ?? {};
|
|
2245
|
+
config.skills.entries[slug] = { ...existing, enabled: true };
|
|
2191
2246
|
writeConfig(config);
|
|
2192
2247
|
console.log(`[OpenClawConfig] Added skill entry: ${slug}`);
|
|
2193
2248
|
}
|
|
2194
2249
|
function removeSkillEntry(slug) {
|
|
2195
|
-
const config =
|
|
2250
|
+
const config = readOpenClawConfig();
|
|
2196
2251
|
if (config.skills?.entries?.[slug]) {
|
|
2197
|
-
|
|
2252
|
+
const existing = config.skills.entries[slug];
|
|
2253
|
+
delete existing.env;
|
|
2254
|
+
config.skills.entries[slug] = { ...existing, enabled: false };
|
|
2198
2255
|
writeConfig(config);
|
|
2199
|
-
console.log(`[OpenClawConfig]
|
|
2256
|
+
console.log(`[OpenClawConfig] Disabled skill entry: ${slug}`);
|
|
2200
2257
|
}
|
|
2201
2258
|
}
|
|
2202
2259
|
function syncOpenClawFromPolicies(policies) {
|
|
2203
|
-
const config =
|
|
2260
|
+
const config = readOpenClawConfig();
|
|
2204
2261
|
if (!config.skills) config.skills = {};
|
|
2205
2262
|
const allowBundled = [];
|
|
2206
2263
|
for (const p of policies) {
|
|
@@ -2419,7 +2476,7 @@ function generatePolicyMarkdown(policies, knownSkills) {
|
|
|
2419
2476
|
// libs/shield-daemon/src/watchers/skills.ts
|
|
2420
2477
|
import * as fs11 from "node:fs";
|
|
2421
2478
|
import * as path11 from "node:path";
|
|
2422
|
-
import * as
|
|
2479
|
+
import * as crypto4 from "node:crypto";
|
|
2423
2480
|
import { parseSkillMd } from "@agenshield/sandbox";
|
|
2424
2481
|
|
|
2425
2482
|
// libs/shield-daemon/src/services/marketplace.ts
|
|
@@ -2448,35 +2505,35 @@ var ANALYSIS_TIMEOUT = 4 * 6e4;
|
|
|
2448
2505
|
var SEARCH_CACHE_TTL = 6e4;
|
|
2449
2506
|
var DETAIL_CACHE_TTL = 5 * 6e4;
|
|
2450
2507
|
var SHORT_TIMEOUT = 1e4;
|
|
2451
|
-
async function convexAction(
|
|
2508
|
+
async function convexAction(path24, args, timeout) {
|
|
2452
2509
|
const res = await fetch(`${CONVEX_BASE}/api/action`, {
|
|
2453
2510
|
method: "POST",
|
|
2454
2511
|
signal: AbortSignal.timeout(timeout),
|
|
2455
2512
|
headers: { "Content-Type": "application/json" },
|
|
2456
|
-
body: JSON.stringify({ path:
|
|
2513
|
+
body: JSON.stringify({ path: path24, args, format: "json" })
|
|
2457
2514
|
});
|
|
2458
2515
|
if (!res.ok) {
|
|
2459
|
-
throw new Error(`Convex action ${
|
|
2516
|
+
throw new Error(`Convex action ${path24} returned ${res.status}`);
|
|
2460
2517
|
}
|
|
2461
2518
|
const body = await res.json();
|
|
2462
2519
|
if (body.status === "error") {
|
|
2463
|
-
throw new Error(`Convex action ${
|
|
2520
|
+
throw new Error(`Convex action ${path24}: ${body.errorMessage ?? "unknown error"}`);
|
|
2464
2521
|
}
|
|
2465
2522
|
return body.value;
|
|
2466
2523
|
}
|
|
2467
|
-
async function convexQuery(
|
|
2524
|
+
async function convexQuery(path24, args, timeout) {
|
|
2468
2525
|
const res = await fetch(`${CONVEX_BASE}/api/query`, {
|
|
2469
2526
|
method: "POST",
|
|
2470
2527
|
signal: AbortSignal.timeout(timeout),
|
|
2471
2528
|
headers: { "Content-Type": "application/json" },
|
|
2472
|
-
body: JSON.stringify({ path:
|
|
2529
|
+
body: JSON.stringify({ path: path24, args, format: "json" })
|
|
2473
2530
|
});
|
|
2474
2531
|
if (!res.ok) {
|
|
2475
|
-
throw new Error(`Convex query ${
|
|
2532
|
+
throw new Error(`Convex query ${path24} returned ${res.status}`);
|
|
2476
2533
|
}
|
|
2477
2534
|
const body = await res.json();
|
|
2478
2535
|
if (body.status === "error") {
|
|
2479
|
-
throw new Error(`Convex query ${
|
|
2536
|
+
throw new Error(`Convex query ${path24}: ${body.errorMessage ?? "unknown error"}`);
|
|
2480
2537
|
}
|
|
2481
2538
|
return body.value;
|
|
2482
2539
|
}
|
|
@@ -2608,6 +2665,17 @@ function updateDownloadedAnalysis(slug, analysis) {
|
|
|
2608
2665
|
} catch {
|
|
2609
2666
|
}
|
|
2610
2667
|
}
|
|
2668
|
+
function markDownloadedAsInstalled(slug) {
|
|
2669
|
+
const metaPath = path9.join(getMarketplaceDir(), slug, "metadata.json");
|
|
2670
|
+
try {
|
|
2671
|
+
if (!fs9.existsSync(metaPath)) return;
|
|
2672
|
+
const meta = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
|
|
2673
|
+
if (meta.wasInstalled) return;
|
|
2674
|
+
meta.wasInstalled = true;
|
|
2675
|
+
fs9.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
2676
|
+
} catch {
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2611
2679
|
function listDownloadedSkills() {
|
|
2612
2680
|
const baseDir = getMarketplaceDir();
|
|
2613
2681
|
if (!fs9.existsSync(baseDir)) return [];
|
|
@@ -2628,7 +2696,8 @@ function listDownloadedSkills() {
|
|
|
2628
2696
|
tags: meta.tags ?? [],
|
|
2629
2697
|
hasAnalysis: !!meta.analysis,
|
|
2630
2698
|
source: meta.source,
|
|
2631
|
-
analysis: meta.analysis
|
|
2699
|
+
analysis: meta.analysis,
|
|
2700
|
+
wasInstalled: meta.wasInstalled ?? false
|
|
2632
2701
|
});
|
|
2633
2702
|
} catch {
|
|
2634
2703
|
}
|
|
@@ -2678,9 +2747,9 @@ function inlineImagesInMarkdown(markdown, files) {
|
|
|
2678
2747
|
const mime = isImageExt(file.name);
|
|
2679
2748
|
if (mime && file.content.startsWith("data:")) {
|
|
2680
2749
|
imageMap.set(file.name, file.content);
|
|
2681
|
-
const
|
|
2682
|
-
if (
|
|
2683
|
-
imageMap.set(
|
|
2750
|
+
const basename6 = file.name.split("/").pop() ?? "";
|
|
2751
|
+
if (basename6 && !imageMap.has(basename6)) {
|
|
2752
|
+
imageMap.set(basename6, file.content);
|
|
2684
2753
|
}
|
|
2685
2754
|
}
|
|
2686
2755
|
}
|
|
@@ -3206,10 +3275,10 @@ function emitSecurityWarning(warning) {
|
|
|
3206
3275
|
function emitSecurityCritical(issue) {
|
|
3207
3276
|
daemonEvents.broadcast("security:critical", { message: issue });
|
|
3208
3277
|
}
|
|
3209
|
-
function emitApiRequest(method,
|
|
3278
|
+
function emitApiRequest(method, path24, statusCode, duration, requestBody, responseBody) {
|
|
3210
3279
|
daemonEvents.broadcast("api:request", {
|
|
3211
3280
|
method,
|
|
3212
|
-
path:
|
|
3281
|
+
path: path24,
|
|
3213
3282
|
statusCode,
|
|
3214
3283
|
duration,
|
|
3215
3284
|
...requestBody !== void 0 && { requestBody },
|
|
@@ -3225,6 +3294,9 @@ function emitSkillUntrustedDetected(name, reason) {
|
|
|
3225
3294
|
function emitSkillApproved(skillName) {
|
|
3226
3295
|
daemonEvents.broadcast("skills:approved", { name: skillName });
|
|
3227
3296
|
}
|
|
3297
|
+
function emitExecDenied(command, reason) {
|
|
3298
|
+
daemonEvents.broadcast("exec:denied", { command, reason });
|
|
3299
|
+
}
|
|
3228
3300
|
function emitAgenCoAuthRequired(authUrl, integration) {
|
|
3229
3301
|
daemonEvents.broadcast("agenco:auth_required", { authUrl, integration });
|
|
3230
3302
|
}
|
|
@@ -3264,6 +3336,56 @@ function emitEvent(type, data) {
|
|
|
3264
3336
|
|
|
3265
3337
|
// libs/shield-daemon/src/watchers/skills.ts
|
|
3266
3338
|
init_paths();
|
|
3339
|
+
|
|
3340
|
+
// libs/shield-daemon/src/services/skill-tag-injector.ts
|
|
3341
|
+
init_installation_key();
|
|
3342
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
3343
|
+
var FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
|
|
3344
|
+
async function injectInstallationTag(content) {
|
|
3345
|
+
const tag = await getInstallationTag();
|
|
3346
|
+
const match = content.match(FRONTMATTER_RE);
|
|
3347
|
+
if (!match) {
|
|
3348
|
+
return `---
|
|
3349
|
+
tags:
|
|
3350
|
+
- ${tag}
|
|
3351
|
+
---
|
|
3352
|
+
${content}`;
|
|
3353
|
+
}
|
|
3354
|
+
try {
|
|
3355
|
+
const metadata = parseYaml(match[1]);
|
|
3356
|
+
if (!metadata || typeof metadata !== "object") {
|
|
3357
|
+
return content;
|
|
3358
|
+
}
|
|
3359
|
+
if (!Array.isArray(metadata.tags)) {
|
|
3360
|
+
metadata.tags = [];
|
|
3361
|
+
}
|
|
3362
|
+
metadata.tags = metadata.tags.filter(
|
|
3363
|
+
(t) => typeof t !== "string" || !t.startsWith("agenshield-")
|
|
3364
|
+
);
|
|
3365
|
+
metadata.tags.push(tag);
|
|
3366
|
+
return `---
|
|
3367
|
+
${stringifyYaml(metadata).trimEnd()}
|
|
3368
|
+
---
|
|
3369
|
+
${match[2]}`;
|
|
3370
|
+
} catch {
|
|
3371
|
+
return content;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
function extractTagsFromSkillMd(content) {
|
|
3375
|
+
const match = content.match(FRONTMATTER_RE);
|
|
3376
|
+
if (!match) return [];
|
|
3377
|
+
try {
|
|
3378
|
+
const metadata = parseYaml(match[1]);
|
|
3379
|
+
if (!metadata || typeof metadata !== "object") return [];
|
|
3380
|
+
if (!Array.isArray(metadata.tags)) return [];
|
|
3381
|
+
return metadata.tags.filter((t) => typeof t === "string");
|
|
3382
|
+
} catch {
|
|
3383
|
+
return [];
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// libs/shield-daemon/src/watchers/skills.ts
|
|
3388
|
+
init_installation_key();
|
|
3267
3389
|
function getApprovedSkillsPath() {
|
|
3268
3390
|
return path11.join(getSystemConfigDir(), "approved-skills.json");
|
|
3269
3391
|
}
|
|
@@ -3402,7 +3524,7 @@ function computeSkillHash(skillDir) {
|
|
|
3402
3524
|
const files = readSkillFiles(skillDir);
|
|
3403
3525
|
if (files.length === 0) return null;
|
|
3404
3526
|
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
3405
|
-
const hash =
|
|
3527
|
+
const hash = crypto4.createHash("sha256");
|
|
3406
3528
|
for (const file of files) {
|
|
3407
3529
|
hash.update(file.name);
|
|
3408
3530
|
hash.update(file.content);
|
|
@@ -3431,10 +3553,33 @@ function scanSkills() {
|
|
|
3431
3553
|
const approvedEntry = approvedMap.get(skillName);
|
|
3432
3554
|
if (!approvedEntry) {
|
|
3433
3555
|
const fullPath = path11.join(skillsDir, skillName);
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3556
|
+
let autoApproved = false;
|
|
3557
|
+
for (const mdName of ["SKILL.md", "skill.md"]) {
|
|
3558
|
+
const mdPath = path11.join(fullPath, mdName);
|
|
3559
|
+
try {
|
|
3560
|
+
if (fs11.existsSync(mdPath)) {
|
|
3561
|
+
const content = fs11.readFileSync(mdPath, "utf-8");
|
|
3562
|
+
const tags = extractTagsFromSkillMd(content);
|
|
3563
|
+
if (hasValidInstallationTagSync(tags)) {
|
|
3564
|
+
console.log(`[SkillsWatcher] Auto-approving skill with valid installation tag: ${skillName}`);
|
|
3565
|
+
const hash = computeSkillHash(fullPath);
|
|
3566
|
+
addToApprovedList(skillName, void 0, hash ?? void 0);
|
|
3567
|
+
if (callbacks.onApproved) {
|
|
3568
|
+
callbacks.onApproved(skillName);
|
|
3569
|
+
}
|
|
3570
|
+
autoApproved = true;
|
|
3571
|
+
break;
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
} catch {
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
if (!autoApproved) {
|
|
3578
|
+
const slug = moveToMarketplace(skillName, fullPath);
|
|
3579
|
+
if (slug) {
|
|
3580
|
+
if (callbacks.onUntrustedDetected) {
|
|
3581
|
+
callbacks.onUntrustedDetected({ name: skillName, reason: "Skill not in approved list" });
|
|
3582
|
+
}
|
|
3438
3583
|
}
|
|
3439
3584
|
}
|
|
3440
3585
|
} else if (approvedEntry.hash) {
|
|
@@ -3464,26 +3609,16 @@ function scanSkills() {
|
|
|
3464
3609
|
}
|
|
3465
3610
|
function detectOpenClawMismatches() {
|
|
3466
3611
|
try {
|
|
3467
|
-
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
3468
|
-
const configPath = path11.join(agentHome, ".openclaw", "openclaw.json");
|
|
3469
|
-
if (!fs11.existsSync(configPath)) return;
|
|
3470
|
-
const raw = fs11.readFileSync(configPath, "utf-8");
|
|
3471
|
-
const config = JSON.parse(raw);
|
|
3472
|
-
if (!config.skills?.entries) return;
|
|
3473
3612
|
const approved = loadApprovedSkills();
|
|
3474
3613
|
const approvedNames = new Set(approved.map((a) => a.name));
|
|
3475
|
-
const
|
|
3476
|
-
|
|
3477
|
-
for (const name of entries) {
|
|
3614
|
+
const config = readOpenClawConfig();
|
|
3615
|
+
if (!config.skills?.entries) return;
|
|
3616
|
+
for (const name of Object.keys(config.skills.entries)) {
|
|
3478
3617
|
if (!approvedNames.has(name)) {
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
console.log(`[SkillsWatcher] Removed stale openclaw.json entry: ${name}`);
|
|
3618
|
+
removeSkillEntry(name);
|
|
3619
|
+
console.log(`[SkillsWatcher] Disabled stale openclaw.json entry: ${name}`);
|
|
3482
3620
|
}
|
|
3483
3621
|
}
|
|
3484
|
-
if (changed) {
|
|
3485
|
-
fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
3486
|
-
}
|
|
3487
3622
|
} catch {
|
|
3488
3623
|
}
|
|
3489
3624
|
}
|
|
@@ -3550,7 +3685,7 @@ function approveSkill(skillName) {
|
|
|
3550
3685
|
const cachedFiles = getDownloadedSkillFiles(slug);
|
|
3551
3686
|
let hash;
|
|
3552
3687
|
if (cachedFiles.length > 0) {
|
|
3553
|
-
const h =
|
|
3688
|
+
const h = crypto4.createHash("sha256");
|
|
3554
3689
|
const sorted = [...cachedFiles].sort((a, b) => a.name.localeCompare(b.name));
|
|
3555
3690
|
for (const file of sorted) {
|
|
3556
3691
|
h.update(file.name);
|
|
@@ -3622,14 +3757,15 @@ function triggerSkillsScan() {
|
|
|
3622
3757
|
function getSkillsDir() {
|
|
3623
3758
|
return skillsDir;
|
|
3624
3759
|
}
|
|
3625
|
-
function addToApprovedList(skillName, publisher, hash) {
|
|
3760
|
+
function addToApprovedList(skillName, publisher, hash, slug) {
|
|
3626
3761
|
const approved = loadApprovedSkills();
|
|
3627
3762
|
if (!approved.some((s) => s.name === skillName)) {
|
|
3628
3763
|
approved.push({
|
|
3629
3764
|
name: skillName,
|
|
3630
3765
|
approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3631
3766
|
...publisher ? { publisher } : {},
|
|
3632
|
-
...hash ? { hash } : {}
|
|
3767
|
+
...hash ? { hash } : {},
|
|
3768
|
+
...slug ? { slug } : {}
|
|
3633
3769
|
});
|
|
3634
3770
|
saveApprovedSkills(approved);
|
|
3635
3771
|
}
|
|
@@ -3876,7 +4012,7 @@ async function securityRoutes(app) {
|
|
|
3876
4012
|
// libs/shield-daemon/src/auth/passcode.ts
|
|
3877
4013
|
init_vault();
|
|
3878
4014
|
init_state();
|
|
3879
|
-
import * as
|
|
4015
|
+
import * as crypto5 from "node:crypto";
|
|
3880
4016
|
import { DEFAULT_AUTH_CONFIG as DEFAULT_AUTH_CONFIG2 } from "@agenshield/ipc";
|
|
3881
4017
|
var ITERATIONS = 1e5;
|
|
3882
4018
|
var KEY_LENGTH = 64;
|
|
@@ -3884,8 +4020,8 @@ var DIGEST = "sha512";
|
|
|
3884
4020
|
var SALT_LENGTH = 16;
|
|
3885
4021
|
function hashPasscode(passcode) {
|
|
3886
4022
|
return new Promise((resolve3, reject) => {
|
|
3887
|
-
const salt =
|
|
3888
|
-
|
|
4023
|
+
const salt = crypto5.randomBytes(SALT_LENGTH);
|
|
4024
|
+
crypto5.pbkdf2(passcode, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => {
|
|
3889
4025
|
if (err) {
|
|
3890
4026
|
reject(err);
|
|
3891
4027
|
return;
|
|
@@ -3905,12 +4041,12 @@ function verifyPasscode(passcode, storedHash) {
|
|
|
3905
4041
|
const iterations = parseInt(parts[0], 10);
|
|
3906
4042
|
const salt = Buffer.from(parts[1], "base64");
|
|
3907
4043
|
const hash = Buffer.from(parts[2], "base64");
|
|
3908
|
-
|
|
4044
|
+
crypto5.pbkdf2(passcode, salt, iterations, hash.length, DIGEST, (err, derivedKey) => {
|
|
3909
4045
|
if (err) {
|
|
3910
4046
|
reject(err);
|
|
3911
4047
|
return;
|
|
3912
4048
|
}
|
|
3913
|
-
resolve3(
|
|
4049
|
+
resolve3(crypto5.timingSafeEqual(hash, derivedKey));
|
|
3914
4050
|
});
|
|
3915
4051
|
});
|
|
3916
4052
|
}
|
|
@@ -4042,16 +4178,16 @@ var PROTECTED_ROUTES = [
|
|
|
4042
4178
|
{ method: "POST", path: "/api/skills/install" },
|
|
4043
4179
|
{ method: "GET", path: "/api/openclaw/dashboard-url" }
|
|
4044
4180
|
];
|
|
4045
|
-
function isProtectedRoute(method,
|
|
4181
|
+
function isProtectedRoute(method, path24) {
|
|
4046
4182
|
return PROTECTED_ROUTES.some(
|
|
4047
|
-
(route) => route.method === method &&
|
|
4183
|
+
(route) => route.method === method && path24.startsWith(route.path)
|
|
4048
4184
|
);
|
|
4049
4185
|
}
|
|
4050
4186
|
function createAuthHook() {
|
|
4051
4187
|
return async (request2, reply) => {
|
|
4052
4188
|
const method = request2.method;
|
|
4053
|
-
const
|
|
4054
|
-
if (!isProtectedRoute(method,
|
|
4189
|
+
const path24 = request2.url.split("?")[0];
|
|
4190
|
+
if (!isProtectedRoute(method, path24)) {
|
|
4055
4191
|
return;
|
|
4056
4192
|
}
|
|
4057
4193
|
if (!isAuthenticated(request2)) {
|
|
@@ -4082,6 +4218,12 @@ data: ${data}
|
|
|
4082
4218
|
|
|
4083
4219
|
`;
|
|
4084
4220
|
}
|
|
4221
|
+
var ALWAYS_FULL_PREFIXES = ["skills:", "exec:", "interceptor:", "security:", "wrappers:", "process:", "config:"];
|
|
4222
|
+
function shouldSendFull(event, authenticated) {
|
|
4223
|
+
if (authenticated) return true;
|
|
4224
|
+
if (event.type === "heartbeat" || event.type === "daemon:status") return true;
|
|
4225
|
+
return ALWAYS_FULL_PREFIXES.some((p) => event.type.startsWith(p));
|
|
4226
|
+
}
|
|
4085
4227
|
async function sseRoutes(app) {
|
|
4086
4228
|
app.get("/sse/events", async (request2, reply) => {
|
|
4087
4229
|
const authenticated = isAuthenticated(request2);
|
|
@@ -4107,7 +4249,7 @@ async function sseRoutes(app) {
|
|
|
4107
4249
|
reply.raw.write(authenticated ? formatSSE(statusEvent) : formatStrippedSSE(statusEvent));
|
|
4108
4250
|
const unsubscribe = daemonEvents.subscribe((event) => {
|
|
4109
4251
|
try {
|
|
4110
|
-
if (event
|
|
4252
|
+
if (shouldSendFull(event, authenticated)) {
|
|
4111
4253
|
reply.raw.write(formatSSE(event));
|
|
4112
4254
|
} else {
|
|
4113
4255
|
reply.raw.write(formatStrippedSSE(event));
|
|
@@ -4155,7 +4297,7 @@ async function sseRoutes(app) {
|
|
|
4155
4297
|
const unsubscribe = daemonEvents.subscribe((event) => {
|
|
4156
4298
|
if (event.type.startsWith(filter) || event.type === "heartbeat" || event.type === "daemon:status") {
|
|
4157
4299
|
try {
|
|
4158
|
-
if (event
|
|
4300
|
+
if (shouldSendFull(event, authenticated)) {
|
|
4159
4301
|
reply.raw.write(formatSSE(event));
|
|
4160
4302
|
} else {
|
|
4161
4303
|
reply.raw.write(formatStrippedSSE(event));
|
|
@@ -7550,19 +7692,50 @@ async function agencoRoutes(app) {
|
|
|
7550
7692
|
}
|
|
7551
7693
|
|
|
7552
7694
|
// libs/shield-daemon/src/routes/skills.ts
|
|
7553
|
-
import * as
|
|
7554
|
-
import * as
|
|
7695
|
+
import * as fs17 from "node:fs";
|
|
7696
|
+
import * as path17 from "node:path";
|
|
7555
7697
|
import { execSync as execSync9 } from "node:child_process";
|
|
7556
|
-
import { parseSkillMd as
|
|
7698
|
+
import { parseSkillMd as parseSkillMd3, stripEnvFromSkillMd as stripEnvFromSkillMd2 } from "@agenshield/sandbox";
|
|
7557
7699
|
|
|
7558
7700
|
// libs/shield-daemon/src/services/skill-lifecycle.ts
|
|
7559
7701
|
import * as fs14 from "node:fs";
|
|
7560
7702
|
import * as path14 from "node:path";
|
|
7561
7703
|
import { execSync as execSync8 } from "node:child_process";
|
|
7562
|
-
function
|
|
7563
|
-
|
|
7564
|
-
fs14.mkdirSync(
|
|
7704
|
+
function sudoMkdir(dir, agentUsername) {
|
|
7705
|
+
try {
|
|
7706
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
7707
|
+
} catch (err) {
|
|
7708
|
+
if (err.code === "EACCES") {
|
|
7709
|
+
execSync8(`sudo -H -u ${agentUsername} /bin/mkdir -p "${dir}"`, { cwd: "/", stdio: "pipe" });
|
|
7710
|
+
} else {
|
|
7711
|
+
throw err;
|
|
7712
|
+
}
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
function sudoWriteFile(filePath, content, agentUsername, mode) {
|
|
7716
|
+
try {
|
|
7717
|
+
fs14.writeFileSync(filePath, content, { mode });
|
|
7718
|
+
} catch (err) {
|
|
7719
|
+
if (err.code === "EACCES") {
|
|
7720
|
+
execSync8(
|
|
7721
|
+
`sudo -H -u ${agentUsername} tee "${filePath}" > /dev/null`,
|
|
7722
|
+
{ input: content, cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
|
|
7723
|
+
);
|
|
7724
|
+
if (mode) {
|
|
7725
|
+
try {
|
|
7726
|
+
execSync8(`sudo -H -u ${agentUsername} chmod ${mode.toString(8)} "${filePath}"`, { cwd: "/", stdio: "pipe" });
|
|
7727
|
+
} catch {
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
} else {
|
|
7731
|
+
throw err;
|
|
7732
|
+
}
|
|
7565
7733
|
}
|
|
7734
|
+
}
|
|
7735
|
+
function createSkillWrapper(name, binDir) {
|
|
7736
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
7737
|
+
const agentUsername = path14.basename(agentHome);
|
|
7738
|
+
sudoMkdir(binDir, agentUsername);
|
|
7566
7739
|
const wrapperPath = path14.join(binDir, name);
|
|
7567
7740
|
const wrapperContent = `#!/bin/bash
|
|
7568
7741
|
# ${name} skill wrapper - policy-enforced execution
|
|
@@ -7570,7 +7743,7 @@ function createSkillWrapper(name, binDir) {
|
|
|
7570
7743
|
if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
|
|
7571
7744
|
exec /opt/agenshield/bin/shield-client skill run "${name}" "$@"
|
|
7572
7745
|
`;
|
|
7573
|
-
|
|
7746
|
+
sudoWriteFile(wrapperPath, wrapperContent, agentUsername, 493);
|
|
7574
7747
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
7575
7748
|
try {
|
|
7576
7749
|
execSync8(`chown root:${socketGroup} "${wrapperPath}"`, { stdio: "pipe" });
|
|
@@ -7615,8 +7788,8 @@ function removeSkillPolicy(name) {
|
|
|
7615
7788
|
}
|
|
7616
7789
|
|
|
7617
7790
|
// libs/shield-daemon/src/routes/marketplace.ts
|
|
7618
|
-
import * as
|
|
7619
|
-
import * as
|
|
7791
|
+
import * as fs16 from "node:fs";
|
|
7792
|
+
import * as path16 from "node:path";
|
|
7620
7793
|
|
|
7621
7794
|
// libs/shield-broker/dist/index.js
|
|
7622
7795
|
import { exec as exec4 } from "node:child_process";
|
|
@@ -7892,6 +8065,176 @@ async function uninstallSkillViaBroker(slug, options = {}) {
|
|
|
7892
8065
|
return result;
|
|
7893
8066
|
}
|
|
7894
8067
|
|
|
8068
|
+
// libs/shield-daemon/src/routes/marketplace.ts
|
|
8069
|
+
import { stripEnvFromSkillMd } from "@agenshield/sandbox";
|
|
8070
|
+
|
|
8071
|
+
// libs/shield-daemon/src/services/skill-deps.ts
|
|
8072
|
+
import * as fs15 from "node:fs";
|
|
8073
|
+
import * as path15 from "node:path";
|
|
8074
|
+
import { parseSkillMd as parseSkillMd2, extractSkillInfo } from "@agenshield/sandbox";
|
|
8075
|
+
import { execWithProgress as execWithProgress3 } from "@agenshield/sandbox";
|
|
8076
|
+
var SUPPORTED_KINDS = /* @__PURE__ */ new Set(["brew", "npm", "pip"]);
|
|
8077
|
+
var SAFE_PACKAGE_RE = /^[a-zA-Z0-9@/_.\-]+$/;
|
|
8078
|
+
var SYSTEM_PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
8079
|
+
function findSkillMdRecursive(dir, depth = 0) {
|
|
8080
|
+
if (depth > 3) return null;
|
|
8081
|
+
try {
|
|
8082
|
+
for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
|
|
8083
|
+
const candidate = path15.join(dir, name);
|
|
8084
|
+
if (fs15.existsSync(candidate)) return candidate;
|
|
8085
|
+
}
|
|
8086
|
+
const entries = fs15.readdirSync(dir, { withFileTypes: true });
|
|
8087
|
+
for (const entry of entries) {
|
|
8088
|
+
if (!entry.isDirectory()) continue;
|
|
8089
|
+
const found = findSkillMdRecursive(path15.join(dir, entry.name), depth + 1);
|
|
8090
|
+
if (found) return found;
|
|
8091
|
+
}
|
|
8092
|
+
} catch {
|
|
8093
|
+
}
|
|
8094
|
+
return null;
|
|
8095
|
+
}
|
|
8096
|
+
async function executeSkillInstallSteps(options) {
|
|
8097
|
+
const { slug, skillDir, agentHome, agentUsername, onLog } = options;
|
|
8098
|
+
const installed = [];
|
|
8099
|
+
const errors = [];
|
|
8100
|
+
const skillMdPath = findSkillMdRecursive(skillDir);
|
|
8101
|
+
if (!skillMdPath) {
|
|
8102
|
+
return { success: true, installed, errors };
|
|
8103
|
+
}
|
|
8104
|
+
let content;
|
|
8105
|
+
try {
|
|
8106
|
+
content = fs15.readFileSync(skillMdPath, "utf-8");
|
|
8107
|
+
} catch {
|
|
8108
|
+
return { success: true, installed, errors };
|
|
8109
|
+
}
|
|
8110
|
+
const parsed = parseSkillMd2(content);
|
|
8111
|
+
if (!parsed) {
|
|
8112
|
+
return { success: true, installed, errors };
|
|
8113
|
+
}
|
|
8114
|
+
const info = extractSkillInfo(parsed.metadata);
|
|
8115
|
+
const installSteps = info.installSteps;
|
|
8116
|
+
if (!Array.isArray(installSteps) || installSteps.length === 0) {
|
|
8117
|
+
return { success: true, installed, errors };
|
|
8118
|
+
}
|
|
8119
|
+
onLog(`Found ${installSteps.length} dependency install step(s) for ${slug}`);
|
|
8120
|
+
for (const step of installSteps) {
|
|
8121
|
+
const kind = step.kind;
|
|
8122
|
+
const stepId = step.id || kind;
|
|
8123
|
+
if (!SUPPORTED_KINDS.has(kind)) {
|
|
8124
|
+
errors.push(`Unsupported install kind "${kind}" (step: ${stepId})`);
|
|
8125
|
+
continue;
|
|
8126
|
+
}
|
|
8127
|
+
try {
|
|
8128
|
+
switch (kind) {
|
|
8129
|
+
case "brew": {
|
|
8130
|
+
const formula = step.formula;
|
|
8131
|
+
if (!formula) {
|
|
8132
|
+
errors.push(`Brew step "${stepId}" missing formula`);
|
|
8133
|
+
break;
|
|
8134
|
+
}
|
|
8135
|
+
if (!SAFE_PACKAGE_RE.test(formula)) {
|
|
8136
|
+
errors.push(`Unsafe brew formula name: ${formula}`);
|
|
8137
|
+
break;
|
|
8138
|
+
}
|
|
8139
|
+
onLog(`Installing brew formula: ${formula}`);
|
|
8140
|
+
const brewCmd = [
|
|
8141
|
+
`export HOME="${agentHome}"`,
|
|
8142
|
+
`export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8143
|
+
`brew install ${formula}`
|
|
8144
|
+
].join(" && ");
|
|
8145
|
+
await execWithProgress3(
|
|
8146
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${brewCmd}'`,
|
|
8147
|
+
onLog,
|
|
8148
|
+
{ timeout: 12e4, cwd: "/" }
|
|
8149
|
+
);
|
|
8150
|
+
installed.push(formula);
|
|
8151
|
+
break;
|
|
8152
|
+
}
|
|
8153
|
+
case "npm": {
|
|
8154
|
+
const pkg = step["package"];
|
|
8155
|
+
if (!pkg) {
|
|
8156
|
+
errors.push(`npm step "${stepId}" missing package`);
|
|
8157
|
+
break;
|
|
8158
|
+
}
|
|
8159
|
+
if (!SAFE_PACKAGE_RE.test(pkg)) {
|
|
8160
|
+
errors.push(`Unsafe npm package name: ${pkg}`);
|
|
8161
|
+
break;
|
|
8162
|
+
}
|
|
8163
|
+
onLog(`Installing npm package: ${pkg}`);
|
|
8164
|
+
const npmCmd = [
|
|
8165
|
+
`export HOME="${agentHome}"`,
|
|
8166
|
+
`export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8167
|
+
`source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
|
|
8168
|
+
`npm install -g ${pkg}`
|
|
8169
|
+
].join(" && ");
|
|
8170
|
+
await execWithProgress3(
|
|
8171
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${npmCmd}'`,
|
|
8172
|
+
onLog,
|
|
8173
|
+
{ timeout: 6e4, cwd: "/" }
|
|
8174
|
+
);
|
|
8175
|
+
installed.push(pkg);
|
|
8176
|
+
break;
|
|
8177
|
+
}
|
|
8178
|
+
case "pip": {
|
|
8179
|
+
const pkg = step["package"];
|
|
8180
|
+
if (!pkg) {
|
|
8181
|
+
errors.push(`pip step "${stepId}" missing package`);
|
|
8182
|
+
break;
|
|
8183
|
+
}
|
|
8184
|
+
if (!SAFE_PACKAGE_RE.test(pkg)) {
|
|
8185
|
+
errors.push(`Unsafe pip package name: ${pkg}`);
|
|
8186
|
+
break;
|
|
8187
|
+
}
|
|
8188
|
+
onLog(`Installing pip package: ${pkg}`);
|
|
8189
|
+
const pipCmd = [
|
|
8190
|
+
`export HOME="${agentHome}"`,
|
|
8191
|
+
`export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8192
|
+
`pip install ${pkg}`
|
|
8193
|
+
].join(" && ");
|
|
8194
|
+
await execWithProgress3(
|
|
8195
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${pipCmd}'`,
|
|
8196
|
+
onLog,
|
|
8197
|
+
{ timeout: 6e4, cwd: "/" }
|
|
8198
|
+
);
|
|
8199
|
+
installed.push(pkg);
|
|
8200
|
+
break;
|
|
8201
|
+
}
|
|
8202
|
+
}
|
|
8203
|
+
} catch (err) {
|
|
8204
|
+
const msg = `Failed to install ${kind} dep (step: ${stepId}): ${err.message}`;
|
|
8205
|
+
onLog(msg);
|
|
8206
|
+
errors.push(msg);
|
|
8207
|
+
}
|
|
8208
|
+
}
|
|
8209
|
+
const requiredBins = info.bins;
|
|
8210
|
+
if (requiredBins.length > 0) {
|
|
8211
|
+
onLog(`Verifying required binaries: ${requiredBins.join(", ")}`);
|
|
8212
|
+
for (const bin of requiredBins) {
|
|
8213
|
+
try {
|
|
8214
|
+
const checkCmd = [
|
|
8215
|
+
`export HOME="${agentHome}"`,
|
|
8216
|
+
`export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8217
|
+
`source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
|
|
8218
|
+
`which ${bin}`
|
|
8219
|
+
].join(" && ");
|
|
8220
|
+
await execWithProgress3(
|
|
8221
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${checkCmd}'`,
|
|
8222
|
+
() => {
|
|
8223
|
+
},
|
|
8224
|
+
{ timeout: 5e3, cwd: "/" }
|
|
8225
|
+
);
|
|
8226
|
+
} catch {
|
|
8227
|
+
errors.push(`Required binary "${bin}" not found in agent PATH after install`);
|
|
8228
|
+
}
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
return {
|
|
8232
|
+
success: errors.length === 0,
|
|
8233
|
+
installed,
|
|
8234
|
+
errors
|
|
8235
|
+
};
|
|
8236
|
+
}
|
|
8237
|
+
|
|
7895
8238
|
// libs/shield-daemon/src/routes/marketplace.ts
|
|
7896
8239
|
var installInProgress = /* @__PURE__ */ new Set();
|
|
7897
8240
|
function isInstallInProgress(slug) {
|
|
@@ -8146,7 +8489,7 @@ async function marketplaceRoutes(app) {
|
|
|
8146
8489
|
error: "Critical vulnerability detected",
|
|
8147
8490
|
analysis: analysisResult
|
|
8148
8491
|
});
|
|
8149
|
-
return { success: false, name: slug, analysis: analysisResult, logs };
|
|
8492
|
+
return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
|
|
8150
8493
|
}
|
|
8151
8494
|
emitSkillInstallProgress(slug, "download", "Downloading skill files");
|
|
8152
8495
|
const skill = await getMarketplaceSkill(slug);
|
|
@@ -8156,7 +8499,7 @@ async function marketplaceRoutes(app) {
|
|
|
8156
8499
|
name: slug,
|
|
8157
8500
|
error: "No files available for installation"
|
|
8158
8501
|
});
|
|
8159
|
-
return { success: false, name: slug, analysis: analysisResult, logs };
|
|
8502
|
+
return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
|
|
8160
8503
|
}
|
|
8161
8504
|
const publisher = skill.author;
|
|
8162
8505
|
logs.push("Downloaded skill files");
|
|
@@ -8165,18 +8508,27 @@ async function marketplaceRoutes(app) {
|
|
|
8165
8508
|
throw new Error("Skills directory not configured");
|
|
8166
8509
|
}
|
|
8167
8510
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8168
|
-
const
|
|
8511
|
+
const agentUsername = path16.basename(agentHome);
|
|
8512
|
+
const binDir = path16.join(agentHome, "bin");
|
|
8169
8513
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8170
|
-
skillDir =
|
|
8514
|
+
skillDir = path16.join(skillsDir2, slug);
|
|
8171
8515
|
emitSkillInstallProgress(slug, "approve", "Pre-approving skill");
|
|
8172
|
-
addToApprovedList(slug, publisher);
|
|
8516
|
+
addToApprovedList(slug, publisher, void 0, slug);
|
|
8173
8517
|
logs.push("Skill pre-approved");
|
|
8174
8518
|
emitSkillInstallProgress(slug, "copy", "Writing skill files");
|
|
8519
|
+
const taggedFiles = await Promise.all(files.map(async (f) => {
|
|
8520
|
+
let content = f.content;
|
|
8521
|
+
if (/SKILL\.md$/i.test(f.name)) {
|
|
8522
|
+
content = stripEnvFromSkillMd(content);
|
|
8523
|
+
content = await injectInstallationTag(content);
|
|
8524
|
+
}
|
|
8525
|
+
return { ...f, content };
|
|
8526
|
+
}));
|
|
8175
8527
|
const brokerAvailable = await isBrokerAvailable();
|
|
8176
8528
|
if (brokerAvailable) {
|
|
8177
8529
|
const brokerResult = await installSkillViaBroker(
|
|
8178
8530
|
slug,
|
|
8179
|
-
|
|
8531
|
+
taggedFiles.map((f) => ({ name: f.name, content: f.content })),
|
|
8180
8532
|
{ createWrapper: true, agentHome, socketGroup }
|
|
8181
8533
|
);
|
|
8182
8534
|
if (!brokerResult.installed) {
|
|
@@ -8195,18 +8547,65 @@ async function marketplaceRoutes(app) {
|
|
|
8195
8547
|
}
|
|
8196
8548
|
} else {
|
|
8197
8549
|
console.log(`[Marketplace] Broker unavailable, installing ${slug} directly`);
|
|
8198
|
-
|
|
8199
|
-
for (const file of
|
|
8200
|
-
const filePath =
|
|
8201
|
-
const fileDir =
|
|
8550
|
+
sudoMkdir(skillDir, agentUsername);
|
|
8551
|
+
for (const file of taggedFiles) {
|
|
8552
|
+
const filePath = path16.join(skillDir, file.name);
|
|
8553
|
+
const fileDir = path16.dirname(filePath);
|
|
8202
8554
|
if (fileDir !== skillDir) {
|
|
8203
|
-
|
|
8555
|
+
sudoMkdir(fileDir, agentUsername);
|
|
8204
8556
|
}
|
|
8205
|
-
|
|
8557
|
+
sudoWriteFile(filePath, file.content, agentUsername);
|
|
8206
8558
|
}
|
|
8207
8559
|
createSkillWrapper(slug, binDir);
|
|
8208
|
-
logs.push(`Files written directly: ${
|
|
8209
|
-
logs.push(`Wrapper created: ${
|
|
8560
|
+
logs.push(`Files written directly: ${taggedFiles.length} files`);
|
|
8561
|
+
logs.push(`Wrapper created: ${path16.join(binDir, slug)}`);
|
|
8562
|
+
}
|
|
8563
|
+
let depsSuccess = true;
|
|
8564
|
+
emitSkillInstallProgress(slug, "deps", "Installing skill dependencies");
|
|
8565
|
+
try {
|
|
8566
|
+
let depsLineCount = 0;
|
|
8567
|
+
let depsLastEmit = Date.now();
|
|
8568
|
+
let depsLastLine = "";
|
|
8569
|
+
const DEPS_DEBOUNCE_MS = 3e3;
|
|
8570
|
+
const depsOnLog = (msg) => {
|
|
8571
|
+
depsLineCount++;
|
|
8572
|
+
depsLastLine = msg;
|
|
8573
|
+
if (/^(Installing|Found|Verifying)\s/.test(msg)) {
|
|
8574
|
+
emitSkillInstallProgress(slug, "deps", msg);
|
|
8575
|
+
depsLastEmit = Date.now();
|
|
8576
|
+
return;
|
|
8577
|
+
}
|
|
8578
|
+
const now = Date.now();
|
|
8579
|
+
if (now - depsLastEmit >= DEPS_DEBOUNCE_MS) {
|
|
8580
|
+
emitSkillInstallProgress(slug, "deps", `Installing... (${depsLineCount} lines)`);
|
|
8581
|
+
depsLastEmit = now;
|
|
8582
|
+
}
|
|
8583
|
+
};
|
|
8584
|
+
const depsResult = await executeSkillInstallSteps({
|
|
8585
|
+
slug,
|
|
8586
|
+
skillDir,
|
|
8587
|
+
agentHome,
|
|
8588
|
+
agentUsername,
|
|
8589
|
+
onLog: depsOnLog
|
|
8590
|
+
});
|
|
8591
|
+
if (depsLineCount > 0) {
|
|
8592
|
+
emitSkillInstallProgress(slug, "deps", `Dependency install complete (${depsLineCount} lines processed)`);
|
|
8593
|
+
}
|
|
8594
|
+
if (depsResult.installed.length > 0) {
|
|
8595
|
+
logs.push(`Dependencies installed: ${depsResult.installed.join(", ")}`);
|
|
8596
|
+
}
|
|
8597
|
+
if (depsResult.errors.length > 0) {
|
|
8598
|
+
depsSuccess = false;
|
|
8599
|
+
for (const err of depsResult.errors) {
|
|
8600
|
+
emitSkillInstallProgress(slug, "warning", `Dependency warning: ${err}`);
|
|
8601
|
+
logs.push(`Dependency warning: ${err}`);
|
|
8602
|
+
}
|
|
8603
|
+
}
|
|
8604
|
+
} catch (err) {
|
|
8605
|
+
depsSuccess = false;
|
|
8606
|
+
const msg = `Dependency installation failed: ${err.message}`;
|
|
8607
|
+
emitSkillInstallProgress(slug, "warning", msg);
|
|
8608
|
+
logs.push(msg);
|
|
8210
8609
|
}
|
|
8211
8610
|
addSkillEntry(slug);
|
|
8212
8611
|
addSkillPolicy(slug);
|
|
@@ -8217,14 +8616,19 @@ async function marketplaceRoutes(app) {
|
|
|
8217
8616
|
updateApprovedHash(slug, hash);
|
|
8218
8617
|
logs.push("Integrity hash recorded");
|
|
8219
8618
|
}
|
|
8619
|
+
try {
|
|
8620
|
+
markDownloadedAsInstalled(slug);
|
|
8621
|
+
} catch {
|
|
8622
|
+
}
|
|
8220
8623
|
installInProgress.delete(slug);
|
|
8221
|
-
|
|
8624
|
+
const depsWarnings = depsSuccess ? void 0 : logs.filter((l) => l.startsWith("Dependency"));
|
|
8625
|
+
daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult, depsWarnings });
|
|
8222
8626
|
logs.push("Installation complete");
|
|
8223
|
-
return { success: true, name: slug, analysis: analysisResult, logs };
|
|
8627
|
+
return { success: true, name: slug, analysis: analysisResult, logs, depsSuccess };
|
|
8224
8628
|
} catch (err) {
|
|
8225
8629
|
try {
|
|
8226
|
-
if (skillDir &&
|
|
8227
|
-
|
|
8630
|
+
if (skillDir && fs16.existsSync(skillDir)) {
|
|
8631
|
+
fs16.rmSync(skillDir, { recursive: true, force: true });
|
|
8228
8632
|
}
|
|
8229
8633
|
removeFromApprovedList(slug);
|
|
8230
8634
|
} catch {
|
|
@@ -8233,7 +8637,7 @@ async function marketplaceRoutes(app) {
|
|
|
8233
8637
|
installInProgress.delete(slug);
|
|
8234
8638
|
daemonEvents.broadcast("skills:install_failed", { name: slug, error: errorMsg });
|
|
8235
8639
|
console.error("[Marketplace] Install failed:", errorMsg);
|
|
8236
|
-
return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`] };
|
|
8640
|
+
return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`], depsSuccess: void 0 };
|
|
8237
8641
|
} finally {
|
|
8238
8642
|
installInProgress.delete(slug);
|
|
8239
8643
|
}
|
|
@@ -8257,17 +8661,17 @@ async function marketplaceRoutes(app) {
|
|
|
8257
8661
|
}
|
|
8258
8662
|
|
|
8259
8663
|
// libs/shield-daemon/src/routes/skills.ts
|
|
8260
|
-
function
|
|
8664
|
+
function findSkillMdRecursive2(dir, depth = 0) {
|
|
8261
8665
|
if (depth > 3) return null;
|
|
8262
8666
|
try {
|
|
8263
8667
|
for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
|
|
8264
|
-
const candidate =
|
|
8265
|
-
if (
|
|
8668
|
+
const candidate = path17.join(dir, name);
|
|
8669
|
+
if (fs17.existsSync(candidate)) return candidate;
|
|
8266
8670
|
}
|
|
8267
|
-
const entries =
|
|
8671
|
+
const entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
8268
8672
|
for (const entry of entries) {
|
|
8269
8673
|
if (!entry.isDirectory()) continue;
|
|
8270
|
-
const found =
|
|
8674
|
+
const found = findSkillMdRecursive2(path17.join(dir, entry.name), depth + 1);
|
|
8271
8675
|
if (found) return found;
|
|
8272
8676
|
}
|
|
8273
8677
|
} catch {
|
|
@@ -8276,10 +8680,10 @@ function findSkillMdRecursive(dir, depth = 0) {
|
|
|
8276
8680
|
}
|
|
8277
8681
|
function readSkillMetadata(skillDir) {
|
|
8278
8682
|
try {
|
|
8279
|
-
const mdPath =
|
|
8683
|
+
const mdPath = findSkillMdRecursive2(skillDir);
|
|
8280
8684
|
if (!mdPath) return {};
|
|
8281
|
-
const content =
|
|
8282
|
-
const parsed =
|
|
8685
|
+
const content = fs17.readFileSync(mdPath, "utf-8");
|
|
8686
|
+
const parsed = parseSkillMd3(content);
|
|
8283
8687
|
const meta = parsed?.metadata;
|
|
8284
8688
|
return {
|
|
8285
8689
|
description: meta?.description,
|
|
@@ -8337,7 +8741,7 @@ async function skillsRoutes(app) {
|
|
|
8337
8741
|
let onDiskNames = [];
|
|
8338
8742
|
if (skillsDir2) {
|
|
8339
8743
|
try {
|
|
8340
|
-
onDiskNames =
|
|
8744
|
+
onDiskNames = fs17.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
8341
8745
|
} catch {
|
|
8342
8746
|
}
|
|
8343
8747
|
}
|
|
@@ -8347,14 +8751,14 @@ async function skillsRoutes(app) {
|
|
|
8347
8751
|
const data = [
|
|
8348
8752
|
// Approved → active (with metadata from SKILL.md + cached analysis)
|
|
8349
8753
|
...approved.map((a) => {
|
|
8350
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8754
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, a.name)) : {};
|
|
8351
8755
|
const cached = getCachedAnalysis2(a.name);
|
|
8352
8756
|
const dlMeta = getDownloadedSkillMeta(a.name);
|
|
8353
8757
|
return {
|
|
8354
8758
|
name: a.name,
|
|
8355
8759
|
source: "user",
|
|
8356
8760
|
status: "active",
|
|
8357
|
-
path:
|
|
8761
|
+
path: path17.join(skillsDir2 ?? "", a.name),
|
|
8358
8762
|
publisher: a.publisher,
|
|
8359
8763
|
description: meta.description,
|
|
8360
8764
|
version: meta.version,
|
|
@@ -8381,19 +8785,42 @@ async function skillsRoutes(app) {
|
|
|
8381
8785
|
}),
|
|
8382
8786
|
// Workspace: on disk but not approved or untrusted
|
|
8383
8787
|
...workspaceNames.map((name) => {
|
|
8384
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8788
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
|
|
8385
8789
|
return {
|
|
8386
8790
|
name,
|
|
8387
8791
|
source: "workspace",
|
|
8388
8792
|
status: "workspace",
|
|
8389
|
-
path:
|
|
8793
|
+
path: path17.join(skillsDir2 ?? "", name),
|
|
8390
8794
|
description: meta.description,
|
|
8391
8795
|
version: meta.version,
|
|
8392
8796
|
author: meta.author,
|
|
8393
8797
|
tags: meta.tags,
|
|
8394
8798
|
analysis: buildAnalysisSummary(name, getCachedAnalysis2(name))
|
|
8395
8799
|
};
|
|
8396
|
-
})
|
|
8800
|
+
}),
|
|
8801
|
+
// Disabled: previously installed marketplace skills that are no longer active
|
|
8802
|
+
...(() => {
|
|
8803
|
+
const allKnown = /* @__PURE__ */ new Set([
|
|
8804
|
+
...approvedNames,
|
|
8805
|
+
...untrustedNames,
|
|
8806
|
+
...workspaceNames
|
|
8807
|
+
]);
|
|
8808
|
+
return listDownloadedSkills().filter((d) => d.wasInstalled && !allKnown.has(d.slug) && !allKnown.has(d.name)).map((d) => {
|
|
8809
|
+
const cached = getCachedAnalysis2(d.slug) || getCachedAnalysis2(d.name);
|
|
8810
|
+
return {
|
|
8811
|
+
name: d.slug,
|
|
8812
|
+
source: "marketplace",
|
|
8813
|
+
status: "disabled",
|
|
8814
|
+
path: "",
|
|
8815
|
+
publisher: d.author,
|
|
8816
|
+
description: d.description,
|
|
8817
|
+
version: d.version,
|
|
8818
|
+
author: d.author,
|
|
8819
|
+
tags: d.tags,
|
|
8820
|
+
analysis: buildAnalysisSummary(d.slug, d.analysis || cached)
|
|
8821
|
+
};
|
|
8822
|
+
});
|
|
8823
|
+
})()
|
|
8397
8824
|
];
|
|
8398
8825
|
return reply.send({ data });
|
|
8399
8826
|
});
|
|
@@ -8417,7 +8844,7 @@ async function skillsRoutes(app) {
|
|
|
8417
8844
|
let isWorkspace = false;
|
|
8418
8845
|
if (!entry && !uEntry && skillsDir2) {
|
|
8419
8846
|
try {
|
|
8420
|
-
isWorkspace =
|
|
8847
|
+
isWorkspace = fs17.existsSync(path17.join(skillsDir2, name));
|
|
8421
8848
|
} catch {
|
|
8422
8849
|
}
|
|
8423
8850
|
}
|
|
@@ -8435,13 +8862,13 @@ async function skillsRoutes(app) {
|
|
|
8435
8862
|
analysis: buildFullAnalysis(name, dlMeta?.analysis || getCachedAnalysis2(name))
|
|
8436
8863
|
};
|
|
8437
8864
|
} else if (entry) {
|
|
8438
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8865
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
|
|
8439
8866
|
const cached = getCachedAnalysis2(name);
|
|
8440
8867
|
summary = {
|
|
8441
8868
|
name,
|
|
8442
8869
|
source: "user",
|
|
8443
8870
|
status: "active",
|
|
8444
|
-
path: skillsDir2 ?
|
|
8871
|
+
path: skillsDir2 ? path17.join(skillsDir2, name) : "",
|
|
8445
8872
|
publisher: entry.publisher,
|
|
8446
8873
|
description: meta.description,
|
|
8447
8874
|
version: meta.version,
|
|
@@ -8450,12 +8877,12 @@ async function skillsRoutes(app) {
|
|
|
8450
8877
|
analysis: buildFullAnalysis(name, dlMeta?.analysis || cached)
|
|
8451
8878
|
};
|
|
8452
8879
|
} else if (isWorkspace) {
|
|
8453
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8880
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
|
|
8454
8881
|
summary = {
|
|
8455
8882
|
name,
|
|
8456
8883
|
source: "workspace",
|
|
8457
8884
|
status: "workspace",
|
|
8458
|
-
path: skillsDir2 ?
|
|
8885
|
+
path: skillsDir2 ? path17.join(skillsDir2, name) : "",
|
|
8459
8886
|
description: meta.description,
|
|
8460
8887
|
version: meta.version,
|
|
8461
8888
|
author: meta.author,
|
|
@@ -8467,7 +8894,7 @@ async function skillsRoutes(app) {
|
|
|
8467
8894
|
summary = {
|
|
8468
8895
|
name: dlMeta.slug ?? name,
|
|
8469
8896
|
source: "marketplace",
|
|
8470
|
-
status: "downloaded",
|
|
8897
|
+
status: dlMeta.wasInstalled ? "disabled" : "downloaded",
|
|
8471
8898
|
description: dlMeta.description,
|
|
8472
8899
|
path: "",
|
|
8473
8900
|
publisher: dlMeta.author,
|
|
@@ -8480,11 +8907,11 @@ async function skillsRoutes(app) {
|
|
|
8480
8907
|
return reply.code(404).send({ error: `Skill "${name}" not found` });
|
|
8481
8908
|
}
|
|
8482
8909
|
let content = "";
|
|
8483
|
-
const dirToRead = summary.path || (skillsDir2 ?
|
|
8910
|
+
const dirToRead = summary.path || (skillsDir2 ? path17.join(skillsDir2, name) : "");
|
|
8484
8911
|
if (dirToRead) {
|
|
8485
8912
|
try {
|
|
8486
|
-
const mdPath =
|
|
8487
|
-
if (mdPath) content =
|
|
8913
|
+
const mdPath = findSkillMdRecursive2(dirToRead);
|
|
8914
|
+
if (mdPath) content = fs17.readFileSync(mdPath, "utf-8");
|
|
8488
8915
|
} catch {
|
|
8489
8916
|
}
|
|
8490
8917
|
}
|
|
@@ -8522,14 +8949,14 @@ async function skillsRoutes(app) {
|
|
|
8522
8949
|
if (!content) {
|
|
8523
8950
|
const skillsDir2 = getSkillsDir();
|
|
8524
8951
|
const possibleDirs = [
|
|
8525
|
-
skillsDir2 ?
|
|
8952
|
+
skillsDir2 ? path17.join(skillsDir2, name) : null
|
|
8526
8953
|
].filter(Boolean);
|
|
8527
8954
|
for (const dir of possibleDirs) {
|
|
8528
8955
|
try {
|
|
8529
|
-
const mdPath =
|
|
8956
|
+
const mdPath = findSkillMdRecursive2(dir);
|
|
8530
8957
|
if (mdPath) {
|
|
8531
|
-
content =
|
|
8532
|
-
const parsed =
|
|
8958
|
+
content = fs17.readFileSync(mdPath, "utf-8");
|
|
8959
|
+
const parsed = parseSkillMd3(content);
|
|
8533
8960
|
if (parsed?.metadata && !metadata) {
|
|
8534
8961
|
metadata = parsed.metadata;
|
|
8535
8962
|
}
|
|
@@ -8544,7 +8971,7 @@ async function skillsRoutes(app) {
|
|
|
8544
8971
|
const skillFile = localFiles.find((f) => /skill\.md/i.test(f.name));
|
|
8545
8972
|
if (skillFile?.content) {
|
|
8546
8973
|
content = skillFile.content;
|
|
8547
|
-
const parsed =
|
|
8974
|
+
const parsed = parseSkillMd3(content);
|
|
8548
8975
|
if (parsed?.metadata && !metadata) {
|
|
8549
8976
|
metadata = parsed.metadata;
|
|
8550
8977
|
}
|
|
@@ -8559,7 +8986,7 @@ async function skillsRoutes(app) {
|
|
|
8559
8986
|
const skillFile = freshFiles.find((f) => /skill\.md/i.test(f.name));
|
|
8560
8987
|
if (skillFile?.content) {
|
|
8561
8988
|
content = skillFile.content;
|
|
8562
|
-
const parsed =
|
|
8989
|
+
const parsed = parseSkillMd3(content);
|
|
8563
8990
|
if (parsed?.metadata && !metadata) {
|
|
8564
8991
|
metadata = parsed.metadata;
|
|
8565
8992
|
}
|
|
@@ -8713,10 +9140,10 @@ async function skillsRoutes(app) {
|
|
|
8713
9140
|
return reply.code(500).send({ error: "Skills directory not configured" });
|
|
8714
9141
|
}
|
|
8715
9142
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8716
|
-
const binDir =
|
|
9143
|
+
const binDir = path17.join(agentHome, "bin");
|
|
8717
9144
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8718
|
-
const skillDir =
|
|
8719
|
-
const isInstalled =
|
|
9145
|
+
const skillDir = path17.join(skillsDir2, name);
|
|
9146
|
+
const isInstalled = fs17.existsSync(skillDir);
|
|
8720
9147
|
if (isInstalled) {
|
|
8721
9148
|
try {
|
|
8722
9149
|
const brokerAvailable = await isBrokerAvailable();
|
|
@@ -8726,7 +9153,7 @@ async function skillsRoutes(app) {
|
|
|
8726
9153
|
agentHome
|
|
8727
9154
|
});
|
|
8728
9155
|
} else {
|
|
8729
|
-
|
|
9156
|
+
fs17.rmSync(skillDir, { recursive: true, force: true });
|
|
8730
9157
|
removeSkillWrapper(name, binDir);
|
|
8731
9158
|
}
|
|
8732
9159
|
removeSkillEntry(name);
|
|
@@ -8734,7 +9161,7 @@ async function skillsRoutes(app) {
|
|
|
8734
9161
|
syncOpenClawFromPolicies(loadConfig().policies);
|
|
8735
9162
|
removeFromApprovedList(name);
|
|
8736
9163
|
try {
|
|
8737
|
-
|
|
9164
|
+
markDownloadedAsInstalled(name);
|
|
8738
9165
|
} catch {
|
|
8739
9166
|
}
|
|
8740
9167
|
console.log(`[Skills] Disabled marketplace skill: ${name}`);
|
|
@@ -8754,12 +9181,20 @@ async function skillsRoutes(app) {
|
|
|
8754
9181
|
return reply.code(404).send({ error: "No files in download cache for this skill" });
|
|
8755
9182
|
}
|
|
8756
9183
|
try {
|
|
8757
|
-
addToApprovedList(name, meta.author);
|
|
9184
|
+
addToApprovedList(name, meta.author, void 0, meta.slug);
|
|
9185
|
+
const taggedFiles = await Promise.all(files.map(async (f) => {
|
|
9186
|
+
let content = f.content;
|
|
9187
|
+
if (/SKILL\.md$/i.test(f.name)) {
|
|
9188
|
+
content = stripEnvFromSkillMd2(content);
|
|
9189
|
+
content = await injectInstallationTag(content);
|
|
9190
|
+
}
|
|
9191
|
+
return { name: f.name, content, type: f.type };
|
|
9192
|
+
}));
|
|
8758
9193
|
const brokerAvailable = await isBrokerAvailable();
|
|
8759
9194
|
if (brokerAvailable) {
|
|
8760
9195
|
const brokerResult = await installSkillViaBroker(
|
|
8761
9196
|
name,
|
|
8762
|
-
|
|
9197
|
+
taggedFiles.map((f) => ({ name: f.name, content: f.content })),
|
|
8763
9198
|
{ createWrapper: true, agentHome, socketGroup }
|
|
8764
9199
|
);
|
|
8765
9200
|
if (!brokerResult.installed) {
|
|
@@ -8771,11 +9206,11 @@ async function skillsRoutes(app) {
|
|
|
8771
9206
|
}
|
|
8772
9207
|
}
|
|
8773
9208
|
} else {
|
|
8774
|
-
|
|
8775
|
-
for (const file of
|
|
8776
|
-
const filePath =
|
|
8777
|
-
|
|
8778
|
-
|
|
9209
|
+
fs17.mkdirSync(skillDir, { recursive: true });
|
|
9210
|
+
for (const file of taggedFiles) {
|
|
9211
|
+
const filePath = path17.join(skillDir, file.name);
|
|
9212
|
+
fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
9213
|
+
fs17.writeFileSync(filePath, file.content, "utf-8");
|
|
8779
9214
|
}
|
|
8780
9215
|
try {
|
|
8781
9216
|
execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
@@ -8789,12 +9224,16 @@ async function skillsRoutes(app) {
|
|
|
8789
9224
|
syncOpenClawFromPolicies(loadConfig().policies);
|
|
8790
9225
|
const hash = computeSkillHash(skillDir);
|
|
8791
9226
|
if (hash) updateApprovedHash(name, hash);
|
|
9227
|
+
try {
|
|
9228
|
+
markDownloadedAsInstalled(name);
|
|
9229
|
+
} catch {
|
|
9230
|
+
}
|
|
8792
9231
|
console.log(`[Skills] Enabled marketplace skill: ${name}`);
|
|
8793
9232
|
return reply.send({ success: true, action: "enabled", name });
|
|
8794
9233
|
} catch (err) {
|
|
8795
9234
|
try {
|
|
8796
|
-
if (
|
|
8797
|
-
|
|
9235
|
+
if (fs17.existsSync(skillDir)) {
|
|
9236
|
+
fs17.rmSync(skillDir, { recursive: true, force: true });
|
|
8798
9237
|
}
|
|
8799
9238
|
removeFromApprovedList(name);
|
|
8800
9239
|
} catch {
|
|
@@ -8820,7 +9259,7 @@ async function skillsRoutes(app) {
|
|
|
8820
9259
|
const skillMdFile = files.find((f) => f.name === "SKILL.md");
|
|
8821
9260
|
let metadata;
|
|
8822
9261
|
if (skillMdFile) {
|
|
8823
|
-
const parsed =
|
|
9262
|
+
const parsed = parseSkillMd3(skillMdFile.content);
|
|
8824
9263
|
metadata = parsed?.metadata;
|
|
8825
9264
|
}
|
|
8826
9265
|
const analysis = analyzeSkill(name, combinedContent, metadata);
|
|
@@ -8832,16 +9271,25 @@ async function skillsRoutes(app) {
|
|
|
8832
9271
|
return reply.code(500).send({ error: "Skills directory not configured" });
|
|
8833
9272
|
}
|
|
8834
9273
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8835
|
-
const
|
|
9274
|
+
const agentUsername = path17.basename(agentHome);
|
|
9275
|
+
const binDir = path17.join(agentHome, "bin");
|
|
8836
9276
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8837
|
-
const skillDir =
|
|
9277
|
+
const skillDir = path17.join(skillsDir2, name);
|
|
8838
9278
|
try {
|
|
8839
9279
|
addToApprovedList(name, publisher);
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
9280
|
+
const taggedFiles = await Promise.all(files.map(async (f) => {
|
|
9281
|
+
let content = f.content;
|
|
9282
|
+
if (/SKILL\.md$/i.test(f.name)) {
|
|
9283
|
+
content = stripEnvFromSkillMd2(content);
|
|
9284
|
+
content = await injectInstallationTag(content);
|
|
9285
|
+
}
|
|
9286
|
+
return { name: f.name, content };
|
|
9287
|
+
}));
|
|
9288
|
+
sudoMkdir(skillDir, agentUsername);
|
|
9289
|
+
for (const file of taggedFiles) {
|
|
9290
|
+
const filePath = path17.join(skillDir, file.name);
|
|
9291
|
+
sudoMkdir(path17.dirname(filePath), agentUsername);
|
|
9292
|
+
sudoWriteFile(filePath, file.content, agentUsername);
|
|
8845
9293
|
}
|
|
8846
9294
|
try {
|
|
8847
9295
|
execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
@@ -8855,8 +9303,8 @@ async function skillsRoutes(app) {
|
|
|
8855
9303
|
return reply.send({ success: true, name, analysis });
|
|
8856
9304
|
} catch (err) {
|
|
8857
9305
|
try {
|
|
8858
|
-
if (
|
|
8859
|
-
|
|
9306
|
+
if (fs17.existsSync(skillDir)) {
|
|
9307
|
+
fs17.rmSync(skillDir, { recursive: true, force: true });
|
|
8860
9308
|
}
|
|
8861
9309
|
removeFromApprovedList(name);
|
|
8862
9310
|
} catch {
|
|
@@ -8890,15 +9338,23 @@ async function skillsRoutes(app) {
|
|
|
8890
9338
|
}
|
|
8891
9339
|
try {
|
|
8892
9340
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8893
|
-
const binDir =
|
|
9341
|
+
const binDir = path17.join(agentHome, "bin");
|
|
8894
9342
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8895
|
-
const skillDir =
|
|
8896
|
-
addToApprovedList(name, meta.author);
|
|
9343
|
+
const skillDir = path17.join(getSkillsDir(), name);
|
|
9344
|
+
addToApprovedList(name, meta.author, void 0, meta.slug);
|
|
9345
|
+
const taggedFiles = await Promise.all(files.map(async (f) => {
|
|
9346
|
+
let content = f.content;
|
|
9347
|
+
if (/SKILL\.md$/i.test(f.name)) {
|
|
9348
|
+
content = stripEnvFromSkillMd2(content);
|
|
9349
|
+
content = await injectInstallationTag(content);
|
|
9350
|
+
}
|
|
9351
|
+
return { name: f.name, content, type: f.type };
|
|
9352
|
+
}));
|
|
8897
9353
|
const brokerAvailable = await isBrokerAvailable();
|
|
8898
9354
|
if (brokerAvailable) {
|
|
8899
9355
|
const brokerResult = await installSkillViaBroker(
|
|
8900
9356
|
name,
|
|
8901
|
-
|
|
9357
|
+
taggedFiles.map((f) => ({ name: f.name, content: f.content })),
|
|
8902
9358
|
{ createWrapper: true, agentHome, socketGroup }
|
|
8903
9359
|
);
|
|
8904
9360
|
if (!brokerResult.installed) {
|
|
@@ -8910,11 +9366,11 @@ async function skillsRoutes(app) {
|
|
|
8910
9366
|
}
|
|
8911
9367
|
}
|
|
8912
9368
|
} else {
|
|
8913
|
-
|
|
8914
|
-
for (const file of
|
|
8915
|
-
const filePath =
|
|
8916
|
-
|
|
8917
|
-
|
|
9369
|
+
fs17.mkdirSync(skillDir, { recursive: true });
|
|
9370
|
+
for (const file of taggedFiles) {
|
|
9371
|
+
const filePath = path17.join(skillDir, file.name);
|
|
9372
|
+
fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
9373
|
+
fs17.writeFileSync(filePath, file.content, "utf-8");
|
|
8918
9374
|
}
|
|
8919
9375
|
try {
|
|
8920
9376
|
execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
@@ -8926,8 +9382,12 @@ async function skillsRoutes(app) {
|
|
|
8926
9382
|
addSkillEntry(name);
|
|
8927
9383
|
addSkillPolicy(name);
|
|
8928
9384
|
syncOpenClawFromPolicies(loadConfig().policies);
|
|
8929
|
-
const unblockHash = computeSkillHash(
|
|
9385
|
+
const unblockHash = computeSkillHash(path17.join(getSkillsDir(), name));
|
|
8930
9386
|
if (unblockHash) updateApprovedHash(name, unblockHash);
|
|
9387
|
+
try {
|
|
9388
|
+
markDownloadedAsInstalled(name);
|
|
9389
|
+
} catch {
|
|
9390
|
+
}
|
|
8931
9391
|
console.log(`[Skills] Unblocked and installed skill: ${name}`);
|
|
8932
9392
|
return reply.send({ success: true, message: `Skill "${name}" approved and installed` });
|
|
8933
9393
|
} catch (err) {
|
|
@@ -8957,7 +9417,7 @@ async function skillsRoutes(app) {
|
|
|
8957
9417
|
const skillMdFile = files.find((f) => /skill\.md/i.test(f.name));
|
|
8958
9418
|
if (skillMdFile) {
|
|
8959
9419
|
try {
|
|
8960
|
-
const parsed =
|
|
9420
|
+
const parsed = parseSkillMd3(skillMdFile.content);
|
|
8961
9421
|
if (parsed?.metadata) {
|
|
8962
9422
|
parsedDescription = parsedDescription || parsed.metadata.description;
|
|
8963
9423
|
parsedVersion = parsedVersion || parsed.metadata.version;
|
|
@@ -8986,10 +9446,10 @@ async function skillsRoutes(app) {
|
|
|
8986
9446
|
|
|
8987
9447
|
// libs/shield-daemon/src/routes/exec.ts
|
|
8988
9448
|
init_paths();
|
|
8989
|
-
import * as
|
|
8990
|
-
import * as
|
|
9449
|
+
import * as fs18 from "node:fs";
|
|
9450
|
+
import * as path18 from "node:path";
|
|
8991
9451
|
function getAllowedCommandsPath2() {
|
|
8992
|
-
return
|
|
9452
|
+
return path18.join(getSystemConfigDir(), "allowed-commands.json");
|
|
8993
9453
|
}
|
|
8994
9454
|
var BIN_DIRS = [
|
|
8995
9455
|
"/usr/bin",
|
|
@@ -9002,22 +9462,22 @@ var binCache = null;
|
|
|
9002
9462
|
var BIN_CACHE_TTL = 6e4;
|
|
9003
9463
|
var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
9004
9464
|
function loadConfig2() {
|
|
9005
|
-
if (!
|
|
9465
|
+
if (!fs18.existsSync(getAllowedCommandsPath2())) {
|
|
9006
9466
|
return { version: "1.0.0", commands: [] };
|
|
9007
9467
|
}
|
|
9008
9468
|
try {
|
|
9009
|
-
const content =
|
|
9469
|
+
const content = fs18.readFileSync(getAllowedCommandsPath2(), "utf-8");
|
|
9010
9470
|
return JSON.parse(content);
|
|
9011
9471
|
} catch {
|
|
9012
9472
|
return { version: "1.0.0", commands: [] };
|
|
9013
9473
|
}
|
|
9014
9474
|
}
|
|
9015
9475
|
function saveConfig2(config) {
|
|
9016
|
-
const dir =
|
|
9017
|
-
if (!
|
|
9018
|
-
|
|
9476
|
+
const dir = path18.dirname(getAllowedCommandsPath2());
|
|
9477
|
+
if (!fs18.existsSync(dir)) {
|
|
9478
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
9019
9479
|
}
|
|
9020
|
-
|
|
9480
|
+
fs18.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
9021
9481
|
}
|
|
9022
9482
|
function scanSystemBins() {
|
|
9023
9483
|
const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
@@ -9026,13 +9486,13 @@ function scanSystemBins() {
|
|
|
9026
9486
|
const results = [];
|
|
9027
9487
|
for (const dir of allDirs) {
|
|
9028
9488
|
try {
|
|
9029
|
-
if (!
|
|
9030
|
-
const entries =
|
|
9489
|
+
if (!fs18.existsSync(dir)) continue;
|
|
9490
|
+
const entries = fs18.readdirSync(dir);
|
|
9031
9491
|
for (const entry of entries) {
|
|
9032
9492
|
if (seen.has(entry)) continue;
|
|
9033
|
-
const fullPath =
|
|
9493
|
+
const fullPath = path18.join(dir, entry);
|
|
9034
9494
|
try {
|
|
9035
|
-
const stat =
|
|
9495
|
+
const stat = fs18.statSync(fullPath);
|
|
9036
9496
|
if (stat.isFile() && (stat.mode & 73) !== 0) {
|
|
9037
9497
|
seen.add(entry);
|
|
9038
9498
|
results.push({ name: entry, path: fullPath });
|
|
@@ -9085,7 +9545,7 @@ async function execRoutes(app) {
|
|
|
9085
9545
|
};
|
|
9086
9546
|
}
|
|
9087
9547
|
for (const p of paths) {
|
|
9088
|
-
if (!
|
|
9548
|
+
if (!path18.isAbsolute(p)) {
|
|
9089
9549
|
return {
|
|
9090
9550
|
success: false,
|
|
9091
9551
|
error: {
|
|
@@ -9427,7 +9887,7 @@ async function authRoutes(app) {
|
|
|
9427
9887
|
init_vault();
|
|
9428
9888
|
import { isSecretEnvVar } from "@agenshield/sandbox";
|
|
9429
9889
|
init_secret_sync();
|
|
9430
|
-
import
|
|
9890
|
+
import crypto6 from "node:crypto";
|
|
9431
9891
|
function maskValue(value) {
|
|
9432
9892
|
if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
9433
9893
|
return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
|
|
@@ -9522,7 +9982,7 @@ async function secretsRoutes(app) {
|
|
|
9522
9982
|
const secrets = await vault.get("secrets") ?? [];
|
|
9523
9983
|
const resolvedScope = scope ?? (policyIds?.length > 0 ? "policed" : "global");
|
|
9524
9984
|
const newSecret = {
|
|
9525
|
-
id:
|
|
9985
|
+
id: crypto6.randomUUID(),
|
|
9526
9986
|
name: name.trim(),
|
|
9527
9987
|
value,
|
|
9528
9988
|
policyIds: resolvedScope === "standalone" ? [] : policyIds ?? [],
|
|
@@ -9581,18 +10041,18 @@ async function secretsRoutes(app) {
|
|
|
9581
10041
|
}
|
|
9582
10042
|
|
|
9583
10043
|
// libs/shield-daemon/src/routes/fs.ts
|
|
9584
|
-
import * as
|
|
9585
|
-
import * as
|
|
10044
|
+
import * as fs19 from "node:fs";
|
|
10045
|
+
import * as path19 from "node:path";
|
|
9586
10046
|
import * as os6 from "node:os";
|
|
9587
10047
|
var MAX_ENTRIES = 200;
|
|
9588
10048
|
async function fsRoutes(app) {
|
|
9589
10049
|
app.get("/fs/browse", async (request2) => {
|
|
9590
10050
|
const dirPath = request2.query.path || os6.homedir();
|
|
9591
10051
|
const showHidden = request2.query.showHidden === "true";
|
|
9592
|
-
const resolvedPath =
|
|
10052
|
+
const resolvedPath = path19.resolve(dirPath);
|
|
9593
10053
|
let dirents;
|
|
9594
10054
|
try {
|
|
9595
|
-
dirents =
|
|
10055
|
+
dirents = fs19.readdirSync(resolvedPath, { withFileTypes: true });
|
|
9596
10056
|
} catch {
|
|
9597
10057
|
return { success: true, data: { entries: [] } };
|
|
9598
10058
|
}
|
|
@@ -9601,7 +10061,7 @@ async function fsRoutes(app) {
|
|
|
9601
10061
|
if (!showHidden && dirent.name.startsWith(".")) continue;
|
|
9602
10062
|
entries.push({
|
|
9603
10063
|
name: dirent.name,
|
|
9604
|
-
path:
|
|
10064
|
+
path: path19.join(resolvedPath, dirent.name),
|
|
9605
10065
|
type: dirent.isDirectory() ? "directory" : "file"
|
|
9606
10066
|
});
|
|
9607
10067
|
if (entries.length >= MAX_ENTRIES) break;
|
|
@@ -9615,8 +10075,8 @@ async function fsRoutes(app) {
|
|
|
9615
10075
|
}
|
|
9616
10076
|
|
|
9617
10077
|
// libs/shield-daemon/src/services/activity-log.ts
|
|
9618
|
-
import * as
|
|
9619
|
-
import * as
|
|
10078
|
+
import * as fs20 from "node:fs";
|
|
10079
|
+
import * as path20 from "node:path";
|
|
9620
10080
|
init_paths();
|
|
9621
10081
|
var ACTIVITY_FILE = "activity.jsonl";
|
|
9622
10082
|
var MAX_SIZE_BYTES = 100 * 1024 * 1024;
|
|
@@ -9634,12 +10094,12 @@ var ActivityLog = class {
|
|
|
9634
10094
|
writeCount = 0;
|
|
9635
10095
|
unsubscribe;
|
|
9636
10096
|
constructor() {
|
|
9637
|
-
this.filePath =
|
|
10097
|
+
this.filePath = path20.join(getConfigDir(), ACTIVITY_FILE);
|
|
9638
10098
|
}
|
|
9639
10099
|
/** Read historical events from the JSONL file, newest first */
|
|
9640
10100
|
getHistory(limit = 500) {
|
|
9641
|
-
if (!
|
|
9642
|
-
const content =
|
|
10101
|
+
if (!fs20.existsSync(this.filePath)) return [];
|
|
10102
|
+
const content = fs20.readFileSync(this.filePath, "utf-8");
|
|
9643
10103
|
const lines = content.split("\n").filter(Boolean);
|
|
9644
10104
|
const events = [];
|
|
9645
10105
|
for (const line of lines) {
|
|
@@ -9664,7 +10124,7 @@ var ActivityLog = class {
|
|
|
9664
10124
|
}
|
|
9665
10125
|
append(event) {
|
|
9666
10126
|
const line = JSON.stringify(event) + "\n";
|
|
9667
|
-
|
|
10127
|
+
fs20.appendFileSync(this.filePath, line, "utf-8");
|
|
9668
10128
|
this.writeCount++;
|
|
9669
10129
|
if (this.writeCount % PRUNE_INTERVAL === 0) {
|
|
9670
10130
|
this.rotate();
|
|
@@ -9672,7 +10132,7 @@ var ActivityLog = class {
|
|
|
9672
10132
|
}
|
|
9673
10133
|
rotate() {
|
|
9674
10134
|
try {
|
|
9675
|
-
const stat =
|
|
10135
|
+
const stat = fs20.statSync(this.filePath);
|
|
9676
10136
|
if (stat.size > MAX_SIZE_BYTES) {
|
|
9677
10137
|
this.truncateBySize();
|
|
9678
10138
|
}
|
|
@@ -9682,15 +10142,15 @@ var ActivityLog = class {
|
|
|
9682
10142
|
}
|
|
9683
10143
|
/** Keep newest half of lines when file exceeds size limit */
|
|
9684
10144
|
truncateBySize() {
|
|
9685
|
-
const content =
|
|
10145
|
+
const content = fs20.readFileSync(this.filePath, "utf-8");
|
|
9686
10146
|
const lines = content.split("\n").filter(Boolean);
|
|
9687
10147
|
const keep = lines.slice(Math.floor(lines.length / 2));
|
|
9688
|
-
|
|
10148
|
+
fs20.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
|
|
9689
10149
|
}
|
|
9690
10150
|
/** Remove entries older than 24 hours */
|
|
9691
10151
|
pruneOldEntries() {
|
|
9692
|
-
if (!
|
|
9693
|
-
const content =
|
|
10152
|
+
if (!fs20.existsSync(this.filePath)) return;
|
|
10153
|
+
const content = fs20.readFileSync(this.filePath, "utf-8");
|
|
9694
10154
|
const lines = content.split("\n").filter(Boolean);
|
|
9695
10155
|
const cutoff = Date.now() - MAX_AGE_MS;
|
|
9696
10156
|
const kept = lines.filter((line) => {
|
|
@@ -9702,7 +10162,7 @@ var ActivityLog = class {
|
|
|
9702
10162
|
}
|
|
9703
10163
|
});
|
|
9704
10164
|
if (kept.length < lines.length) {
|
|
9705
|
-
|
|
10165
|
+
fs20.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
|
|
9706
10166
|
}
|
|
9707
10167
|
}
|
|
9708
10168
|
};
|
|
@@ -9823,7 +10283,7 @@ async function openclawRoutes(app) {
|
|
|
9823
10283
|
}
|
|
9824
10284
|
|
|
9825
10285
|
// libs/shield-daemon/src/routes/rpc.ts
|
|
9826
|
-
import * as
|
|
10286
|
+
import * as crypto7 from "node:crypto";
|
|
9827
10287
|
import * as nodefs from "node:fs";
|
|
9828
10288
|
|
|
9829
10289
|
// libs/shield-daemon/src/policy/url-matcher.ts
|
|
@@ -9843,11 +10303,11 @@ function normalizeUrlTarget(url) {
|
|
|
9843
10303
|
const trimmed = url.trim();
|
|
9844
10304
|
try {
|
|
9845
10305
|
const parsed = new URL(trimmed);
|
|
9846
|
-
let
|
|
9847
|
-
if (
|
|
9848
|
-
|
|
10306
|
+
let path24 = parsed.pathname;
|
|
10307
|
+
if (path24.length > 1) {
|
|
10308
|
+
path24 = path24.replace(/\/+$/, "");
|
|
9849
10309
|
}
|
|
9850
|
-
return `${parsed.protocol}//${parsed.host}${
|
|
10310
|
+
return `${parsed.protocol}//${parsed.host}${path24}${parsed.search}`;
|
|
9851
10311
|
} catch {
|
|
9852
10312
|
return trimmed.replace(/\/+$/, "");
|
|
9853
10313
|
}
|
|
@@ -9937,7 +10397,7 @@ function checkUrlPolicy(policies, url) {
|
|
|
9937
10397
|
// libs/shield-daemon/src/proxy/server.ts
|
|
9938
10398
|
import * as http from "node:http";
|
|
9939
10399
|
import * as net from "node:net";
|
|
9940
|
-
function createPerRunProxy(urlPolicies, onActivity, logger) {
|
|
10400
|
+
function createPerRunProxy(urlPolicies, onActivity, logger, onBlock) {
|
|
9941
10401
|
const server = http.createServer((req, res) => {
|
|
9942
10402
|
onActivity();
|
|
9943
10403
|
const url = req.url;
|
|
@@ -9949,6 +10409,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
|
|
|
9949
10409
|
const allowed = checkUrlPolicy(urlPolicies, url);
|
|
9950
10410
|
if (!allowed) {
|
|
9951
10411
|
logger(`BLOCKED HTTP ${req.method} ${url}`);
|
|
10412
|
+
onBlock?.(req.method || "GET", url, "http");
|
|
9952
10413
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
9953
10414
|
res.end("Blocked by AgenShield URL policy");
|
|
9954
10415
|
return;
|
|
@@ -9992,6 +10453,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
|
|
|
9992
10453
|
const allowed = checkUrlPolicy(urlPolicies, `https://${hostname2}`);
|
|
9993
10454
|
if (!allowed) {
|
|
9994
10455
|
logger(`BLOCKED CONNECT ${hostname2}:${port}`);
|
|
10456
|
+
onBlock?.("CONNECT", `${hostname2}:${port}`, "https");
|
|
9995
10457
|
clientSocket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
9996
10458
|
clientSocket.destroy();
|
|
9997
10459
|
return;
|
|
@@ -10054,7 +10516,16 @@ var ProxyPool = class {
|
|
|
10054
10516
|
const logger = (msg) => {
|
|
10055
10517
|
console.log(`[proxy:${execId.slice(0, 8)}] ${msg}`);
|
|
10056
10518
|
};
|
|
10057
|
-
const
|
|
10519
|
+
const onBlock = (method, target, protocol) => {
|
|
10520
|
+
emitInterceptorEvent({
|
|
10521
|
+
type: "denied",
|
|
10522
|
+
operation: "http_request",
|
|
10523
|
+
target: protocol === "https" ? `https://${target}` : target,
|
|
10524
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10525
|
+
error: `Blocked by URL policy (${method})`
|
|
10526
|
+
});
|
|
10527
|
+
};
|
|
10528
|
+
const server = createPerRunProxy(urlPolicies, onActivity, logger, onBlock);
|
|
10058
10529
|
const port = await new Promise((resolve3, reject) => {
|
|
10059
10530
|
server.listen(0, "127.0.0.1", () => {
|
|
10060
10531
|
const addr = server.address();
|
|
@@ -10133,16 +10604,23 @@ function matchCommandPattern(pattern, target) {
|
|
|
10133
10604
|
const firstSpace = target.indexOf(" ");
|
|
10134
10605
|
const cmd = firstSpace >= 0 ? target.slice(0, firstSpace) : target;
|
|
10135
10606
|
if (cmd.startsWith("/")) {
|
|
10136
|
-
const
|
|
10137
|
-
normalizedTarget = firstSpace >= 0 ?
|
|
10607
|
+
const basename6 = cmd.split("/").pop() || cmd;
|
|
10608
|
+
normalizedTarget = firstSpace >= 0 ? basename6 + target.slice(firstSpace) : basename6;
|
|
10138
10609
|
}
|
|
10139
10610
|
if (trimmed.endsWith(":*")) {
|
|
10140
|
-
|
|
10611
|
+
let prefix = trimmed.slice(0, -2);
|
|
10612
|
+
if (prefix.includes("/")) {
|
|
10613
|
+
prefix = prefix.split("/").pop() || prefix;
|
|
10614
|
+
}
|
|
10141
10615
|
const lowerTarget = normalizedTarget.toLowerCase();
|
|
10142
10616
|
const lowerPrefix = prefix.toLowerCase();
|
|
10143
10617
|
return lowerTarget === lowerPrefix || lowerTarget.startsWith(lowerPrefix + " ");
|
|
10144
10618
|
}
|
|
10145
|
-
|
|
10619
|
+
let normalizedPattern = trimmed;
|
|
10620
|
+
if (trimmed.includes("/")) {
|
|
10621
|
+
normalizedPattern = trimmed.split("/").pop() || trimmed;
|
|
10622
|
+
}
|
|
10623
|
+
return normalizedTarget.toLowerCase() === normalizedPattern.toLowerCase();
|
|
10146
10624
|
}
|
|
10147
10625
|
function operationToTarget(operation) {
|
|
10148
10626
|
switch (operation) {
|
|
@@ -10185,8 +10663,8 @@ function determineNetworkAccess(_config, matchedPolicy, target) {
|
|
|
10185
10663
|
if (matchedPolicy?.networkAccess) return matchedPolicy.networkAccess;
|
|
10186
10664
|
const cleanTarget = target.startsWith("fork:") ? target.slice(5) : target;
|
|
10187
10665
|
const cmdPart = cleanTarget.split(" ")[0] || "";
|
|
10188
|
-
const
|
|
10189
|
-
if (!NETWORK_COMMANDS.has(
|
|
10666
|
+
const basename6 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
|
|
10667
|
+
if (!NETWORK_COMMANDS.has(basename6.toLowerCase())) return "none";
|
|
10190
10668
|
return "proxy";
|
|
10191
10669
|
}
|
|
10192
10670
|
async function buildSandboxConfig(config, matchedPolicy, _context, target) {
|
|
@@ -10235,7 +10713,7 @@ async function buildSandboxConfig(config, matchedPolicy, _context, target) {
|
|
|
10235
10713
|
console.log(`[sandbox] direct network access (no proxy)`);
|
|
10236
10714
|
sandbox.networkAllowed = true;
|
|
10237
10715
|
} else if (networkMode === "proxy") {
|
|
10238
|
-
const execId =
|
|
10716
|
+
const execId = crypto7.randomUUID();
|
|
10239
10717
|
const commandBasename = extractCommandBasename(target || "");
|
|
10240
10718
|
const urlPolicies = filterUrlPoliciesForCommand(config.policies || [], commandBasename);
|
|
10241
10719
|
const pool = getProxyPool();
|
|
@@ -10300,7 +10778,11 @@ async function evaluatePolicyCheck(operation, target, context) {
|
|
|
10300
10778
|
} else if (targetType === "command") {
|
|
10301
10779
|
matches = matchCommandPattern(pattern, effectiveTarget);
|
|
10302
10780
|
} else {
|
|
10303
|
-
|
|
10781
|
+
let fsPattern = pattern;
|
|
10782
|
+
if (targetType === "filesystem" && fsPattern.endsWith("/")) {
|
|
10783
|
+
fsPattern = fsPattern + "**";
|
|
10784
|
+
}
|
|
10785
|
+
const regex = globToRegex(fsPattern);
|
|
10304
10786
|
matches = regex.test(effectiveTarget);
|
|
10305
10787
|
}
|
|
10306
10788
|
console.log("[policy_check] pattern:", pattern, "-> base:", targetType === "url" ? normalizeUrlBase(pattern) : pattern, "| target:", effectiveTarget, "| matches:", matches);
|
|
@@ -10366,12 +10848,29 @@ async function handleHttpRequest(params) {
|
|
|
10366
10848
|
body: responseBody
|
|
10367
10849
|
};
|
|
10368
10850
|
}
|
|
10851
|
+
async function handlePolicyCheck(params) {
|
|
10852
|
+
const operation = String(params["operation"] ?? "");
|
|
10853
|
+
const target = String(params["target"] ?? "");
|
|
10854
|
+
const context = params["context"];
|
|
10855
|
+
const result = await evaluatePolicyCheck(operation, target, context);
|
|
10856
|
+
if (!result.allowed) {
|
|
10857
|
+
if (operation === "exec") {
|
|
10858
|
+
emitExecDenied(target, result.reason || "Denied by policy");
|
|
10859
|
+
} else {
|
|
10860
|
+
emitInterceptorEvent({
|
|
10861
|
+
type: "denied",
|
|
10862
|
+
operation,
|
|
10863
|
+
target,
|
|
10864
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10865
|
+
policyId: result.policyId,
|
|
10866
|
+
error: result.reason || "Denied by policy"
|
|
10867
|
+
});
|
|
10868
|
+
}
|
|
10869
|
+
}
|
|
10870
|
+
return result;
|
|
10871
|
+
}
|
|
10369
10872
|
var handlers = {
|
|
10370
|
-
policy_check: (params) =>
|
|
10371
|
-
String(params["operation"] ?? ""),
|
|
10372
|
-
String(params["target"] ?? ""),
|
|
10373
|
-
params["context"]
|
|
10374
|
-
),
|
|
10873
|
+
policy_check: (params) => handlePolicyCheck(params),
|
|
10375
10874
|
events_batch: (params) => handleEventsBatch(params),
|
|
10376
10875
|
http_request: (params) => handleHttpRequest(params),
|
|
10377
10876
|
ping: () => ({ status: "ok" })
|
|
@@ -10487,22 +10986,22 @@ async function registerRoutes(app) {
|
|
|
10487
10986
|
}
|
|
10488
10987
|
|
|
10489
10988
|
// libs/shield-daemon/src/static.ts
|
|
10490
|
-
import * as
|
|
10491
|
-
import * as
|
|
10989
|
+
import * as fs21 from "node:fs";
|
|
10990
|
+
import * as path21 from "node:path";
|
|
10492
10991
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10493
10992
|
var __filename = fileURLToPath2(import.meta.url);
|
|
10494
|
-
var __dirname =
|
|
10993
|
+
var __dirname = path21.dirname(__filename);
|
|
10495
10994
|
function getUiAssetsPath() {
|
|
10496
|
-
const pkgRootPath =
|
|
10497
|
-
if (
|
|
10995
|
+
const pkgRootPath = path21.join(__dirname, "..", "ui-assets");
|
|
10996
|
+
if (fs21.existsSync(pkgRootPath)) {
|
|
10498
10997
|
return pkgRootPath;
|
|
10499
10998
|
}
|
|
10500
|
-
const bundledPath =
|
|
10501
|
-
if (
|
|
10999
|
+
const bundledPath = path21.join(__dirname, "ui-assets");
|
|
11000
|
+
if (fs21.existsSync(bundledPath)) {
|
|
10502
11001
|
return bundledPath;
|
|
10503
11002
|
}
|
|
10504
|
-
const devPath =
|
|
10505
|
-
if (
|
|
11003
|
+
const devPath = path21.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
|
|
11004
|
+
if (fs21.existsSync(devPath)) {
|
|
10506
11005
|
return devPath;
|
|
10507
11006
|
}
|
|
10508
11007
|
return null;
|
|
@@ -10592,10 +11091,16 @@ async function startServer(config) {
|
|
|
10592
11091
|
startSecurityWatcher(1e4);
|
|
10593
11092
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
10594
11093
|
const skillsDir2 = `${agentHome}/.openclaw/skills`;
|
|
10595
|
-
if (!
|
|
10596
|
-
|
|
11094
|
+
if (!fs23.existsSync(skillsDir2)) {
|
|
11095
|
+
fs23.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
|
|
10597
11096
|
console.log(`[Daemon] Created skills directory: ${skillsDir2}`);
|
|
10598
11097
|
}
|
|
11098
|
+
try {
|
|
11099
|
+
await getInstallationKey();
|
|
11100
|
+
console.log("[Daemon] Installation key ready");
|
|
11101
|
+
} catch (err) {
|
|
11102
|
+
console.warn("[Daemon] Failed to initialize installation key:", err.message);
|
|
11103
|
+
}
|
|
10599
11104
|
startSkillsWatcher(skillsDir2, {
|
|
10600
11105
|
onUntrustedDetected: (info) => emitSkillUntrustedDetected(info.name, info.reason),
|
|
10601
11106
|
onApproved: (name) => emitSkillApproved(name)
|