@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/main.js
CHANGED
|
@@ -1044,10 +1044,7 @@ PLISTEOF`);
|
|
|
1044
1044
|
}
|
|
1045
1045
|
async function startOpenClawServices() {
|
|
1046
1046
|
try {
|
|
1047
|
-
|
|
1048
|
-
await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1049
|
-
} catch {
|
|
1050
|
-
}
|
|
1047
|
+
await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1051
1048
|
return {
|
|
1052
1049
|
success: true,
|
|
1053
1050
|
message: "OpenClaw gateway started"
|
|
@@ -1062,10 +1059,7 @@ async function startOpenClawServices() {
|
|
|
1062
1059
|
}
|
|
1063
1060
|
async function stopOpenClawServices() {
|
|
1064
1061
|
try {
|
|
1065
|
-
|
|
1066
|
-
await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1067
|
-
} catch {
|
|
1068
|
-
}
|
|
1062
|
+
await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1069
1063
|
return {
|
|
1070
1064
|
success: true,
|
|
1071
1065
|
message: "OpenClaw gateway stopped"
|
|
@@ -1080,10 +1074,7 @@ async function stopOpenClawServices() {
|
|
|
1080
1074
|
}
|
|
1081
1075
|
async function restartOpenClawServices() {
|
|
1082
1076
|
try {
|
|
1083
|
-
|
|
1084
|
-
await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1085
|
-
} catch {
|
|
1086
|
-
}
|
|
1077
|
+
await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
1087
1078
|
return {
|
|
1088
1079
|
success: true,
|
|
1089
1080
|
message: "OpenClaw gateway restarted"
|
|
@@ -1166,15 +1157,29 @@ function getOpenClawStatusSync() {
|
|
|
1166
1157
|
async function getOpenClawDashboardUrl() {
|
|
1167
1158
|
try {
|
|
1168
1159
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
)
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1160
|
+
const configPath = path22.join(agentHome, ".openclaw", "openclaw.json");
|
|
1161
|
+
let raw;
|
|
1162
|
+
try {
|
|
1163
|
+
raw = await fs32.readFile(configPath, "utf-8");
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
if (err.code === "EACCES") {
|
|
1166
|
+
const agentUsername = path22.basename(agentHome);
|
|
1167
|
+
const { stdout } = await execAsync3(
|
|
1168
|
+
`sudo -H -u ${agentUsername} cat "${configPath}"`,
|
|
1169
|
+
{ cwd: "/" }
|
|
1170
|
+
);
|
|
1171
|
+
raw = stdout;
|
|
1172
|
+
} else {
|
|
1173
|
+
return { success: false, error: `Cannot read openclaw.json: ${err.message}` };
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
const config = JSON.parse(raw);
|
|
1177
|
+
const port = config.gateway?.port;
|
|
1178
|
+
const token = config.gateway?.auth?.token;
|
|
1179
|
+
if (!port || !token) {
|
|
1180
|
+
return { success: false, error: "Gateway port or auth token not found in openclaw.json" };
|
|
1177
1181
|
}
|
|
1182
|
+
const url = `http://127.0.0.1:${port}/?token=${token}`;
|
|
1178
1183
|
return { success: true, url };
|
|
1179
1184
|
} catch (error) {
|
|
1180
1185
|
return { success: false, error: `Failed to get dashboard URL: ${error.message}` };
|
|
@@ -1288,6 +1293,41 @@ var init_crypto = __esm({
|
|
|
1288
1293
|
}
|
|
1289
1294
|
});
|
|
1290
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
|
+
|
|
1291
1331
|
// libs/shield-daemon/src/vault/index.ts
|
|
1292
1332
|
import * as fs4 from "node:fs";
|
|
1293
1333
|
import * as path4 from "node:path";
|
|
@@ -1305,6 +1345,7 @@ var init_vault = __esm({
|
|
|
1305
1345
|
init_crypto();
|
|
1306
1346
|
init_paths();
|
|
1307
1347
|
init_crypto();
|
|
1348
|
+
init_installation_key();
|
|
1308
1349
|
Vault = class {
|
|
1309
1350
|
key;
|
|
1310
1351
|
vaultPath;
|
|
@@ -1761,7 +1802,7 @@ var init_secret_sync = __esm({
|
|
|
1761
1802
|
});
|
|
1762
1803
|
|
|
1763
1804
|
// libs/shield-daemon/src/main.ts
|
|
1764
|
-
import * as
|
|
1805
|
+
import * as fs24 from "node:fs";
|
|
1765
1806
|
|
|
1766
1807
|
// libs/shield-daemon/src/config/index.ts
|
|
1767
1808
|
init_paths();
|
|
@@ -1769,7 +1810,7 @@ init_defaults();
|
|
|
1769
1810
|
init_loader();
|
|
1770
1811
|
|
|
1771
1812
|
// libs/shield-daemon/src/server.ts
|
|
1772
|
-
import * as
|
|
1813
|
+
import * as fs23 from "node:fs";
|
|
1773
1814
|
import Fastify from "fastify";
|
|
1774
1815
|
import cors from "@fastify/cors";
|
|
1775
1816
|
import fastifyStatic from "@fastify/static";
|
|
@@ -1842,7 +1883,7 @@ init_state();
|
|
|
1842
1883
|
init_vault();
|
|
1843
1884
|
|
|
1844
1885
|
// libs/shield-daemon/src/auth/session.ts
|
|
1845
|
-
import * as
|
|
1886
|
+
import * as crypto3 from "node:crypto";
|
|
1846
1887
|
import { DEFAULT_AUTH_CONFIG } from "@agenshield/ipc";
|
|
1847
1888
|
var SessionManager = class {
|
|
1848
1889
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -1856,7 +1897,7 @@ var SessionManager = class {
|
|
|
1856
1897
|
* Generate a secure random token
|
|
1857
1898
|
*/
|
|
1858
1899
|
generateToken() {
|
|
1859
|
-
return
|
|
1900
|
+
return crypto3.randomBytes(32).toString("base64url");
|
|
1860
1901
|
}
|
|
1861
1902
|
/**
|
|
1862
1903
|
* Create a new session
|
|
@@ -2141,11 +2182,24 @@ function getOpenClawConfigPath() {
|
|
|
2141
2182
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
2142
2183
|
return path8.join(agentHome, ".openclaw", "openclaw.json");
|
|
2143
2184
|
}
|
|
2144
|
-
function
|
|
2185
|
+
function readOpenClawConfig() {
|
|
2145
2186
|
const configPath = getOpenClawConfigPath();
|
|
2146
2187
|
try {
|
|
2147
2188
|
if (fs8.existsSync(configPath)) {
|
|
2148
|
-
|
|
2189
|
+
try {
|
|
2190
|
+
return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
2191
|
+
} catch (err) {
|
|
2192
|
+
if (err.code === "EACCES") {
|
|
2193
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
2194
|
+
const agentUsername = path8.basename(agentHome);
|
|
2195
|
+
const raw = execSync6(
|
|
2196
|
+
`sudo -H -u ${agentUsername} cat "${configPath}"`,
|
|
2197
|
+
{ encoding: "utf-8", cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
|
|
2198
|
+
);
|
|
2199
|
+
return JSON.parse(raw);
|
|
2200
|
+
}
|
|
2201
|
+
throw err;
|
|
2202
|
+
}
|
|
2149
2203
|
}
|
|
2150
2204
|
} catch {
|
|
2151
2205
|
console.warn("[OpenClawConfig] Failed to read openclaw.json, starting fresh");
|
|
@@ -2163,7 +2217,7 @@ function writeConfig(config) {
|
|
|
2163
2217
|
const agentUsername = path8.basename(agentHome);
|
|
2164
2218
|
execSync6(
|
|
2165
2219
|
`sudo -H -u ${agentUsername} tee "${configPath}" > /dev/null`,
|
|
2166
|
-
{ input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"] }
|
|
2220
|
+
{ input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"], cwd: "/" }
|
|
2167
2221
|
);
|
|
2168
2222
|
} else {
|
|
2169
2223
|
throw err;
|
|
@@ -2171,27 +2225,30 @@ function writeConfig(config) {
|
|
|
2171
2225
|
}
|
|
2172
2226
|
}
|
|
2173
2227
|
function addSkillEntry(slug) {
|
|
2174
|
-
const config =
|
|
2228
|
+
const config = readOpenClawConfig();
|
|
2175
2229
|
if (!config.skills) {
|
|
2176
2230
|
config.skills = {};
|
|
2177
2231
|
}
|
|
2178
2232
|
if (!config.skills.entries) {
|
|
2179
2233
|
config.skills.entries = {};
|
|
2180
2234
|
}
|
|
2181
|
-
config.skills.entries[slug]
|
|
2235
|
+
const existing = config.skills.entries[slug] ?? {};
|
|
2236
|
+
config.skills.entries[slug] = { ...existing, enabled: true };
|
|
2182
2237
|
writeConfig(config);
|
|
2183
2238
|
console.log(`[OpenClawConfig] Added skill entry: ${slug}`);
|
|
2184
2239
|
}
|
|
2185
2240
|
function removeSkillEntry(slug) {
|
|
2186
|
-
const config =
|
|
2241
|
+
const config = readOpenClawConfig();
|
|
2187
2242
|
if (config.skills?.entries?.[slug]) {
|
|
2188
|
-
|
|
2243
|
+
const existing = config.skills.entries[slug];
|
|
2244
|
+
delete existing.env;
|
|
2245
|
+
config.skills.entries[slug] = { ...existing, enabled: false };
|
|
2189
2246
|
writeConfig(config);
|
|
2190
|
-
console.log(`[OpenClawConfig]
|
|
2247
|
+
console.log(`[OpenClawConfig] Disabled skill entry: ${slug}`);
|
|
2191
2248
|
}
|
|
2192
2249
|
}
|
|
2193
2250
|
function syncOpenClawFromPolicies(policies) {
|
|
2194
|
-
const config =
|
|
2251
|
+
const config = readOpenClawConfig();
|
|
2195
2252
|
if (!config.skills) config.skills = {};
|
|
2196
2253
|
const allowBundled = [];
|
|
2197
2254
|
for (const p of policies) {
|
|
@@ -2410,7 +2467,7 @@ function generatePolicyMarkdown(policies, knownSkills) {
|
|
|
2410
2467
|
// libs/shield-daemon/src/watchers/skills.ts
|
|
2411
2468
|
import * as fs11 from "node:fs";
|
|
2412
2469
|
import * as path11 from "node:path";
|
|
2413
|
-
import * as
|
|
2470
|
+
import * as crypto4 from "node:crypto";
|
|
2414
2471
|
import { parseSkillMd } from "@agenshield/sandbox";
|
|
2415
2472
|
|
|
2416
2473
|
// libs/shield-daemon/src/services/marketplace.ts
|
|
@@ -2439,35 +2496,35 @@ var ANALYSIS_TIMEOUT = 4 * 6e4;
|
|
|
2439
2496
|
var SEARCH_CACHE_TTL = 6e4;
|
|
2440
2497
|
var DETAIL_CACHE_TTL = 5 * 6e4;
|
|
2441
2498
|
var SHORT_TIMEOUT = 1e4;
|
|
2442
|
-
async function convexAction(
|
|
2499
|
+
async function convexAction(path24, args, timeout) {
|
|
2443
2500
|
const res = await fetch(`${CONVEX_BASE}/api/action`, {
|
|
2444
2501
|
method: "POST",
|
|
2445
2502
|
signal: AbortSignal.timeout(timeout),
|
|
2446
2503
|
headers: { "Content-Type": "application/json" },
|
|
2447
|
-
body: JSON.stringify({ path:
|
|
2504
|
+
body: JSON.stringify({ path: path24, args, format: "json" })
|
|
2448
2505
|
});
|
|
2449
2506
|
if (!res.ok) {
|
|
2450
|
-
throw new Error(`Convex action ${
|
|
2507
|
+
throw new Error(`Convex action ${path24} returned ${res.status}`);
|
|
2451
2508
|
}
|
|
2452
2509
|
const body = await res.json();
|
|
2453
2510
|
if (body.status === "error") {
|
|
2454
|
-
throw new Error(`Convex action ${
|
|
2511
|
+
throw new Error(`Convex action ${path24}: ${body.errorMessage ?? "unknown error"}`);
|
|
2455
2512
|
}
|
|
2456
2513
|
return body.value;
|
|
2457
2514
|
}
|
|
2458
|
-
async function convexQuery(
|
|
2515
|
+
async function convexQuery(path24, args, timeout) {
|
|
2459
2516
|
const res = await fetch(`${CONVEX_BASE}/api/query`, {
|
|
2460
2517
|
method: "POST",
|
|
2461
2518
|
signal: AbortSignal.timeout(timeout),
|
|
2462
2519
|
headers: { "Content-Type": "application/json" },
|
|
2463
|
-
body: JSON.stringify({ path:
|
|
2520
|
+
body: JSON.stringify({ path: path24, args, format: "json" })
|
|
2464
2521
|
});
|
|
2465
2522
|
if (!res.ok) {
|
|
2466
|
-
throw new Error(`Convex query ${
|
|
2523
|
+
throw new Error(`Convex query ${path24} returned ${res.status}`);
|
|
2467
2524
|
}
|
|
2468
2525
|
const body = await res.json();
|
|
2469
2526
|
if (body.status === "error") {
|
|
2470
|
-
throw new Error(`Convex query ${
|
|
2527
|
+
throw new Error(`Convex query ${path24}: ${body.errorMessage ?? "unknown error"}`);
|
|
2471
2528
|
}
|
|
2472
2529
|
return body.value;
|
|
2473
2530
|
}
|
|
@@ -2599,6 +2656,17 @@ function updateDownloadedAnalysis(slug, analysis) {
|
|
|
2599
2656
|
} catch {
|
|
2600
2657
|
}
|
|
2601
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
|
+
}
|
|
2602
2670
|
function listDownloadedSkills() {
|
|
2603
2671
|
const baseDir = getMarketplaceDir();
|
|
2604
2672
|
if (!fs9.existsSync(baseDir)) return [];
|
|
@@ -2619,7 +2687,8 @@ function listDownloadedSkills() {
|
|
|
2619
2687
|
tags: meta.tags ?? [],
|
|
2620
2688
|
hasAnalysis: !!meta.analysis,
|
|
2621
2689
|
source: meta.source,
|
|
2622
|
-
analysis: meta.analysis
|
|
2690
|
+
analysis: meta.analysis,
|
|
2691
|
+
wasInstalled: meta.wasInstalled ?? false
|
|
2623
2692
|
});
|
|
2624
2693
|
} catch {
|
|
2625
2694
|
}
|
|
@@ -2669,9 +2738,9 @@ function inlineImagesInMarkdown(markdown, files) {
|
|
|
2669
2738
|
const mime = isImageExt(file.name);
|
|
2670
2739
|
if (mime && file.content.startsWith("data:")) {
|
|
2671
2740
|
imageMap.set(file.name, file.content);
|
|
2672
|
-
const
|
|
2673
|
-
if (
|
|
2674
|
-
imageMap.set(
|
|
2741
|
+
const basename6 = file.name.split("/").pop() ?? "";
|
|
2742
|
+
if (basename6 && !imageMap.has(basename6)) {
|
|
2743
|
+
imageMap.set(basename6, file.content);
|
|
2675
2744
|
}
|
|
2676
2745
|
}
|
|
2677
2746
|
}
|
|
@@ -3197,10 +3266,10 @@ function emitSecurityWarning(warning) {
|
|
|
3197
3266
|
function emitSecurityCritical(issue) {
|
|
3198
3267
|
daemonEvents.broadcast("security:critical", { message: issue });
|
|
3199
3268
|
}
|
|
3200
|
-
function emitApiRequest(method,
|
|
3269
|
+
function emitApiRequest(method, path24, statusCode, duration, requestBody, responseBody) {
|
|
3201
3270
|
daemonEvents.broadcast("api:request", {
|
|
3202
3271
|
method,
|
|
3203
|
-
path:
|
|
3272
|
+
path: path24,
|
|
3204
3273
|
statusCode,
|
|
3205
3274
|
duration,
|
|
3206
3275
|
...requestBody !== void 0 && { requestBody },
|
|
@@ -3216,6 +3285,9 @@ function emitSkillUntrustedDetected(name, reason) {
|
|
|
3216
3285
|
function emitSkillApproved(skillName) {
|
|
3217
3286
|
daemonEvents.broadcast("skills:approved", { name: skillName });
|
|
3218
3287
|
}
|
|
3288
|
+
function emitExecDenied(command, reason) {
|
|
3289
|
+
daemonEvents.broadcast("exec:denied", { command, reason });
|
|
3290
|
+
}
|
|
3219
3291
|
function emitAgenCoAuthRequired(authUrl, integration) {
|
|
3220
3292
|
daemonEvents.broadcast("agenco:auth_required", { authUrl, integration });
|
|
3221
3293
|
}
|
|
@@ -3255,6 +3327,56 @@ function emitEvent(type, data) {
|
|
|
3255
3327
|
|
|
3256
3328
|
// libs/shield-daemon/src/watchers/skills.ts
|
|
3257
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();
|
|
3258
3380
|
function getApprovedSkillsPath() {
|
|
3259
3381
|
return path11.join(getSystemConfigDir(), "approved-skills.json");
|
|
3260
3382
|
}
|
|
@@ -3393,7 +3515,7 @@ function computeSkillHash(skillDir) {
|
|
|
3393
3515
|
const files = readSkillFiles(skillDir);
|
|
3394
3516
|
if (files.length === 0) return null;
|
|
3395
3517
|
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
3396
|
-
const hash =
|
|
3518
|
+
const hash = crypto4.createHash("sha256");
|
|
3397
3519
|
for (const file of files) {
|
|
3398
3520
|
hash.update(file.name);
|
|
3399
3521
|
hash.update(file.content);
|
|
@@ -3422,10 +3544,33 @@ function scanSkills() {
|
|
|
3422
3544
|
const approvedEntry = approvedMap.get(skillName);
|
|
3423
3545
|
if (!approvedEntry) {
|
|
3424
3546
|
const fullPath = path11.join(skillsDir, skillName);
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
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
|
+
}
|
|
3429
3574
|
}
|
|
3430
3575
|
}
|
|
3431
3576
|
} else if (approvedEntry.hash) {
|
|
@@ -3455,26 +3600,16 @@ function scanSkills() {
|
|
|
3455
3600
|
}
|
|
3456
3601
|
function detectOpenClawMismatches() {
|
|
3457
3602
|
try {
|
|
3458
|
-
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
3459
|
-
const configPath = path11.join(agentHome, ".openclaw", "openclaw.json");
|
|
3460
|
-
if (!fs11.existsSync(configPath)) return;
|
|
3461
|
-
const raw = fs11.readFileSync(configPath, "utf-8");
|
|
3462
|
-
const config = JSON.parse(raw);
|
|
3463
|
-
if (!config.skills?.entries) return;
|
|
3464
3603
|
const approved = loadApprovedSkills();
|
|
3465
3604
|
const approvedNames = new Set(approved.map((a) => a.name));
|
|
3466
|
-
const
|
|
3467
|
-
|
|
3468
|
-
for (const name of entries) {
|
|
3605
|
+
const config = readOpenClawConfig();
|
|
3606
|
+
if (!config.skills?.entries) return;
|
|
3607
|
+
for (const name of Object.keys(config.skills.entries)) {
|
|
3469
3608
|
if (!approvedNames.has(name)) {
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
console.log(`[SkillsWatcher] Removed stale openclaw.json entry: ${name}`);
|
|
3609
|
+
removeSkillEntry(name);
|
|
3610
|
+
console.log(`[SkillsWatcher] Disabled stale openclaw.json entry: ${name}`);
|
|
3473
3611
|
}
|
|
3474
3612
|
}
|
|
3475
|
-
if (changed) {
|
|
3476
|
-
fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
3477
|
-
}
|
|
3478
3613
|
} catch {
|
|
3479
3614
|
}
|
|
3480
3615
|
}
|
|
@@ -3541,7 +3676,7 @@ function approveSkill(skillName) {
|
|
|
3541
3676
|
const cachedFiles = getDownloadedSkillFiles(slug);
|
|
3542
3677
|
let hash;
|
|
3543
3678
|
if (cachedFiles.length > 0) {
|
|
3544
|
-
const h =
|
|
3679
|
+
const h = crypto4.createHash("sha256");
|
|
3545
3680
|
const sorted = [...cachedFiles].sort((a, b) => a.name.localeCompare(b.name));
|
|
3546
3681
|
for (const file of sorted) {
|
|
3547
3682
|
h.update(file.name);
|
|
@@ -3610,14 +3745,15 @@ function listApproved() {
|
|
|
3610
3745
|
function getSkillsDir() {
|
|
3611
3746
|
return skillsDir;
|
|
3612
3747
|
}
|
|
3613
|
-
function addToApprovedList(skillName, publisher, hash) {
|
|
3748
|
+
function addToApprovedList(skillName, publisher, hash, slug) {
|
|
3614
3749
|
const approved = loadApprovedSkills();
|
|
3615
3750
|
if (!approved.some((s) => s.name === skillName)) {
|
|
3616
3751
|
approved.push({
|
|
3617
3752
|
name: skillName,
|
|
3618
3753
|
approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3619
3754
|
...publisher ? { publisher } : {},
|
|
3620
|
-
...hash ? { hash } : {}
|
|
3755
|
+
...hash ? { hash } : {},
|
|
3756
|
+
...slug ? { slug } : {}
|
|
3621
3757
|
});
|
|
3622
3758
|
saveApprovedSkills(approved);
|
|
3623
3759
|
}
|
|
@@ -3864,7 +4000,7 @@ async function securityRoutes(app) {
|
|
|
3864
4000
|
// libs/shield-daemon/src/auth/passcode.ts
|
|
3865
4001
|
init_vault();
|
|
3866
4002
|
init_state();
|
|
3867
|
-
import * as
|
|
4003
|
+
import * as crypto5 from "node:crypto";
|
|
3868
4004
|
import { DEFAULT_AUTH_CONFIG as DEFAULT_AUTH_CONFIG2 } from "@agenshield/ipc";
|
|
3869
4005
|
var ITERATIONS = 1e5;
|
|
3870
4006
|
var KEY_LENGTH = 64;
|
|
@@ -3872,8 +4008,8 @@ var DIGEST = "sha512";
|
|
|
3872
4008
|
var SALT_LENGTH = 16;
|
|
3873
4009
|
function hashPasscode(passcode) {
|
|
3874
4010
|
return new Promise((resolve3, reject) => {
|
|
3875
|
-
const salt =
|
|
3876
|
-
|
|
4011
|
+
const salt = crypto5.randomBytes(SALT_LENGTH);
|
|
4012
|
+
crypto5.pbkdf2(passcode, salt, ITERATIONS, KEY_LENGTH, DIGEST, (err, derivedKey) => {
|
|
3877
4013
|
if (err) {
|
|
3878
4014
|
reject(err);
|
|
3879
4015
|
return;
|
|
@@ -3893,12 +4029,12 @@ function verifyPasscode(passcode, storedHash) {
|
|
|
3893
4029
|
const iterations = parseInt(parts[0], 10);
|
|
3894
4030
|
const salt = Buffer.from(parts[1], "base64");
|
|
3895
4031
|
const hash = Buffer.from(parts[2], "base64");
|
|
3896
|
-
|
|
4032
|
+
crypto5.pbkdf2(passcode, salt, iterations, hash.length, DIGEST, (err, derivedKey) => {
|
|
3897
4033
|
if (err) {
|
|
3898
4034
|
reject(err);
|
|
3899
4035
|
return;
|
|
3900
4036
|
}
|
|
3901
|
-
resolve3(
|
|
4037
|
+
resolve3(crypto5.timingSafeEqual(hash, derivedKey));
|
|
3902
4038
|
});
|
|
3903
4039
|
});
|
|
3904
4040
|
}
|
|
@@ -4030,16 +4166,16 @@ var PROTECTED_ROUTES = [
|
|
|
4030
4166
|
{ method: "POST", path: "/api/skills/install" },
|
|
4031
4167
|
{ method: "GET", path: "/api/openclaw/dashboard-url" }
|
|
4032
4168
|
];
|
|
4033
|
-
function isProtectedRoute(method,
|
|
4169
|
+
function isProtectedRoute(method, path24) {
|
|
4034
4170
|
return PROTECTED_ROUTES.some(
|
|
4035
|
-
(route) => route.method === method &&
|
|
4171
|
+
(route) => route.method === method && path24.startsWith(route.path)
|
|
4036
4172
|
);
|
|
4037
4173
|
}
|
|
4038
4174
|
function createAuthHook() {
|
|
4039
4175
|
return async (request2, reply) => {
|
|
4040
4176
|
const method = request2.method;
|
|
4041
|
-
const
|
|
4042
|
-
if (!isProtectedRoute(method,
|
|
4177
|
+
const path24 = request2.url.split("?")[0];
|
|
4178
|
+
if (!isProtectedRoute(method, path24)) {
|
|
4043
4179
|
return;
|
|
4044
4180
|
}
|
|
4045
4181
|
if (!isAuthenticated(request2)) {
|
|
@@ -4070,6 +4206,12 @@ data: ${data}
|
|
|
4070
4206
|
|
|
4071
4207
|
`;
|
|
4072
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
|
+
}
|
|
4073
4215
|
async function sseRoutes(app) {
|
|
4074
4216
|
app.get("/sse/events", async (request2, reply) => {
|
|
4075
4217
|
const authenticated = isAuthenticated(request2);
|
|
@@ -4095,7 +4237,7 @@ async function sseRoutes(app) {
|
|
|
4095
4237
|
reply.raw.write(authenticated ? formatSSE(statusEvent) : formatStrippedSSE(statusEvent));
|
|
4096
4238
|
const unsubscribe = daemonEvents.subscribe((event) => {
|
|
4097
4239
|
try {
|
|
4098
|
-
if (event
|
|
4240
|
+
if (shouldSendFull(event, authenticated)) {
|
|
4099
4241
|
reply.raw.write(formatSSE(event));
|
|
4100
4242
|
} else {
|
|
4101
4243
|
reply.raw.write(formatStrippedSSE(event));
|
|
@@ -4143,7 +4285,7 @@ async function sseRoutes(app) {
|
|
|
4143
4285
|
const unsubscribe = daemonEvents.subscribe((event) => {
|
|
4144
4286
|
if (event.type.startsWith(filter) || event.type === "heartbeat" || event.type === "daemon:status") {
|
|
4145
4287
|
try {
|
|
4146
|
-
if (event
|
|
4288
|
+
if (shouldSendFull(event, authenticated)) {
|
|
4147
4289
|
reply.raw.write(formatSSE(event));
|
|
4148
4290
|
} else {
|
|
4149
4291
|
reply.raw.write(formatStrippedSSE(event));
|
|
@@ -7538,19 +7680,50 @@ async function agencoRoutes(app) {
|
|
|
7538
7680
|
}
|
|
7539
7681
|
|
|
7540
7682
|
// libs/shield-daemon/src/routes/skills.ts
|
|
7541
|
-
import * as
|
|
7542
|
-
import * as
|
|
7683
|
+
import * as fs17 from "node:fs";
|
|
7684
|
+
import * as path17 from "node:path";
|
|
7543
7685
|
import { execSync as execSync9 } from "node:child_process";
|
|
7544
|
-
import { parseSkillMd as
|
|
7686
|
+
import { parseSkillMd as parseSkillMd3, stripEnvFromSkillMd as stripEnvFromSkillMd2 } from "@agenshield/sandbox";
|
|
7545
7687
|
|
|
7546
7688
|
// libs/shield-daemon/src/services/skill-lifecycle.ts
|
|
7547
7689
|
import * as fs14 from "node:fs";
|
|
7548
7690
|
import * as path14 from "node:path";
|
|
7549
7691
|
import { execSync as execSync8 } from "node:child_process";
|
|
7550
|
-
function
|
|
7551
|
-
|
|
7552
|
-
fs14.mkdirSync(
|
|
7692
|
+
function sudoMkdir(dir, agentUsername) {
|
|
7693
|
+
try {
|
|
7694
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
7695
|
+
} catch (err) {
|
|
7696
|
+
if (err.code === "EACCES") {
|
|
7697
|
+
execSync8(`sudo -H -u ${agentUsername} /bin/mkdir -p "${dir}"`, { cwd: "/", stdio: "pipe" });
|
|
7698
|
+
} else {
|
|
7699
|
+
throw err;
|
|
7700
|
+
}
|
|
7701
|
+
}
|
|
7702
|
+
}
|
|
7703
|
+
function sudoWriteFile(filePath, content, agentUsername, mode) {
|
|
7704
|
+
try {
|
|
7705
|
+
fs14.writeFileSync(filePath, content, { mode });
|
|
7706
|
+
} catch (err) {
|
|
7707
|
+
if (err.code === "EACCES") {
|
|
7708
|
+
execSync8(
|
|
7709
|
+
`sudo -H -u ${agentUsername} tee "${filePath}" > /dev/null`,
|
|
7710
|
+
{ input: content, cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
|
|
7711
|
+
);
|
|
7712
|
+
if (mode) {
|
|
7713
|
+
try {
|
|
7714
|
+
execSync8(`sudo -H -u ${agentUsername} chmod ${mode.toString(8)} "${filePath}"`, { cwd: "/", stdio: "pipe" });
|
|
7715
|
+
} catch {
|
|
7716
|
+
}
|
|
7717
|
+
}
|
|
7718
|
+
} else {
|
|
7719
|
+
throw err;
|
|
7720
|
+
}
|
|
7553
7721
|
}
|
|
7722
|
+
}
|
|
7723
|
+
function createSkillWrapper(name, binDir) {
|
|
7724
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
7725
|
+
const agentUsername = path14.basename(agentHome);
|
|
7726
|
+
sudoMkdir(binDir, agentUsername);
|
|
7554
7727
|
const wrapperPath = path14.join(binDir, name);
|
|
7555
7728
|
const wrapperContent = `#!/bin/bash
|
|
7556
7729
|
# ${name} skill wrapper - policy-enforced execution
|
|
@@ -7558,7 +7731,7 @@ function createSkillWrapper(name, binDir) {
|
|
|
7558
7731
|
if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
|
|
7559
7732
|
exec /opt/agenshield/bin/shield-client skill run "${name}" "$@"
|
|
7560
7733
|
`;
|
|
7561
|
-
|
|
7734
|
+
sudoWriteFile(wrapperPath, wrapperContent, agentUsername, 493);
|
|
7562
7735
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
7563
7736
|
try {
|
|
7564
7737
|
execSync8(`chown root:${socketGroup} "${wrapperPath}"`, { stdio: "pipe" });
|
|
@@ -7603,8 +7776,8 @@ function removeSkillPolicy(name) {
|
|
|
7603
7776
|
}
|
|
7604
7777
|
|
|
7605
7778
|
// libs/shield-daemon/src/routes/marketplace.ts
|
|
7606
|
-
import * as
|
|
7607
|
-
import * as
|
|
7779
|
+
import * as fs16 from "node:fs";
|
|
7780
|
+
import * as path16 from "node:path";
|
|
7608
7781
|
|
|
7609
7782
|
// libs/shield-broker/dist/index.js
|
|
7610
7783
|
import { exec as exec4 } from "node:child_process";
|
|
@@ -7880,6 +8053,176 @@ async function uninstallSkillViaBroker(slug, options = {}) {
|
|
|
7880
8053
|
return result;
|
|
7881
8054
|
}
|
|
7882
8055
|
|
|
8056
|
+
// libs/shield-daemon/src/routes/marketplace.ts
|
|
8057
|
+
import { stripEnvFromSkillMd } from "@agenshield/sandbox";
|
|
8058
|
+
|
|
8059
|
+
// libs/shield-daemon/src/services/skill-deps.ts
|
|
8060
|
+
import * as fs15 from "node:fs";
|
|
8061
|
+
import * as path15 from "node:path";
|
|
8062
|
+
import { parseSkillMd as parseSkillMd2, extractSkillInfo } from "@agenshield/sandbox";
|
|
8063
|
+
import { execWithProgress as execWithProgress3 } from "@agenshield/sandbox";
|
|
8064
|
+
var SUPPORTED_KINDS = /* @__PURE__ */ new Set(["brew", "npm", "pip"]);
|
|
8065
|
+
var SAFE_PACKAGE_RE = /^[a-zA-Z0-9@/_.\-]+$/;
|
|
8066
|
+
var SYSTEM_PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
8067
|
+
function findSkillMdRecursive(dir, depth = 0) {
|
|
8068
|
+
if (depth > 3) return null;
|
|
8069
|
+
try {
|
|
8070
|
+
for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
|
|
8071
|
+
const candidate = path15.join(dir, name);
|
|
8072
|
+
if (fs15.existsSync(candidate)) return candidate;
|
|
8073
|
+
}
|
|
8074
|
+
const entries = fs15.readdirSync(dir, { withFileTypes: true });
|
|
8075
|
+
for (const entry of entries) {
|
|
8076
|
+
if (!entry.isDirectory()) continue;
|
|
8077
|
+
const found = findSkillMdRecursive(path15.join(dir, entry.name), depth + 1);
|
|
8078
|
+
if (found) return found;
|
|
8079
|
+
}
|
|
8080
|
+
} catch {
|
|
8081
|
+
}
|
|
8082
|
+
return null;
|
|
8083
|
+
}
|
|
8084
|
+
async function executeSkillInstallSteps(options) {
|
|
8085
|
+
const { slug, skillDir, agentHome, agentUsername, onLog } = options;
|
|
8086
|
+
const installed = [];
|
|
8087
|
+
const errors = [];
|
|
8088
|
+
const skillMdPath = findSkillMdRecursive(skillDir);
|
|
8089
|
+
if (!skillMdPath) {
|
|
8090
|
+
return { success: true, installed, errors };
|
|
8091
|
+
}
|
|
8092
|
+
let content;
|
|
8093
|
+
try {
|
|
8094
|
+
content = fs15.readFileSync(skillMdPath, "utf-8");
|
|
8095
|
+
} catch {
|
|
8096
|
+
return { success: true, installed, errors };
|
|
8097
|
+
}
|
|
8098
|
+
const parsed = parseSkillMd2(content);
|
|
8099
|
+
if (!parsed) {
|
|
8100
|
+
return { success: true, installed, errors };
|
|
8101
|
+
}
|
|
8102
|
+
const info = extractSkillInfo(parsed.metadata);
|
|
8103
|
+
const installSteps = info.installSteps;
|
|
8104
|
+
if (!Array.isArray(installSteps) || installSteps.length === 0) {
|
|
8105
|
+
return { success: true, installed, errors };
|
|
8106
|
+
}
|
|
8107
|
+
onLog(`Found ${installSteps.length} dependency install step(s) for ${slug}`);
|
|
8108
|
+
for (const step of installSteps) {
|
|
8109
|
+
const kind = step.kind;
|
|
8110
|
+
const stepId = step.id || kind;
|
|
8111
|
+
if (!SUPPORTED_KINDS.has(kind)) {
|
|
8112
|
+
errors.push(`Unsupported install kind "${kind}" (step: ${stepId})`);
|
|
8113
|
+
continue;
|
|
8114
|
+
}
|
|
8115
|
+
try {
|
|
8116
|
+
switch (kind) {
|
|
8117
|
+
case "brew": {
|
|
8118
|
+
const formula = step.formula;
|
|
8119
|
+
if (!formula) {
|
|
8120
|
+
errors.push(`Brew step "${stepId}" missing formula`);
|
|
8121
|
+
break;
|
|
8122
|
+
}
|
|
8123
|
+
if (!SAFE_PACKAGE_RE.test(formula)) {
|
|
8124
|
+
errors.push(`Unsafe brew formula name: ${formula}`);
|
|
8125
|
+
break;
|
|
8126
|
+
}
|
|
8127
|
+
onLog(`Installing brew formula: ${formula}`);
|
|
8128
|
+
const brewCmd = [
|
|
8129
|
+
`export HOME="${agentHome}"`,
|
|
8130
|
+
`export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8131
|
+
`brew install ${formula}`
|
|
8132
|
+
].join(" && ");
|
|
8133
|
+
await execWithProgress3(
|
|
8134
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${brewCmd}'`,
|
|
8135
|
+
onLog,
|
|
8136
|
+
{ timeout: 12e4, cwd: "/" }
|
|
8137
|
+
);
|
|
8138
|
+
installed.push(formula);
|
|
8139
|
+
break;
|
|
8140
|
+
}
|
|
8141
|
+
case "npm": {
|
|
8142
|
+
const pkg = step["package"];
|
|
8143
|
+
if (!pkg) {
|
|
8144
|
+
errors.push(`npm step "${stepId}" missing package`);
|
|
8145
|
+
break;
|
|
8146
|
+
}
|
|
8147
|
+
if (!SAFE_PACKAGE_RE.test(pkg)) {
|
|
8148
|
+
errors.push(`Unsafe npm package name: ${pkg}`);
|
|
8149
|
+
break;
|
|
8150
|
+
}
|
|
8151
|
+
onLog(`Installing npm package: ${pkg}`);
|
|
8152
|
+
const npmCmd = [
|
|
8153
|
+
`export HOME="${agentHome}"`,
|
|
8154
|
+
`export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8155
|
+
`source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
|
|
8156
|
+
`npm install -g ${pkg}`
|
|
8157
|
+
].join(" && ");
|
|
8158
|
+
await execWithProgress3(
|
|
8159
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${npmCmd}'`,
|
|
8160
|
+
onLog,
|
|
8161
|
+
{ timeout: 6e4, cwd: "/" }
|
|
8162
|
+
);
|
|
8163
|
+
installed.push(pkg);
|
|
8164
|
+
break;
|
|
8165
|
+
}
|
|
8166
|
+
case "pip": {
|
|
8167
|
+
const pkg = step["package"];
|
|
8168
|
+
if (!pkg) {
|
|
8169
|
+
errors.push(`pip step "${stepId}" missing package`);
|
|
8170
|
+
break;
|
|
8171
|
+
}
|
|
8172
|
+
if (!SAFE_PACKAGE_RE.test(pkg)) {
|
|
8173
|
+
errors.push(`Unsafe pip package name: ${pkg}`);
|
|
8174
|
+
break;
|
|
8175
|
+
}
|
|
8176
|
+
onLog(`Installing pip package: ${pkg}`);
|
|
8177
|
+
const pipCmd = [
|
|
8178
|
+
`export HOME="${agentHome}"`,
|
|
8179
|
+
`export PATH="${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8180
|
+
`pip install ${pkg}`
|
|
8181
|
+
].join(" && ");
|
|
8182
|
+
await execWithProgress3(
|
|
8183
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${pipCmd}'`,
|
|
8184
|
+
onLog,
|
|
8185
|
+
{ timeout: 6e4, cwd: "/" }
|
|
8186
|
+
);
|
|
8187
|
+
installed.push(pkg);
|
|
8188
|
+
break;
|
|
8189
|
+
}
|
|
8190
|
+
}
|
|
8191
|
+
} catch (err) {
|
|
8192
|
+
const msg = `Failed to install ${kind} dep (step: ${stepId}): ${err.message}`;
|
|
8193
|
+
onLog(msg);
|
|
8194
|
+
errors.push(msg);
|
|
8195
|
+
}
|
|
8196
|
+
}
|
|
8197
|
+
const requiredBins = info.bins;
|
|
8198
|
+
if (requiredBins.length > 0) {
|
|
8199
|
+
onLog(`Verifying required binaries: ${requiredBins.join(", ")}`);
|
|
8200
|
+
for (const bin of requiredBins) {
|
|
8201
|
+
try {
|
|
8202
|
+
const checkCmd = [
|
|
8203
|
+
`export HOME="${agentHome}"`,
|
|
8204
|
+
`export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:${SYSTEM_PATH}:$PATH"`,
|
|
8205
|
+
`source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
|
|
8206
|
+
`which ${bin}`
|
|
8207
|
+
].join(" && ");
|
|
8208
|
+
await execWithProgress3(
|
|
8209
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${checkCmd}'`,
|
|
8210
|
+
() => {
|
|
8211
|
+
},
|
|
8212
|
+
{ timeout: 5e3, cwd: "/" }
|
|
8213
|
+
);
|
|
8214
|
+
} catch {
|
|
8215
|
+
errors.push(`Required binary "${bin}" not found in agent PATH after install`);
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
return {
|
|
8220
|
+
success: errors.length === 0,
|
|
8221
|
+
installed,
|
|
8222
|
+
errors
|
|
8223
|
+
};
|
|
8224
|
+
}
|
|
8225
|
+
|
|
7883
8226
|
// libs/shield-daemon/src/routes/marketplace.ts
|
|
7884
8227
|
var installInProgress = /* @__PURE__ */ new Set();
|
|
7885
8228
|
function isInstallInProgress(slug) {
|
|
@@ -8134,7 +8477,7 @@ async function marketplaceRoutes(app) {
|
|
|
8134
8477
|
error: "Critical vulnerability detected",
|
|
8135
8478
|
analysis: analysisResult
|
|
8136
8479
|
});
|
|
8137
|
-
return { success: false, name: slug, analysis: analysisResult, logs };
|
|
8480
|
+
return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
|
|
8138
8481
|
}
|
|
8139
8482
|
emitSkillInstallProgress(slug, "download", "Downloading skill files");
|
|
8140
8483
|
const skill = await getMarketplaceSkill(slug);
|
|
@@ -8144,7 +8487,7 @@ async function marketplaceRoutes(app) {
|
|
|
8144
8487
|
name: slug,
|
|
8145
8488
|
error: "No files available for installation"
|
|
8146
8489
|
});
|
|
8147
|
-
return { success: false, name: slug, analysis: analysisResult, logs };
|
|
8490
|
+
return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
|
|
8148
8491
|
}
|
|
8149
8492
|
const publisher = skill.author;
|
|
8150
8493
|
logs.push("Downloaded skill files");
|
|
@@ -8153,18 +8496,27 @@ async function marketplaceRoutes(app) {
|
|
|
8153
8496
|
throw new Error("Skills directory not configured");
|
|
8154
8497
|
}
|
|
8155
8498
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8156
|
-
const
|
|
8499
|
+
const agentUsername = path16.basename(agentHome);
|
|
8500
|
+
const binDir = path16.join(agentHome, "bin");
|
|
8157
8501
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8158
|
-
skillDir =
|
|
8502
|
+
skillDir = path16.join(skillsDir2, slug);
|
|
8159
8503
|
emitSkillInstallProgress(slug, "approve", "Pre-approving skill");
|
|
8160
|
-
addToApprovedList(slug, publisher);
|
|
8504
|
+
addToApprovedList(slug, publisher, void 0, slug);
|
|
8161
8505
|
logs.push("Skill pre-approved");
|
|
8162
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
|
+
}));
|
|
8163
8515
|
const brokerAvailable = await isBrokerAvailable();
|
|
8164
8516
|
if (brokerAvailable) {
|
|
8165
8517
|
const brokerResult = await installSkillViaBroker(
|
|
8166
8518
|
slug,
|
|
8167
|
-
|
|
8519
|
+
taggedFiles.map((f) => ({ name: f.name, content: f.content })),
|
|
8168
8520
|
{ createWrapper: true, agentHome, socketGroup }
|
|
8169
8521
|
);
|
|
8170
8522
|
if (!brokerResult.installed) {
|
|
@@ -8183,18 +8535,65 @@ async function marketplaceRoutes(app) {
|
|
|
8183
8535
|
}
|
|
8184
8536
|
} else {
|
|
8185
8537
|
console.log(`[Marketplace] Broker unavailable, installing ${slug} directly`);
|
|
8186
|
-
|
|
8187
|
-
for (const file of
|
|
8188
|
-
const filePath =
|
|
8189
|
-
const fileDir =
|
|
8538
|
+
sudoMkdir(skillDir, agentUsername);
|
|
8539
|
+
for (const file of taggedFiles) {
|
|
8540
|
+
const filePath = path16.join(skillDir, file.name);
|
|
8541
|
+
const fileDir = path16.dirname(filePath);
|
|
8190
8542
|
if (fileDir !== skillDir) {
|
|
8191
|
-
|
|
8543
|
+
sudoMkdir(fileDir, agentUsername);
|
|
8192
8544
|
}
|
|
8193
|
-
|
|
8545
|
+
sudoWriteFile(filePath, file.content, agentUsername);
|
|
8194
8546
|
}
|
|
8195
8547
|
createSkillWrapper(slug, binDir);
|
|
8196
|
-
logs.push(`Files written directly: ${
|
|
8197
|
-
logs.push(`Wrapper created: ${
|
|
8548
|
+
logs.push(`Files written directly: ${taggedFiles.length} files`);
|
|
8549
|
+
logs.push(`Wrapper created: ${path16.join(binDir, slug)}`);
|
|
8550
|
+
}
|
|
8551
|
+
let depsSuccess = true;
|
|
8552
|
+
emitSkillInstallProgress(slug, "deps", "Installing skill dependencies");
|
|
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
|
+
};
|
|
8572
|
+
const depsResult = await executeSkillInstallSteps({
|
|
8573
|
+
slug,
|
|
8574
|
+
skillDir,
|
|
8575
|
+
agentHome,
|
|
8576
|
+
agentUsername,
|
|
8577
|
+
onLog: depsOnLog
|
|
8578
|
+
});
|
|
8579
|
+
if (depsLineCount > 0) {
|
|
8580
|
+
emitSkillInstallProgress(slug, "deps", `Dependency install complete (${depsLineCount} lines processed)`);
|
|
8581
|
+
}
|
|
8582
|
+
if (depsResult.installed.length > 0) {
|
|
8583
|
+
logs.push(`Dependencies installed: ${depsResult.installed.join(", ")}`);
|
|
8584
|
+
}
|
|
8585
|
+
if (depsResult.errors.length > 0) {
|
|
8586
|
+
depsSuccess = false;
|
|
8587
|
+
for (const err of depsResult.errors) {
|
|
8588
|
+
emitSkillInstallProgress(slug, "warning", `Dependency warning: ${err}`);
|
|
8589
|
+
logs.push(`Dependency warning: ${err}`);
|
|
8590
|
+
}
|
|
8591
|
+
}
|
|
8592
|
+
} catch (err) {
|
|
8593
|
+
depsSuccess = false;
|
|
8594
|
+
const msg = `Dependency installation failed: ${err.message}`;
|
|
8595
|
+
emitSkillInstallProgress(slug, "warning", msg);
|
|
8596
|
+
logs.push(msg);
|
|
8198
8597
|
}
|
|
8199
8598
|
addSkillEntry(slug);
|
|
8200
8599
|
addSkillPolicy(slug);
|
|
@@ -8205,14 +8604,19 @@ async function marketplaceRoutes(app) {
|
|
|
8205
8604
|
updateApprovedHash(slug, hash);
|
|
8206
8605
|
logs.push("Integrity hash recorded");
|
|
8207
8606
|
}
|
|
8607
|
+
try {
|
|
8608
|
+
markDownloadedAsInstalled(slug);
|
|
8609
|
+
} catch {
|
|
8610
|
+
}
|
|
8208
8611
|
installInProgress.delete(slug);
|
|
8209
|
-
|
|
8612
|
+
const depsWarnings = depsSuccess ? void 0 : logs.filter((l) => l.startsWith("Dependency"));
|
|
8613
|
+
daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult, depsWarnings });
|
|
8210
8614
|
logs.push("Installation complete");
|
|
8211
|
-
return { success: true, name: slug, analysis: analysisResult, logs };
|
|
8615
|
+
return { success: true, name: slug, analysis: analysisResult, logs, depsSuccess };
|
|
8212
8616
|
} catch (err) {
|
|
8213
8617
|
try {
|
|
8214
|
-
if (skillDir &&
|
|
8215
|
-
|
|
8618
|
+
if (skillDir && fs16.existsSync(skillDir)) {
|
|
8619
|
+
fs16.rmSync(skillDir, { recursive: true, force: true });
|
|
8216
8620
|
}
|
|
8217
8621
|
removeFromApprovedList(slug);
|
|
8218
8622
|
} catch {
|
|
@@ -8221,7 +8625,7 @@ async function marketplaceRoutes(app) {
|
|
|
8221
8625
|
installInProgress.delete(slug);
|
|
8222
8626
|
daemonEvents.broadcast("skills:install_failed", { name: slug, error: errorMsg });
|
|
8223
8627
|
console.error("[Marketplace] Install failed:", errorMsg);
|
|
8224
|
-
return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`] };
|
|
8628
|
+
return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`], depsSuccess: void 0 };
|
|
8225
8629
|
} finally {
|
|
8226
8630
|
installInProgress.delete(slug);
|
|
8227
8631
|
}
|
|
@@ -8245,17 +8649,17 @@ async function marketplaceRoutes(app) {
|
|
|
8245
8649
|
}
|
|
8246
8650
|
|
|
8247
8651
|
// libs/shield-daemon/src/routes/skills.ts
|
|
8248
|
-
function
|
|
8652
|
+
function findSkillMdRecursive2(dir, depth = 0) {
|
|
8249
8653
|
if (depth > 3) return null;
|
|
8250
8654
|
try {
|
|
8251
8655
|
for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
|
|
8252
|
-
const candidate =
|
|
8253
|
-
if (
|
|
8656
|
+
const candidate = path17.join(dir, name);
|
|
8657
|
+
if (fs17.existsSync(candidate)) return candidate;
|
|
8254
8658
|
}
|
|
8255
|
-
const entries =
|
|
8659
|
+
const entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
8256
8660
|
for (const entry of entries) {
|
|
8257
8661
|
if (!entry.isDirectory()) continue;
|
|
8258
|
-
const found =
|
|
8662
|
+
const found = findSkillMdRecursive2(path17.join(dir, entry.name), depth + 1);
|
|
8259
8663
|
if (found) return found;
|
|
8260
8664
|
}
|
|
8261
8665
|
} catch {
|
|
@@ -8264,10 +8668,10 @@ function findSkillMdRecursive(dir, depth = 0) {
|
|
|
8264
8668
|
}
|
|
8265
8669
|
function readSkillMetadata(skillDir) {
|
|
8266
8670
|
try {
|
|
8267
|
-
const mdPath =
|
|
8671
|
+
const mdPath = findSkillMdRecursive2(skillDir);
|
|
8268
8672
|
if (!mdPath) return {};
|
|
8269
|
-
const content =
|
|
8270
|
-
const parsed =
|
|
8673
|
+
const content = fs17.readFileSync(mdPath, "utf-8");
|
|
8674
|
+
const parsed = parseSkillMd3(content);
|
|
8271
8675
|
const meta = parsed?.metadata;
|
|
8272
8676
|
return {
|
|
8273
8677
|
description: meta?.description,
|
|
@@ -8325,7 +8729,7 @@ async function skillsRoutes(app) {
|
|
|
8325
8729
|
let onDiskNames = [];
|
|
8326
8730
|
if (skillsDir2) {
|
|
8327
8731
|
try {
|
|
8328
|
-
onDiskNames =
|
|
8732
|
+
onDiskNames = fs17.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
8329
8733
|
} catch {
|
|
8330
8734
|
}
|
|
8331
8735
|
}
|
|
@@ -8335,14 +8739,14 @@ async function skillsRoutes(app) {
|
|
|
8335
8739
|
const data = [
|
|
8336
8740
|
// Approved → active (with metadata from SKILL.md + cached analysis)
|
|
8337
8741
|
...approved.map((a) => {
|
|
8338
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8742
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, a.name)) : {};
|
|
8339
8743
|
const cached = getCachedAnalysis2(a.name);
|
|
8340
8744
|
const dlMeta = getDownloadedSkillMeta(a.name);
|
|
8341
8745
|
return {
|
|
8342
8746
|
name: a.name,
|
|
8343
8747
|
source: "user",
|
|
8344
8748
|
status: "active",
|
|
8345
|
-
path:
|
|
8749
|
+
path: path17.join(skillsDir2 ?? "", a.name),
|
|
8346
8750
|
publisher: a.publisher,
|
|
8347
8751
|
description: meta.description,
|
|
8348
8752
|
version: meta.version,
|
|
@@ -8369,19 +8773,42 @@ async function skillsRoutes(app) {
|
|
|
8369
8773
|
}),
|
|
8370
8774
|
// Workspace: on disk but not approved or untrusted
|
|
8371
8775
|
...workspaceNames.map((name) => {
|
|
8372
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8776
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
|
|
8373
8777
|
return {
|
|
8374
8778
|
name,
|
|
8375
8779
|
source: "workspace",
|
|
8376
8780
|
status: "workspace",
|
|
8377
|
-
path:
|
|
8781
|
+
path: path17.join(skillsDir2 ?? "", name),
|
|
8378
8782
|
description: meta.description,
|
|
8379
8783
|
version: meta.version,
|
|
8380
8784
|
author: meta.author,
|
|
8381
8785
|
tags: meta.tags,
|
|
8382
8786
|
analysis: buildAnalysisSummary(name, getCachedAnalysis2(name))
|
|
8383
8787
|
};
|
|
8384
|
-
})
|
|
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
|
+
})()
|
|
8385
8812
|
];
|
|
8386
8813
|
return reply.send({ data });
|
|
8387
8814
|
});
|
|
@@ -8405,7 +8832,7 @@ async function skillsRoutes(app) {
|
|
|
8405
8832
|
let isWorkspace = false;
|
|
8406
8833
|
if (!entry && !uEntry && skillsDir2) {
|
|
8407
8834
|
try {
|
|
8408
|
-
isWorkspace =
|
|
8835
|
+
isWorkspace = fs17.existsSync(path17.join(skillsDir2, name));
|
|
8409
8836
|
} catch {
|
|
8410
8837
|
}
|
|
8411
8838
|
}
|
|
@@ -8423,13 +8850,13 @@ async function skillsRoutes(app) {
|
|
|
8423
8850
|
analysis: buildFullAnalysis(name, dlMeta?.analysis || getCachedAnalysis2(name))
|
|
8424
8851
|
};
|
|
8425
8852
|
} else if (entry) {
|
|
8426
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8853
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
|
|
8427
8854
|
const cached = getCachedAnalysis2(name);
|
|
8428
8855
|
summary = {
|
|
8429
8856
|
name,
|
|
8430
8857
|
source: "user",
|
|
8431
8858
|
status: "active",
|
|
8432
|
-
path: skillsDir2 ?
|
|
8859
|
+
path: skillsDir2 ? path17.join(skillsDir2, name) : "",
|
|
8433
8860
|
publisher: entry.publisher,
|
|
8434
8861
|
description: meta.description,
|
|
8435
8862
|
version: meta.version,
|
|
@@ -8438,12 +8865,12 @@ async function skillsRoutes(app) {
|
|
|
8438
8865
|
analysis: buildFullAnalysis(name, dlMeta?.analysis || cached)
|
|
8439
8866
|
};
|
|
8440
8867
|
} else if (isWorkspace) {
|
|
8441
|
-
const meta = skillsDir2 ? readSkillMetadata(
|
|
8868
|
+
const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
|
|
8442
8869
|
summary = {
|
|
8443
8870
|
name,
|
|
8444
8871
|
source: "workspace",
|
|
8445
8872
|
status: "workspace",
|
|
8446
|
-
path: skillsDir2 ?
|
|
8873
|
+
path: skillsDir2 ? path17.join(skillsDir2, name) : "",
|
|
8447
8874
|
description: meta.description,
|
|
8448
8875
|
version: meta.version,
|
|
8449
8876
|
author: meta.author,
|
|
@@ -8455,7 +8882,7 @@ async function skillsRoutes(app) {
|
|
|
8455
8882
|
summary = {
|
|
8456
8883
|
name: dlMeta.slug ?? name,
|
|
8457
8884
|
source: "marketplace",
|
|
8458
|
-
status: "downloaded",
|
|
8885
|
+
status: dlMeta.wasInstalled ? "disabled" : "downloaded",
|
|
8459
8886
|
description: dlMeta.description,
|
|
8460
8887
|
path: "",
|
|
8461
8888
|
publisher: dlMeta.author,
|
|
@@ -8468,11 +8895,11 @@ async function skillsRoutes(app) {
|
|
|
8468
8895
|
return reply.code(404).send({ error: `Skill "${name}" not found` });
|
|
8469
8896
|
}
|
|
8470
8897
|
let content = "";
|
|
8471
|
-
const dirToRead = summary.path || (skillsDir2 ?
|
|
8898
|
+
const dirToRead = summary.path || (skillsDir2 ? path17.join(skillsDir2, name) : "");
|
|
8472
8899
|
if (dirToRead) {
|
|
8473
8900
|
try {
|
|
8474
|
-
const mdPath =
|
|
8475
|
-
if (mdPath) content =
|
|
8901
|
+
const mdPath = findSkillMdRecursive2(dirToRead);
|
|
8902
|
+
if (mdPath) content = fs17.readFileSync(mdPath, "utf-8");
|
|
8476
8903
|
} catch {
|
|
8477
8904
|
}
|
|
8478
8905
|
}
|
|
@@ -8510,14 +8937,14 @@ async function skillsRoutes(app) {
|
|
|
8510
8937
|
if (!content) {
|
|
8511
8938
|
const skillsDir2 = getSkillsDir();
|
|
8512
8939
|
const possibleDirs = [
|
|
8513
|
-
skillsDir2 ?
|
|
8940
|
+
skillsDir2 ? path17.join(skillsDir2, name) : null
|
|
8514
8941
|
].filter(Boolean);
|
|
8515
8942
|
for (const dir of possibleDirs) {
|
|
8516
8943
|
try {
|
|
8517
|
-
const mdPath =
|
|
8944
|
+
const mdPath = findSkillMdRecursive2(dir);
|
|
8518
8945
|
if (mdPath) {
|
|
8519
|
-
content =
|
|
8520
|
-
const parsed =
|
|
8946
|
+
content = fs17.readFileSync(mdPath, "utf-8");
|
|
8947
|
+
const parsed = parseSkillMd3(content);
|
|
8521
8948
|
if (parsed?.metadata && !metadata) {
|
|
8522
8949
|
metadata = parsed.metadata;
|
|
8523
8950
|
}
|
|
@@ -8532,7 +8959,7 @@ async function skillsRoutes(app) {
|
|
|
8532
8959
|
const skillFile = localFiles.find((f) => /skill\.md/i.test(f.name));
|
|
8533
8960
|
if (skillFile?.content) {
|
|
8534
8961
|
content = skillFile.content;
|
|
8535
|
-
const parsed =
|
|
8962
|
+
const parsed = parseSkillMd3(content);
|
|
8536
8963
|
if (parsed?.metadata && !metadata) {
|
|
8537
8964
|
metadata = parsed.metadata;
|
|
8538
8965
|
}
|
|
@@ -8547,7 +8974,7 @@ async function skillsRoutes(app) {
|
|
|
8547
8974
|
const skillFile = freshFiles.find((f) => /skill\.md/i.test(f.name));
|
|
8548
8975
|
if (skillFile?.content) {
|
|
8549
8976
|
content = skillFile.content;
|
|
8550
|
-
const parsed =
|
|
8977
|
+
const parsed = parseSkillMd3(content);
|
|
8551
8978
|
if (parsed?.metadata && !metadata) {
|
|
8552
8979
|
metadata = parsed.metadata;
|
|
8553
8980
|
}
|
|
@@ -8701,10 +9128,10 @@ async function skillsRoutes(app) {
|
|
|
8701
9128
|
return reply.code(500).send({ error: "Skills directory not configured" });
|
|
8702
9129
|
}
|
|
8703
9130
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8704
|
-
const binDir =
|
|
9131
|
+
const binDir = path17.join(agentHome, "bin");
|
|
8705
9132
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8706
|
-
const skillDir =
|
|
8707
|
-
const isInstalled =
|
|
9133
|
+
const skillDir = path17.join(skillsDir2, name);
|
|
9134
|
+
const isInstalled = fs17.existsSync(skillDir);
|
|
8708
9135
|
if (isInstalled) {
|
|
8709
9136
|
try {
|
|
8710
9137
|
const brokerAvailable = await isBrokerAvailable();
|
|
@@ -8714,7 +9141,7 @@ async function skillsRoutes(app) {
|
|
|
8714
9141
|
agentHome
|
|
8715
9142
|
});
|
|
8716
9143
|
} else {
|
|
8717
|
-
|
|
9144
|
+
fs17.rmSync(skillDir, { recursive: true, force: true });
|
|
8718
9145
|
removeSkillWrapper(name, binDir);
|
|
8719
9146
|
}
|
|
8720
9147
|
removeSkillEntry(name);
|
|
@@ -8722,7 +9149,7 @@ async function skillsRoutes(app) {
|
|
|
8722
9149
|
syncOpenClawFromPolicies(loadConfig().policies);
|
|
8723
9150
|
removeFromApprovedList(name);
|
|
8724
9151
|
try {
|
|
8725
|
-
|
|
9152
|
+
markDownloadedAsInstalled(name);
|
|
8726
9153
|
} catch {
|
|
8727
9154
|
}
|
|
8728
9155
|
console.log(`[Skills] Disabled marketplace skill: ${name}`);
|
|
@@ -8742,12 +9169,20 @@ async function skillsRoutes(app) {
|
|
|
8742
9169
|
return reply.code(404).send({ error: "No files in download cache for this skill" });
|
|
8743
9170
|
}
|
|
8744
9171
|
try {
|
|
8745
|
-
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
|
+
}));
|
|
8746
9181
|
const brokerAvailable = await isBrokerAvailable();
|
|
8747
9182
|
if (brokerAvailable) {
|
|
8748
9183
|
const brokerResult = await installSkillViaBroker(
|
|
8749
9184
|
name,
|
|
8750
|
-
|
|
9185
|
+
taggedFiles.map((f) => ({ name: f.name, content: f.content })),
|
|
8751
9186
|
{ createWrapper: true, agentHome, socketGroup }
|
|
8752
9187
|
);
|
|
8753
9188
|
if (!brokerResult.installed) {
|
|
@@ -8759,11 +9194,11 @@ async function skillsRoutes(app) {
|
|
|
8759
9194
|
}
|
|
8760
9195
|
}
|
|
8761
9196
|
} else {
|
|
8762
|
-
|
|
8763
|
-
for (const file of
|
|
8764
|
-
const filePath =
|
|
8765
|
-
|
|
8766
|
-
|
|
9197
|
+
fs17.mkdirSync(skillDir, { recursive: true });
|
|
9198
|
+
for (const file of taggedFiles) {
|
|
9199
|
+
const filePath = path17.join(skillDir, file.name);
|
|
9200
|
+
fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
9201
|
+
fs17.writeFileSync(filePath, file.content, "utf-8");
|
|
8767
9202
|
}
|
|
8768
9203
|
try {
|
|
8769
9204
|
execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
@@ -8777,12 +9212,16 @@ async function skillsRoutes(app) {
|
|
|
8777
9212
|
syncOpenClawFromPolicies(loadConfig().policies);
|
|
8778
9213
|
const hash = computeSkillHash(skillDir);
|
|
8779
9214
|
if (hash) updateApprovedHash(name, hash);
|
|
9215
|
+
try {
|
|
9216
|
+
markDownloadedAsInstalled(name);
|
|
9217
|
+
} catch {
|
|
9218
|
+
}
|
|
8780
9219
|
console.log(`[Skills] Enabled marketplace skill: ${name}`);
|
|
8781
9220
|
return reply.send({ success: true, action: "enabled", name });
|
|
8782
9221
|
} catch (err) {
|
|
8783
9222
|
try {
|
|
8784
|
-
if (
|
|
8785
|
-
|
|
9223
|
+
if (fs17.existsSync(skillDir)) {
|
|
9224
|
+
fs17.rmSync(skillDir, { recursive: true, force: true });
|
|
8786
9225
|
}
|
|
8787
9226
|
removeFromApprovedList(name);
|
|
8788
9227
|
} catch {
|
|
@@ -8808,7 +9247,7 @@ async function skillsRoutes(app) {
|
|
|
8808
9247
|
const skillMdFile = files.find((f) => f.name === "SKILL.md");
|
|
8809
9248
|
let metadata;
|
|
8810
9249
|
if (skillMdFile) {
|
|
8811
|
-
const parsed =
|
|
9250
|
+
const parsed = parseSkillMd3(skillMdFile.content);
|
|
8812
9251
|
metadata = parsed?.metadata;
|
|
8813
9252
|
}
|
|
8814
9253
|
const analysis = analyzeSkill(name, combinedContent, metadata);
|
|
@@ -8820,16 +9259,25 @@ async function skillsRoutes(app) {
|
|
|
8820
9259
|
return reply.code(500).send({ error: "Skills directory not configured" });
|
|
8821
9260
|
}
|
|
8822
9261
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8823
|
-
const
|
|
9262
|
+
const agentUsername = path17.basename(agentHome);
|
|
9263
|
+
const binDir = path17.join(agentHome, "bin");
|
|
8824
9264
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8825
|
-
const skillDir =
|
|
9265
|
+
const skillDir = path17.join(skillsDir2, name);
|
|
8826
9266
|
try {
|
|
8827
9267
|
addToApprovedList(name, publisher);
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
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
|
+
}));
|
|
9276
|
+
sudoMkdir(skillDir, agentUsername);
|
|
9277
|
+
for (const file of taggedFiles) {
|
|
9278
|
+
const filePath = path17.join(skillDir, file.name);
|
|
9279
|
+
sudoMkdir(path17.dirname(filePath), agentUsername);
|
|
9280
|
+
sudoWriteFile(filePath, file.content, agentUsername);
|
|
8833
9281
|
}
|
|
8834
9282
|
try {
|
|
8835
9283
|
execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
@@ -8843,8 +9291,8 @@ async function skillsRoutes(app) {
|
|
|
8843
9291
|
return reply.send({ success: true, name, analysis });
|
|
8844
9292
|
} catch (err) {
|
|
8845
9293
|
try {
|
|
8846
|
-
if (
|
|
8847
|
-
|
|
9294
|
+
if (fs17.existsSync(skillDir)) {
|
|
9295
|
+
fs17.rmSync(skillDir, { recursive: true, force: true });
|
|
8848
9296
|
}
|
|
8849
9297
|
removeFromApprovedList(name);
|
|
8850
9298
|
} catch {
|
|
@@ -8878,15 +9326,23 @@ async function skillsRoutes(app) {
|
|
|
8878
9326
|
}
|
|
8879
9327
|
try {
|
|
8880
9328
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
8881
|
-
const binDir =
|
|
9329
|
+
const binDir = path17.join(agentHome, "bin");
|
|
8882
9330
|
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
|
|
8883
|
-
const skillDir =
|
|
8884
|
-
addToApprovedList(name, meta.author);
|
|
9331
|
+
const skillDir = path17.join(getSkillsDir(), name);
|
|
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
|
+
}));
|
|
8885
9341
|
const brokerAvailable = await isBrokerAvailable();
|
|
8886
9342
|
if (brokerAvailable) {
|
|
8887
9343
|
const brokerResult = await installSkillViaBroker(
|
|
8888
9344
|
name,
|
|
8889
|
-
|
|
9345
|
+
taggedFiles.map((f) => ({ name: f.name, content: f.content })),
|
|
8890
9346
|
{ createWrapper: true, agentHome, socketGroup }
|
|
8891
9347
|
);
|
|
8892
9348
|
if (!brokerResult.installed) {
|
|
@@ -8898,11 +9354,11 @@ async function skillsRoutes(app) {
|
|
|
8898
9354
|
}
|
|
8899
9355
|
}
|
|
8900
9356
|
} else {
|
|
8901
|
-
|
|
8902
|
-
for (const file of
|
|
8903
|
-
const filePath =
|
|
8904
|
-
|
|
8905
|
-
|
|
9357
|
+
fs17.mkdirSync(skillDir, { recursive: true });
|
|
9358
|
+
for (const file of taggedFiles) {
|
|
9359
|
+
const filePath = path17.join(skillDir, file.name);
|
|
9360
|
+
fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
9361
|
+
fs17.writeFileSync(filePath, file.content, "utf-8");
|
|
8906
9362
|
}
|
|
8907
9363
|
try {
|
|
8908
9364
|
execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
@@ -8914,8 +9370,12 @@ async function skillsRoutes(app) {
|
|
|
8914
9370
|
addSkillEntry(name);
|
|
8915
9371
|
addSkillPolicy(name);
|
|
8916
9372
|
syncOpenClawFromPolicies(loadConfig().policies);
|
|
8917
|
-
const unblockHash = computeSkillHash(
|
|
9373
|
+
const unblockHash = computeSkillHash(path17.join(getSkillsDir(), name));
|
|
8918
9374
|
if (unblockHash) updateApprovedHash(name, unblockHash);
|
|
9375
|
+
try {
|
|
9376
|
+
markDownloadedAsInstalled(name);
|
|
9377
|
+
} catch {
|
|
9378
|
+
}
|
|
8919
9379
|
console.log(`[Skills] Unblocked and installed skill: ${name}`);
|
|
8920
9380
|
return reply.send({ success: true, message: `Skill "${name}" approved and installed` });
|
|
8921
9381
|
} catch (err) {
|
|
@@ -8945,7 +9405,7 @@ async function skillsRoutes(app) {
|
|
|
8945
9405
|
const skillMdFile = files.find((f) => /skill\.md/i.test(f.name));
|
|
8946
9406
|
if (skillMdFile) {
|
|
8947
9407
|
try {
|
|
8948
|
-
const parsed =
|
|
9408
|
+
const parsed = parseSkillMd3(skillMdFile.content);
|
|
8949
9409
|
if (parsed?.metadata) {
|
|
8950
9410
|
parsedDescription = parsedDescription || parsed.metadata.description;
|
|
8951
9411
|
parsedVersion = parsedVersion || parsed.metadata.version;
|
|
@@ -8974,10 +9434,10 @@ async function skillsRoutes(app) {
|
|
|
8974
9434
|
|
|
8975
9435
|
// libs/shield-daemon/src/routes/exec.ts
|
|
8976
9436
|
init_paths();
|
|
8977
|
-
import * as
|
|
8978
|
-
import * as
|
|
9437
|
+
import * as fs18 from "node:fs";
|
|
9438
|
+
import * as path18 from "node:path";
|
|
8979
9439
|
function getAllowedCommandsPath2() {
|
|
8980
|
-
return
|
|
9440
|
+
return path18.join(getSystemConfigDir(), "allowed-commands.json");
|
|
8981
9441
|
}
|
|
8982
9442
|
var BIN_DIRS = [
|
|
8983
9443
|
"/usr/bin",
|
|
@@ -8990,22 +9450,22 @@ var binCache = null;
|
|
|
8990
9450
|
var BIN_CACHE_TTL = 6e4;
|
|
8991
9451
|
var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
8992
9452
|
function loadConfig2() {
|
|
8993
|
-
if (!
|
|
9453
|
+
if (!fs18.existsSync(getAllowedCommandsPath2())) {
|
|
8994
9454
|
return { version: "1.0.0", commands: [] };
|
|
8995
9455
|
}
|
|
8996
9456
|
try {
|
|
8997
|
-
const content =
|
|
9457
|
+
const content = fs18.readFileSync(getAllowedCommandsPath2(), "utf-8");
|
|
8998
9458
|
return JSON.parse(content);
|
|
8999
9459
|
} catch {
|
|
9000
9460
|
return { version: "1.0.0", commands: [] };
|
|
9001
9461
|
}
|
|
9002
9462
|
}
|
|
9003
9463
|
function saveConfig2(config) {
|
|
9004
|
-
const dir =
|
|
9005
|
-
if (!
|
|
9006
|
-
|
|
9464
|
+
const dir = path18.dirname(getAllowedCommandsPath2());
|
|
9465
|
+
if (!fs18.existsSync(dir)) {
|
|
9466
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
9007
9467
|
}
|
|
9008
|
-
|
|
9468
|
+
fs18.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
9009
9469
|
}
|
|
9010
9470
|
function scanSystemBins() {
|
|
9011
9471
|
const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
@@ -9014,13 +9474,13 @@ function scanSystemBins() {
|
|
|
9014
9474
|
const results = [];
|
|
9015
9475
|
for (const dir of allDirs) {
|
|
9016
9476
|
try {
|
|
9017
|
-
if (!
|
|
9018
|
-
const entries =
|
|
9477
|
+
if (!fs18.existsSync(dir)) continue;
|
|
9478
|
+
const entries = fs18.readdirSync(dir);
|
|
9019
9479
|
for (const entry of entries) {
|
|
9020
9480
|
if (seen.has(entry)) continue;
|
|
9021
|
-
const fullPath =
|
|
9481
|
+
const fullPath = path18.join(dir, entry);
|
|
9022
9482
|
try {
|
|
9023
|
-
const stat =
|
|
9483
|
+
const stat = fs18.statSync(fullPath);
|
|
9024
9484
|
if (stat.isFile() && (stat.mode & 73) !== 0) {
|
|
9025
9485
|
seen.add(entry);
|
|
9026
9486
|
results.push({ name: entry, path: fullPath });
|
|
@@ -9073,7 +9533,7 @@ async function execRoutes(app) {
|
|
|
9073
9533
|
};
|
|
9074
9534
|
}
|
|
9075
9535
|
for (const p of paths) {
|
|
9076
|
-
if (!
|
|
9536
|
+
if (!path18.isAbsolute(p)) {
|
|
9077
9537
|
return {
|
|
9078
9538
|
success: false,
|
|
9079
9539
|
error: {
|
|
@@ -9415,7 +9875,7 @@ async function authRoutes(app) {
|
|
|
9415
9875
|
init_vault();
|
|
9416
9876
|
import { isSecretEnvVar } from "@agenshield/sandbox";
|
|
9417
9877
|
init_secret_sync();
|
|
9418
|
-
import
|
|
9878
|
+
import crypto6 from "node:crypto";
|
|
9419
9879
|
function maskValue(value) {
|
|
9420
9880
|
if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
9421
9881
|
return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
|
|
@@ -9510,7 +9970,7 @@ async function secretsRoutes(app) {
|
|
|
9510
9970
|
const secrets = await vault.get("secrets") ?? [];
|
|
9511
9971
|
const resolvedScope = scope ?? (policyIds?.length > 0 ? "policed" : "global");
|
|
9512
9972
|
const newSecret = {
|
|
9513
|
-
id:
|
|
9973
|
+
id: crypto6.randomUUID(),
|
|
9514
9974
|
name: name.trim(),
|
|
9515
9975
|
value,
|
|
9516
9976
|
policyIds: resolvedScope === "standalone" ? [] : policyIds ?? [],
|
|
@@ -9569,18 +10029,18 @@ async function secretsRoutes(app) {
|
|
|
9569
10029
|
}
|
|
9570
10030
|
|
|
9571
10031
|
// libs/shield-daemon/src/routes/fs.ts
|
|
9572
|
-
import * as
|
|
9573
|
-
import * as
|
|
10032
|
+
import * as fs19 from "node:fs";
|
|
10033
|
+
import * as path19 from "node:path";
|
|
9574
10034
|
import * as os6 from "node:os";
|
|
9575
10035
|
var MAX_ENTRIES = 200;
|
|
9576
10036
|
async function fsRoutes(app) {
|
|
9577
10037
|
app.get("/fs/browse", async (request2) => {
|
|
9578
10038
|
const dirPath = request2.query.path || os6.homedir();
|
|
9579
10039
|
const showHidden = request2.query.showHidden === "true";
|
|
9580
|
-
const resolvedPath =
|
|
10040
|
+
const resolvedPath = path19.resolve(dirPath);
|
|
9581
10041
|
let dirents;
|
|
9582
10042
|
try {
|
|
9583
|
-
dirents =
|
|
10043
|
+
dirents = fs19.readdirSync(resolvedPath, { withFileTypes: true });
|
|
9584
10044
|
} catch {
|
|
9585
10045
|
return { success: true, data: { entries: [] } };
|
|
9586
10046
|
}
|
|
@@ -9589,7 +10049,7 @@ async function fsRoutes(app) {
|
|
|
9589
10049
|
if (!showHidden && dirent.name.startsWith(".")) continue;
|
|
9590
10050
|
entries.push({
|
|
9591
10051
|
name: dirent.name,
|
|
9592
|
-
path:
|
|
10052
|
+
path: path19.join(resolvedPath, dirent.name),
|
|
9593
10053
|
type: dirent.isDirectory() ? "directory" : "file"
|
|
9594
10054
|
});
|
|
9595
10055
|
if (entries.length >= MAX_ENTRIES) break;
|
|
@@ -9603,8 +10063,8 @@ async function fsRoutes(app) {
|
|
|
9603
10063
|
}
|
|
9604
10064
|
|
|
9605
10065
|
// libs/shield-daemon/src/services/activity-log.ts
|
|
9606
|
-
import * as
|
|
9607
|
-
import * as
|
|
10066
|
+
import * as fs20 from "node:fs";
|
|
10067
|
+
import * as path20 from "node:path";
|
|
9608
10068
|
init_paths();
|
|
9609
10069
|
var ACTIVITY_FILE = "activity.jsonl";
|
|
9610
10070
|
var MAX_SIZE_BYTES = 100 * 1024 * 1024;
|
|
@@ -9622,12 +10082,12 @@ var ActivityLog = class {
|
|
|
9622
10082
|
writeCount = 0;
|
|
9623
10083
|
unsubscribe;
|
|
9624
10084
|
constructor() {
|
|
9625
|
-
this.filePath =
|
|
10085
|
+
this.filePath = path20.join(getConfigDir(), ACTIVITY_FILE);
|
|
9626
10086
|
}
|
|
9627
10087
|
/** Read historical events from the JSONL file, newest first */
|
|
9628
10088
|
getHistory(limit = 500) {
|
|
9629
|
-
if (!
|
|
9630
|
-
const content =
|
|
10089
|
+
if (!fs20.existsSync(this.filePath)) return [];
|
|
10090
|
+
const content = fs20.readFileSync(this.filePath, "utf-8");
|
|
9631
10091
|
const lines = content.split("\n").filter(Boolean);
|
|
9632
10092
|
const events = [];
|
|
9633
10093
|
for (const line of lines) {
|
|
@@ -9652,7 +10112,7 @@ var ActivityLog = class {
|
|
|
9652
10112
|
}
|
|
9653
10113
|
append(event) {
|
|
9654
10114
|
const line = JSON.stringify(event) + "\n";
|
|
9655
|
-
|
|
10115
|
+
fs20.appendFileSync(this.filePath, line, "utf-8");
|
|
9656
10116
|
this.writeCount++;
|
|
9657
10117
|
if (this.writeCount % PRUNE_INTERVAL === 0) {
|
|
9658
10118
|
this.rotate();
|
|
@@ -9660,7 +10120,7 @@ var ActivityLog = class {
|
|
|
9660
10120
|
}
|
|
9661
10121
|
rotate() {
|
|
9662
10122
|
try {
|
|
9663
|
-
const stat =
|
|
10123
|
+
const stat = fs20.statSync(this.filePath);
|
|
9664
10124
|
if (stat.size > MAX_SIZE_BYTES) {
|
|
9665
10125
|
this.truncateBySize();
|
|
9666
10126
|
}
|
|
@@ -9670,15 +10130,15 @@ var ActivityLog = class {
|
|
|
9670
10130
|
}
|
|
9671
10131
|
/** Keep newest half of lines when file exceeds size limit */
|
|
9672
10132
|
truncateBySize() {
|
|
9673
|
-
const content =
|
|
10133
|
+
const content = fs20.readFileSync(this.filePath, "utf-8");
|
|
9674
10134
|
const lines = content.split("\n").filter(Boolean);
|
|
9675
10135
|
const keep = lines.slice(Math.floor(lines.length / 2));
|
|
9676
|
-
|
|
10136
|
+
fs20.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
|
|
9677
10137
|
}
|
|
9678
10138
|
/** Remove entries older than 24 hours */
|
|
9679
10139
|
pruneOldEntries() {
|
|
9680
|
-
if (!
|
|
9681
|
-
const content =
|
|
10140
|
+
if (!fs20.existsSync(this.filePath)) return;
|
|
10141
|
+
const content = fs20.readFileSync(this.filePath, "utf-8");
|
|
9682
10142
|
const lines = content.split("\n").filter(Boolean);
|
|
9683
10143
|
const cutoff = Date.now() - MAX_AGE_MS;
|
|
9684
10144
|
const kept = lines.filter((line) => {
|
|
@@ -9690,7 +10150,7 @@ var ActivityLog = class {
|
|
|
9690
10150
|
}
|
|
9691
10151
|
});
|
|
9692
10152
|
if (kept.length < lines.length) {
|
|
9693
|
-
|
|
10153
|
+
fs20.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
|
|
9694
10154
|
}
|
|
9695
10155
|
}
|
|
9696
10156
|
};
|
|
@@ -9811,7 +10271,7 @@ async function openclawRoutes(app) {
|
|
|
9811
10271
|
}
|
|
9812
10272
|
|
|
9813
10273
|
// libs/shield-daemon/src/routes/rpc.ts
|
|
9814
|
-
import * as
|
|
10274
|
+
import * as crypto7 from "node:crypto";
|
|
9815
10275
|
import * as nodefs from "node:fs";
|
|
9816
10276
|
|
|
9817
10277
|
// libs/shield-daemon/src/policy/url-matcher.ts
|
|
@@ -9831,11 +10291,11 @@ function normalizeUrlTarget(url) {
|
|
|
9831
10291
|
const trimmed = url.trim();
|
|
9832
10292
|
try {
|
|
9833
10293
|
const parsed = new URL(trimmed);
|
|
9834
|
-
let
|
|
9835
|
-
if (
|
|
9836
|
-
|
|
10294
|
+
let path24 = parsed.pathname;
|
|
10295
|
+
if (path24.length > 1) {
|
|
10296
|
+
path24 = path24.replace(/\/+$/, "");
|
|
9837
10297
|
}
|
|
9838
|
-
return `${parsed.protocol}//${parsed.host}${
|
|
10298
|
+
return `${parsed.protocol}//${parsed.host}${path24}${parsed.search}`;
|
|
9839
10299
|
} catch {
|
|
9840
10300
|
return trimmed.replace(/\/+$/, "");
|
|
9841
10301
|
}
|
|
@@ -9925,7 +10385,7 @@ function checkUrlPolicy(policies, url) {
|
|
|
9925
10385
|
// libs/shield-daemon/src/proxy/server.ts
|
|
9926
10386
|
import * as http from "node:http";
|
|
9927
10387
|
import * as net from "node:net";
|
|
9928
|
-
function createPerRunProxy(urlPolicies, onActivity, logger) {
|
|
10388
|
+
function createPerRunProxy(urlPolicies, onActivity, logger, onBlock) {
|
|
9929
10389
|
const server = http.createServer((req, res) => {
|
|
9930
10390
|
onActivity();
|
|
9931
10391
|
const url = req.url;
|
|
@@ -9937,6 +10397,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
|
|
|
9937
10397
|
const allowed = checkUrlPolicy(urlPolicies, url);
|
|
9938
10398
|
if (!allowed) {
|
|
9939
10399
|
logger(`BLOCKED HTTP ${req.method} ${url}`);
|
|
10400
|
+
onBlock?.(req.method || "GET", url, "http");
|
|
9940
10401
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
9941
10402
|
res.end("Blocked by AgenShield URL policy");
|
|
9942
10403
|
return;
|
|
@@ -9980,6 +10441,7 @@ function createPerRunProxy(urlPolicies, onActivity, logger) {
|
|
|
9980
10441
|
const allowed = checkUrlPolicy(urlPolicies, `https://${hostname2}`);
|
|
9981
10442
|
if (!allowed) {
|
|
9982
10443
|
logger(`BLOCKED CONNECT ${hostname2}:${port}`);
|
|
10444
|
+
onBlock?.("CONNECT", `${hostname2}:${port}`, "https");
|
|
9983
10445
|
clientSocket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
9984
10446
|
clientSocket.destroy();
|
|
9985
10447
|
return;
|
|
@@ -10042,7 +10504,16 @@ var ProxyPool = class {
|
|
|
10042
10504
|
const logger = (msg) => {
|
|
10043
10505
|
console.log(`[proxy:${execId.slice(0, 8)}] ${msg}`);
|
|
10044
10506
|
};
|
|
10045
|
-
const
|
|
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);
|
|
10046
10517
|
const port = await new Promise((resolve3, reject) => {
|
|
10047
10518
|
server.listen(0, "127.0.0.1", () => {
|
|
10048
10519
|
const addr = server.address();
|
|
@@ -10121,16 +10592,23 @@ function matchCommandPattern(pattern, target) {
|
|
|
10121
10592
|
const firstSpace = target.indexOf(" ");
|
|
10122
10593
|
const cmd = firstSpace >= 0 ? target.slice(0, firstSpace) : target;
|
|
10123
10594
|
if (cmd.startsWith("/")) {
|
|
10124
|
-
const
|
|
10125
|
-
normalizedTarget = firstSpace >= 0 ?
|
|
10595
|
+
const basename6 = cmd.split("/").pop() || cmd;
|
|
10596
|
+
normalizedTarget = firstSpace >= 0 ? basename6 + target.slice(firstSpace) : basename6;
|
|
10126
10597
|
}
|
|
10127
10598
|
if (trimmed.endsWith(":*")) {
|
|
10128
|
-
|
|
10599
|
+
let prefix = trimmed.slice(0, -2);
|
|
10600
|
+
if (prefix.includes("/")) {
|
|
10601
|
+
prefix = prefix.split("/").pop() || prefix;
|
|
10602
|
+
}
|
|
10129
10603
|
const lowerTarget = normalizedTarget.toLowerCase();
|
|
10130
10604
|
const lowerPrefix = prefix.toLowerCase();
|
|
10131
10605
|
return lowerTarget === lowerPrefix || lowerTarget.startsWith(lowerPrefix + " ");
|
|
10132
10606
|
}
|
|
10133
|
-
|
|
10607
|
+
let normalizedPattern = trimmed;
|
|
10608
|
+
if (trimmed.includes("/")) {
|
|
10609
|
+
normalizedPattern = trimmed.split("/").pop() || trimmed;
|
|
10610
|
+
}
|
|
10611
|
+
return normalizedTarget.toLowerCase() === normalizedPattern.toLowerCase();
|
|
10134
10612
|
}
|
|
10135
10613
|
function operationToTarget(operation) {
|
|
10136
10614
|
switch (operation) {
|
|
@@ -10173,8 +10651,8 @@ function determineNetworkAccess(_config, matchedPolicy, target) {
|
|
|
10173
10651
|
if (matchedPolicy?.networkAccess) return matchedPolicy.networkAccess;
|
|
10174
10652
|
const cleanTarget = target.startsWith("fork:") ? target.slice(5) : target;
|
|
10175
10653
|
const cmdPart = cleanTarget.split(" ")[0] || "";
|
|
10176
|
-
const
|
|
10177
|
-
if (!NETWORK_COMMANDS.has(
|
|
10654
|
+
const basename6 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
|
|
10655
|
+
if (!NETWORK_COMMANDS.has(basename6.toLowerCase())) return "none";
|
|
10178
10656
|
return "proxy";
|
|
10179
10657
|
}
|
|
10180
10658
|
async function buildSandboxConfig(config, matchedPolicy, _context, target) {
|
|
@@ -10223,7 +10701,7 @@ async function buildSandboxConfig(config, matchedPolicy, _context, target) {
|
|
|
10223
10701
|
console.log(`[sandbox] direct network access (no proxy)`);
|
|
10224
10702
|
sandbox.networkAllowed = true;
|
|
10225
10703
|
} else if (networkMode === "proxy") {
|
|
10226
|
-
const execId =
|
|
10704
|
+
const execId = crypto7.randomUUID();
|
|
10227
10705
|
const commandBasename = extractCommandBasename(target || "");
|
|
10228
10706
|
const urlPolicies = filterUrlPoliciesForCommand(config.policies || [], commandBasename);
|
|
10229
10707
|
const pool = getProxyPool();
|
|
@@ -10288,7 +10766,11 @@ async function evaluatePolicyCheck(operation, target, context) {
|
|
|
10288
10766
|
} else if (targetType === "command") {
|
|
10289
10767
|
matches = matchCommandPattern(pattern, effectiveTarget);
|
|
10290
10768
|
} else {
|
|
10291
|
-
|
|
10769
|
+
let fsPattern = pattern;
|
|
10770
|
+
if (targetType === "filesystem" && fsPattern.endsWith("/")) {
|
|
10771
|
+
fsPattern = fsPattern + "**";
|
|
10772
|
+
}
|
|
10773
|
+
const regex = globToRegex(fsPattern);
|
|
10292
10774
|
matches = regex.test(effectiveTarget);
|
|
10293
10775
|
}
|
|
10294
10776
|
console.log("[policy_check] pattern:", pattern, "-> base:", targetType === "url" ? normalizeUrlBase(pattern) : pattern, "| target:", effectiveTarget, "| matches:", matches);
|
|
@@ -10354,12 +10836,29 @@ async function handleHttpRequest(params) {
|
|
|
10354
10836
|
body: responseBody
|
|
10355
10837
|
};
|
|
10356
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
|
+
}
|
|
10357
10860
|
var handlers = {
|
|
10358
|
-
policy_check: (params) =>
|
|
10359
|
-
String(params["operation"] ?? ""),
|
|
10360
|
-
String(params["target"] ?? ""),
|
|
10361
|
-
params["context"]
|
|
10362
|
-
),
|
|
10861
|
+
policy_check: (params) => handlePolicyCheck(params),
|
|
10363
10862
|
events_batch: (params) => handleEventsBatch(params),
|
|
10364
10863
|
http_request: (params) => handleHttpRequest(params),
|
|
10365
10864
|
ping: () => ({ status: "ok" })
|
|
@@ -10475,22 +10974,22 @@ async function registerRoutes(app) {
|
|
|
10475
10974
|
}
|
|
10476
10975
|
|
|
10477
10976
|
// libs/shield-daemon/src/static.ts
|
|
10478
|
-
import * as
|
|
10479
|
-
import * as
|
|
10977
|
+
import * as fs21 from "node:fs";
|
|
10978
|
+
import * as path21 from "node:path";
|
|
10480
10979
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10481
10980
|
var __filename = fileURLToPath2(import.meta.url);
|
|
10482
|
-
var __dirname =
|
|
10981
|
+
var __dirname = path21.dirname(__filename);
|
|
10483
10982
|
function getUiAssetsPath() {
|
|
10484
|
-
const pkgRootPath =
|
|
10485
|
-
if (
|
|
10983
|
+
const pkgRootPath = path21.join(__dirname, "..", "ui-assets");
|
|
10984
|
+
if (fs21.existsSync(pkgRootPath)) {
|
|
10486
10985
|
return pkgRootPath;
|
|
10487
10986
|
}
|
|
10488
|
-
const bundledPath =
|
|
10489
|
-
if (
|
|
10987
|
+
const bundledPath = path21.join(__dirname, "ui-assets");
|
|
10988
|
+
if (fs21.existsSync(bundledPath)) {
|
|
10490
10989
|
return bundledPath;
|
|
10491
10990
|
}
|
|
10492
|
-
const devPath =
|
|
10493
|
-
if (
|
|
10991
|
+
const devPath = path21.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
|
|
10992
|
+
if (fs21.existsSync(devPath)) {
|
|
10494
10993
|
return devPath;
|
|
10495
10994
|
}
|
|
10496
10995
|
return null;
|
|
@@ -10580,10 +11079,16 @@ async function startServer(config) {
|
|
|
10580
11079
|
startSecurityWatcher(1e4);
|
|
10581
11080
|
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
10582
11081
|
const skillsDir2 = `${agentHome}/.openclaw/skills`;
|
|
10583
|
-
if (!
|
|
10584
|
-
|
|
11082
|
+
if (!fs23.existsSync(skillsDir2)) {
|
|
11083
|
+
fs23.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
|
|
10585
11084
|
console.log(`[Daemon] Created skills directory: ${skillsDir2}`);
|
|
10586
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
|
+
}
|
|
10587
11092
|
startSkillsWatcher(skillsDir2, {
|
|
10588
11093
|
onUntrustedDetected: (info) => emitSkillUntrustedDetected(info.name, info.reason),
|
|
10589
11094
|
onApproved: (name) => emitSkillApproved(name)
|
|
@@ -10632,10 +11137,10 @@ async function main() {
|
|
|
10632
11137
|
ensureConfigDir();
|
|
10633
11138
|
const config = loadConfig();
|
|
10634
11139
|
const pidPath = getPidPath();
|
|
10635
|
-
|
|
11140
|
+
fs24.writeFileSync(pidPath, process.pid.toString(), "utf-8");
|
|
10636
11141
|
const cleanup = () => {
|
|
10637
|
-
if (
|
|
10638
|
-
|
|
11142
|
+
if (fs24.existsSync(pidPath)) {
|
|
11143
|
+
fs24.unlinkSync(pidPath);
|
|
10639
11144
|
}
|
|
10640
11145
|
process.exit(0);
|
|
10641
11146
|
};
|