@agenshield/daemon 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +708 -539
- package/main.js +708 -539
- package/package.json +4 -4
- package/routes/marketplace.d.ts.map +1 -1
- package/routes/skills.d.ts.map +1 -1
- package/services/marketplace.d.ts +5 -0
- package/services/marketplace.d.ts.map +1 -1
- package/ui-assets/assets/{index-gZFEJ5lI.js → index-C4yZ-JLI.js} +129 -134
- package/ui-assets/index.html +1 -1
package/index.js
CHANGED
|
@@ -5422,6 +5422,20 @@ function mapSearchResult(result) {
|
|
|
5422
5422
|
function isTextMime(mime) {
|
|
5423
5423
|
return mime.startsWith("text/") || mime === "application/json" || mime === "text/yaml" || mime === "text/toml";
|
|
5424
5424
|
}
|
|
5425
|
+
var IMAGE_EXT_MAP = {
|
|
5426
|
+
".png": "image/png",
|
|
5427
|
+
".jpg": "image/jpeg",
|
|
5428
|
+
".jpeg": "image/jpeg",
|
|
5429
|
+
".gif": "image/gif",
|
|
5430
|
+
".svg": "image/svg+xml",
|
|
5431
|
+
".webp": "image/webp",
|
|
5432
|
+
".ico": "image/x-icon"
|
|
5433
|
+
};
|
|
5434
|
+
function isImageExt(filePath) {
|
|
5435
|
+
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
5436
|
+
return IMAGE_EXT_MAP[ext] ?? null;
|
|
5437
|
+
}
|
|
5438
|
+
var MAX_IMAGE_SIZE = 5e5;
|
|
5425
5439
|
function getMarketplaceDir() {
|
|
5426
5440
|
return path11.join(os4.homedir(), CONFIG_DIR2, MARKETPLACE_DIR);
|
|
5427
5441
|
}
|
|
@@ -5444,9 +5458,19 @@ async function downloadAndExtractZip(slug) {
|
|
|
5444
5458
|
const filename = zipPath.split("/").pop() || "";
|
|
5445
5459
|
if (filename.startsWith(".")) continue;
|
|
5446
5460
|
const mimeType = guessContentType(zipPath);
|
|
5447
|
-
if (
|
|
5448
|
-
|
|
5449
|
-
|
|
5461
|
+
if (isTextMime(mimeType)) {
|
|
5462
|
+
const content = await zipEntry.async("text");
|
|
5463
|
+
files.push({ name: zipPath, type: mimeType, content });
|
|
5464
|
+
continue;
|
|
5465
|
+
}
|
|
5466
|
+
const imageMime = isImageExt(zipPath);
|
|
5467
|
+
if (imageMime) {
|
|
5468
|
+
const buf = await zipEntry.async("nodebuffer");
|
|
5469
|
+
if (buf.length <= MAX_IMAGE_SIZE) {
|
|
5470
|
+
const dataUri = `data:${imageMime};base64,${buf.toString("base64")}`;
|
|
5471
|
+
files.push({ name: zipPath, type: imageMime, content: dataUri });
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5450
5474
|
}
|
|
5451
5475
|
return files;
|
|
5452
5476
|
}
|
|
@@ -5528,6 +5552,34 @@ function getDownloadedSkillMeta(slug) {
|
|
|
5528
5552
|
}
|
|
5529
5553
|
return null;
|
|
5530
5554
|
}
|
|
5555
|
+
function inlineImagesInMarkdown(markdown, files) {
|
|
5556
|
+
const imageMap = /* @__PURE__ */ new Map();
|
|
5557
|
+
for (const file of files) {
|
|
5558
|
+
const mime = isImageExt(file.name);
|
|
5559
|
+
if (mime && file.content.startsWith("data:")) {
|
|
5560
|
+
imageMap.set(file.name, file.content);
|
|
5561
|
+
const basename2 = file.name.split("/").pop() ?? "";
|
|
5562
|
+
if (basename2 && !imageMap.has(basename2)) {
|
|
5563
|
+
imageMap.set(basename2, file.content);
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
if (imageMap.size === 0) return markdown;
|
|
5568
|
+
return markdown.replace(
|
|
5569
|
+
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
|
5570
|
+
(_match, alt, src) => {
|
|
5571
|
+
if (src.startsWith("http://") || src.startsWith("https://") || src.startsWith("data:")) {
|
|
5572
|
+
return _match;
|
|
5573
|
+
}
|
|
5574
|
+
const normalized = src.replace(/^\.\//, "");
|
|
5575
|
+
const dataUri = imageMap.get(src) ?? imageMap.get(normalized) ?? imageMap.get(normalized.split("/").pop() ?? "");
|
|
5576
|
+
if (dataUri) {
|
|
5577
|
+
return ``;
|
|
5578
|
+
}
|
|
5579
|
+
return _match;
|
|
5580
|
+
}
|
|
5581
|
+
);
|
|
5582
|
+
}
|
|
5531
5583
|
async function searchMarketplace(query) {
|
|
5532
5584
|
const cacheKey = `search:${query}`;
|
|
5533
5585
|
const cached = getCached(cacheKey);
|
|
@@ -5612,6 +5664,9 @@ async function getMarketplaceSkill(slug) {
|
|
|
5612
5664
|
}
|
|
5613
5665
|
}
|
|
5614
5666
|
const tags = tagsFromRecord(skill.tags);
|
|
5667
|
+
if (readme && files.length > 0) {
|
|
5668
|
+
readme = inlineImagesInMarkdown(readme, files);
|
|
5669
|
+
}
|
|
5615
5670
|
const mapped = {
|
|
5616
5671
|
name: skill.displayName,
|
|
5617
5672
|
slug: skill.slug,
|
|
@@ -5749,127 +5804,484 @@ async function getCachedAnalysis2(skillName, publisher) {
|
|
|
5749
5804
|
};
|
|
5750
5805
|
}
|
|
5751
5806
|
|
|
5752
|
-
// libs/shield-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5807
|
+
// libs/shield-broker/dist/index.js
|
|
5808
|
+
import { exec } from "node:child_process";
|
|
5809
|
+
import { promisify } from "node:util";
|
|
5810
|
+
import * as net2 from "node:net";
|
|
5811
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5812
|
+
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
5813
|
+
var execAsync = promisify(exec);
|
|
5814
|
+
var BrokerClient = class {
|
|
5815
|
+
socketPath;
|
|
5816
|
+
httpHost;
|
|
5817
|
+
httpPort;
|
|
5818
|
+
timeout;
|
|
5819
|
+
preferSocket;
|
|
5820
|
+
constructor(options = {}) {
|
|
5821
|
+
this.socketPath = options.socketPath || "/var/run/agenshield/agenshield.sock";
|
|
5822
|
+
this.httpHost = options.httpHost || "localhost";
|
|
5823
|
+
this.httpPort = options.httpPort || 5201;
|
|
5824
|
+
this.timeout = options.timeout || 3e4;
|
|
5825
|
+
this.preferSocket = options.preferSocket ?? true;
|
|
5762
5826
|
}
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5827
|
+
/**
|
|
5828
|
+
* Make an HTTP request through the broker
|
|
5829
|
+
*/
|
|
5830
|
+
async httpRequest(params, options) {
|
|
5831
|
+
return this.request("http_request", params, options);
|
|
5832
|
+
}
|
|
5833
|
+
/**
|
|
5834
|
+
* Read a file through the broker
|
|
5835
|
+
*/
|
|
5836
|
+
async fileRead(params, options) {
|
|
5837
|
+
return this.request("file_read", params, options);
|
|
5838
|
+
}
|
|
5839
|
+
/**
|
|
5840
|
+
* Write a file through the broker
|
|
5841
|
+
*/
|
|
5842
|
+
async fileWrite(params, options) {
|
|
5843
|
+
return this.request("file_write", params, {
|
|
5844
|
+
...options,
|
|
5845
|
+
channel: "socket"
|
|
5846
|
+
// file_write only allowed via socket
|
|
5847
|
+
});
|
|
5848
|
+
}
|
|
5849
|
+
/**
|
|
5850
|
+
* List files through the broker
|
|
5851
|
+
*/
|
|
5852
|
+
async fileList(params, options) {
|
|
5853
|
+
return this.request("file_list", params, options);
|
|
5854
|
+
}
|
|
5855
|
+
/**
|
|
5856
|
+
* Execute a command through the broker
|
|
5857
|
+
*/
|
|
5858
|
+
async exec(params, options) {
|
|
5859
|
+
return this.request("exec", params, {
|
|
5860
|
+
...options,
|
|
5861
|
+
channel: "socket"
|
|
5862
|
+
// exec only allowed via socket
|
|
5863
|
+
});
|
|
5864
|
+
}
|
|
5865
|
+
/**
|
|
5866
|
+
* Open a URL through the broker
|
|
5867
|
+
*/
|
|
5868
|
+
async openUrl(params, options) {
|
|
5869
|
+
return this.request("open_url", params, options);
|
|
5870
|
+
}
|
|
5871
|
+
/**
|
|
5872
|
+
* Inject a secret through the broker
|
|
5873
|
+
*/
|
|
5874
|
+
async secretInject(params, options) {
|
|
5875
|
+
return this.request("secret_inject", params, {
|
|
5876
|
+
...options,
|
|
5877
|
+
channel: "socket"
|
|
5878
|
+
// secret_inject only allowed via socket
|
|
5879
|
+
});
|
|
5880
|
+
}
|
|
5881
|
+
/**
|
|
5882
|
+
* Ping the broker
|
|
5883
|
+
*/
|
|
5884
|
+
async ping(echo, options) {
|
|
5885
|
+
return this.request("ping", { echo }, options);
|
|
5886
|
+
}
|
|
5887
|
+
/**
|
|
5888
|
+
* Install a skill through the broker
|
|
5889
|
+
* Socket-only operation due to privileged file operations
|
|
5890
|
+
*/
|
|
5891
|
+
async skillInstall(params, options) {
|
|
5892
|
+
return this.request("skill_install", params, {
|
|
5893
|
+
...options,
|
|
5894
|
+
channel: "socket"
|
|
5895
|
+
// skill_install only allowed via socket
|
|
5896
|
+
});
|
|
5897
|
+
}
|
|
5898
|
+
/**
|
|
5899
|
+
* Uninstall a skill through the broker
|
|
5900
|
+
* Socket-only operation due to privileged file operations
|
|
5901
|
+
*/
|
|
5902
|
+
async skillUninstall(params, options) {
|
|
5903
|
+
return this.request("skill_uninstall", params, {
|
|
5904
|
+
...options,
|
|
5905
|
+
channel: "socket"
|
|
5906
|
+
// skill_uninstall only allowed via socket
|
|
5907
|
+
});
|
|
5908
|
+
}
|
|
5909
|
+
/**
|
|
5910
|
+
* Check if the broker is available
|
|
5911
|
+
*/
|
|
5912
|
+
async isAvailable() {
|
|
5913
|
+
try {
|
|
5914
|
+
await this.ping();
|
|
5915
|
+
return true;
|
|
5916
|
+
} catch {
|
|
5917
|
+
return false;
|
|
5779
5918
|
}
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
// Quarantined
|
|
5794
|
-
...quarantined.map((q) => ({
|
|
5795
|
-
name: q.name,
|
|
5796
|
-
source: "quarantine",
|
|
5797
|
-
status: "quarantined",
|
|
5798
|
-
path: q.originalPath,
|
|
5799
|
-
description: void 0
|
|
5800
|
-
})),
|
|
5801
|
-
// Workspace: on disk but not approved or quarantined
|
|
5802
|
-
...workspaceNames.map((name) => ({
|
|
5803
|
-
name,
|
|
5804
|
-
source: "workspace",
|
|
5805
|
-
status: "workspace",
|
|
5806
|
-
path: path12.join(skillsDir2 ?? "", name),
|
|
5807
|
-
description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, name)) : void 0
|
|
5808
|
-
})),
|
|
5809
|
-
// Downloaded (not installed) → available
|
|
5810
|
-
...availableDownloads.map((d) => ({
|
|
5811
|
-
name: d.slug,
|
|
5812
|
-
source: "marketplace",
|
|
5813
|
-
status: "downloaded",
|
|
5814
|
-
description: d.description,
|
|
5815
|
-
path: "",
|
|
5816
|
-
publisher: d.author
|
|
5817
|
-
}))
|
|
5818
|
-
];
|
|
5819
|
-
return reply.send({ data });
|
|
5820
|
-
});
|
|
5821
|
-
app.get("/skills/quarantined", async (_request, reply) => {
|
|
5822
|
-
const quarantined = listQuarantined();
|
|
5823
|
-
return reply.send({ quarantined });
|
|
5824
|
-
});
|
|
5825
|
-
app.get(
|
|
5826
|
-
"/skills/:name",
|
|
5827
|
-
async (request, reply) => {
|
|
5828
|
-
const { name } = request.params;
|
|
5829
|
-
if (!name || typeof name !== "string") {
|
|
5830
|
-
return reply.code(400).send({ error: "Skill name is required" });
|
|
5831
|
-
}
|
|
5832
|
-
const analysis = getCachedAnalysis(name);
|
|
5833
|
-
const approved = listApproved();
|
|
5834
|
-
const entry = approved.find((s) => s.name === name);
|
|
5835
|
-
return reply.send({
|
|
5836
|
-
success: true,
|
|
5837
|
-
data: {
|
|
5838
|
-
name,
|
|
5839
|
-
analysis: analysis ?? null,
|
|
5840
|
-
publisher: entry?.publisher ?? null
|
|
5919
|
+
}
|
|
5920
|
+
/**
|
|
5921
|
+
* Make a request to the broker
|
|
5922
|
+
*/
|
|
5923
|
+
async request(method, params, options) {
|
|
5924
|
+
const channel = options?.channel || (this.preferSocket ? "socket" : "http");
|
|
5925
|
+
const timeout = options?.timeout || this.timeout;
|
|
5926
|
+
if (channel === "socket") {
|
|
5927
|
+
try {
|
|
5928
|
+
return await this.socketRequest(method, params, timeout);
|
|
5929
|
+
} catch (error) {
|
|
5930
|
+
if (!options?.channel) {
|
|
5931
|
+
return await this.httpRequest_internal(method, params, timeout);
|
|
5841
5932
|
}
|
|
5842
|
-
|
|
5843
|
-
}
|
|
5844
|
-
);
|
|
5845
|
-
app.post(
|
|
5846
|
-
"/skills/:name/analyze",
|
|
5847
|
-
async (request, reply) => {
|
|
5848
|
-
const { name } = request.params;
|
|
5849
|
-
const { content, metadata } = request.body ?? {};
|
|
5850
|
-
if (!name || typeof name !== "string") {
|
|
5851
|
-
return reply.code(400).send({ error: "Skill name is required" });
|
|
5852
|
-
}
|
|
5853
|
-
clearCachedAnalysis(name);
|
|
5854
|
-
if (content) {
|
|
5855
|
-
const analysis = analyzeSkill(name, content, metadata);
|
|
5856
|
-
return reply.send({ success: true, data: { analysis } });
|
|
5933
|
+
throw error;
|
|
5857
5934
|
}
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
data: {
|
|
5861
|
-
analysis: {
|
|
5862
|
-
status: "pending",
|
|
5863
|
-
analyzerId: "agenshield",
|
|
5864
|
-
commands: []
|
|
5865
|
-
}
|
|
5866
|
-
}
|
|
5867
|
-
});
|
|
5935
|
+
} else {
|
|
5936
|
+
return await this.httpRequest_internal(method, params, timeout);
|
|
5868
5937
|
}
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5938
|
+
}
|
|
5939
|
+
/**
|
|
5940
|
+
* Make a request via Unix socket
|
|
5941
|
+
*/
|
|
5942
|
+
async socketRequest(method, params, timeout) {
|
|
5943
|
+
return new Promise((resolve3, reject) => {
|
|
5944
|
+
const socket = net2.createConnection(this.socketPath);
|
|
5945
|
+
const id = randomUUID3();
|
|
5946
|
+
let responseData = "";
|
|
5947
|
+
let timeoutId;
|
|
5948
|
+
socket.on("connect", () => {
|
|
5949
|
+
const request = {
|
|
5950
|
+
jsonrpc: "2.0",
|
|
5951
|
+
id,
|
|
5952
|
+
method,
|
|
5953
|
+
params
|
|
5954
|
+
};
|
|
5955
|
+
socket.write(JSON.stringify(request) + "\n");
|
|
5956
|
+
timeoutId = setTimeout(() => {
|
|
5957
|
+
socket.destroy();
|
|
5958
|
+
reject(new Error("Request timeout"));
|
|
5959
|
+
}, timeout);
|
|
5960
|
+
});
|
|
5961
|
+
socket.on("data", (data) => {
|
|
5962
|
+
responseData += data.toString();
|
|
5963
|
+
const newlineIndex = responseData.indexOf("\n");
|
|
5964
|
+
if (newlineIndex !== -1) {
|
|
5965
|
+
clearTimeout(timeoutId);
|
|
5966
|
+
socket.end();
|
|
5967
|
+
try {
|
|
5968
|
+
const response = JSON.parse(
|
|
5969
|
+
responseData.slice(0, newlineIndex)
|
|
5970
|
+
);
|
|
5971
|
+
if (response.error) {
|
|
5972
|
+
const error = new Error(response.error.message);
|
|
5973
|
+
error.code = response.error.code;
|
|
5974
|
+
reject(error);
|
|
5975
|
+
} else {
|
|
5976
|
+
resolve3(response.result);
|
|
5977
|
+
}
|
|
5978
|
+
} catch (error) {
|
|
5979
|
+
reject(new Error("Invalid response from broker"));
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
});
|
|
5983
|
+
socket.on("error", (error) => {
|
|
5984
|
+
clearTimeout(timeoutId);
|
|
5985
|
+
reject(error);
|
|
5986
|
+
});
|
|
5987
|
+
});
|
|
5988
|
+
}
|
|
5989
|
+
/**
|
|
5990
|
+
* Make a request via HTTP
|
|
5991
|
+
*/
|
|
5992
|
+
async httpRequest_internal(method, params, timeout) {
|
|
5993
|
+
const url = `http://${this.httpHost}:${this.httpPort}/rpc`;
|
|
5994
|
+
const id = randomUUID3();
|
|
5995
|
+
const request = {
|
|
5996
|
+
jsonrpc: "2.0",
|
|
5997
|
+
id,
|
|
5998
|
+
method,
|
|
5999
|
+
params
|
|
6000
|
+
};
|
|
6001
|
+
const controller = new AbortController();
|
|
6002
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
6003
|
+
try {
|
|
6004
|
+
const response = await fetch(url, {
|
|
6005
|
+
method: "POST",
|
|
6006
|
+
headers: { "Content-Type": "application/json" },
|
|
6007
|
+
body: JSON.stringify(request),
|
|
6008
|
+
signal: controller.signal
|
|
6009
|
+
});
|
|
6010
|
+
clearTimeout(timeoutId);
|
|
6011
|
+
if (!response.ok) {
|
|
6012
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
6013
|
+
}
|
|
6014
|
+
const jsonResponse = await response.json();
|
|
6015
|
+
if (jsonResponse.error) {
|
|
6016
|
+
const error = new Error(jsonResponse.error.message);
|
|
6017
|
+
error.code = jsonResponse.error.code;
|
|
6018
|
+
throw error;
|
|
6019
|
+
}
|
|
6020
|
+
return jsonResponse.result;
|
|
6021
|
+
} catch (error) {
|
|
6022
|
+
clearTimeout(timeoutId);
|
|
6023
|
+
if (error.name === "AbortError") {
|
|
6024
|
+
throw new Error("Request timeout");
|
|
6025
|
+
}
|
|
6026
|
+
throw error;
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
};
|
|
6030
|
+
|
|
6031
|
+
// libs/shield-daemon/src/services/broker-bridge.ts
|
|
6032
|
+
var brokerClient = null;
|
|
6033
|
+
function getBrokerClient() {
|
|
6034
|
+
if (!brokerClient) {
|
|
6035
|
+
brokerClient = new BrokerClient({
|
|
6036
|
+
socketPath: process.env["AGENSHIELD_SOCKET"] || "/var/run/agenshield/agenshield.sock",
|
|
6037
|
+
httpHost: "localhost",
|
|
6038
|
+
httpPort: 5201,
|
|
6039
|
+
// Broker uses 5201, daemon uses 5200
|
|
6040
|
+
timeout: 6e4,
|
|
6041
|
+
// 60s timeout for file operations
|
|
6042
|
+
preferSocket: true
|
|
6043
|
+
});
|
|
6044
|
+
}
|
|
6045
|
+
return brokerClient;
|
|
6046
|
+
}
|
|
6047
|
+
async function isBrokerAvailable() {
|
|
6048
|
+
try {
|
|
6049
|
+
const client = getBrokerClient();
|
|
6050
|
+
return await client.isAvailable();
|
|
6051
|
+
} catch {
|
|
6052
|
+
return false;
|
|
6053
|
+
}
|
|
6054
|
+
}
|
|
6055
|
+
async function installSkillViaBroker(slug, files, options = {}) {
|
|
6056
|
+
const client = getBrokerClient();
|
|
6057
|
+
const brokerFiles = files.map((file) => ({
|
|
6058
|
+
name: file.name,
|
|
6059
|
+
content: file.content,
|
|
6060
|
+
base64: false
|
|
6061
|
+
}));
|
|
6062
|
+
const result = await client.skillInstall({
|
|
6063
|
+
slug,
|
|
6064
|
+
files: brokerFiles,
|
|
6065
|
+
createWrapper: options.createWrapper ?? true,
|
|
6066
|
+
agentHome: options.agentHome,
|
|
6067
|
+
socketGroup: options.socketGroup
|
|
6068
|
+
});
|
|
6069
|
+
return result;
|
|
6070
|
+
}
|
|
6071
|
+
async function uninstallSkillViaBroker(slug, options = {}) {
|
|
6072
|
+
const client = getBrokerClient();
|
|
6073
|
+
const result = await client.skillUninstall({
|
|
6074
|
+
slug,
|
|
6075
|
+
removeWrapper: options.removeWrapper ?? true,
|
|
6076
|
+
agentHome: options.agentHome
|
|
6077
|
+
});
|
|
6078
|
+
return result;
|
|
6079
|
+
}
|
|
6080
|
+
|
|
6081
|
+
// libs/shield-daemon/src/routes/skills.ts
|
|
6082
|
+
function findSkillMdRecursive(dir, depth = 0) {
|
|
6083
|
+
if (depth > 3) return null;
|
|
6084
|
+
try {
|
|
6085
|
+
for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
|
|
6086
|
+
const candidate = path12.join(dir, name);
|
|
6087
|
+
if (fs13.existsSync(candidate)) return candidate;
|
|
6088
|
+
}
|
|
6089
|
+
const entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
6090
|
+
for (const entry of entries) {
|
|
6091
|
+
if (!entry.isDirectory()) continue;
|
|
6092
|
+
const found = findSkillMdRecursive(path12.join(dir, entry.name), depth + 1);
|
|
6093
|
+
if (found) return found;
|
|
6094
|
+
}
|
|
6095
|
+
} catch {
|
|
6096
|
+
}
|
|
6097
|
+
return null;
|
|
6098
|
+
}
|
|
6099
|
+
function readSkillDescription(skillDir) {
|
|
6100
|
+
try {
|
|
6101
|
+
const mdPath = findSkillMdRecursive(skillDir);
|
|
6102
|
+
if (!mdPath) return void 0;
|
|
6103
|
+
const content = fs13.readFileSync(mdPath, "utf-8");
|
|
6104
|
+
const parsed = parseSkillMd(content);
|
|
6105
|
+
return parsed?.metadata?.description ?? void 0;
|
|
6106
|
+
} catch {
|
|
6107
|
+
return void 0;
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
async function skillsRoutes(app) {
|
|
6111
|
+
app.get("/skills", async (_request, reply) => {
|
|
6112
|
+
const approved = listApproved();
|
|
6113
|
+
const quarantined = listQuarantined();
|
|
6114
|
+
const downloaded = listDownloadedSkills();
|
|
6115
|
+
const skillsDir2 = getSkillsDir();
|
|
6116
|
+
const approvedNames = new Set(approved.map((a) => a.name));
|
|
6117
|
+
const availableDownloads = downloaded.filter((d) => !approvedNames.has(d.slug));
|
|
6118
|
+
const quarantinedNames = new Set(quarantined.map((q) => q.name));
|
|
6119
|
+
let onDiskNames = [];
|
|
6120
|
+
if (skillsDir2) {
|
|
6121
|
+
try {
|
|
6122
|
+
onDiskNames = fs13.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6123
|
+
} catch {
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
const workspaceNames = onDiskNames.filter(
|
|
6127
|
+
(n) => !approvedNames.has(n) && !quarantinedNames.has(n)
|
|
6128
|
+
);
|
|
6129
|
+
const data = [
|
|
6130
|
+
// Approved → active (with descriptions from SKILL.md)
|
|
6131
|
+
...approved.map((a) => ({
|
|
6132
|
+
name: a.name,
|
|
6133
|
+
source: "user",
|
|
6134
|
+
status: "active",
|
|
6135
|
+
path: path12.join(skillsDir2 ?? "", a.name),
|
|
6136
|
+
publisher: a.publisher,
|
|
6137
|
+
description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, a.name)) : void 0
|
|
6138
|
+
})),
|
|
6139
|
+
// Quarantined
|
|
6140
|
+
...quarantined.map((q) => ({
|
|
6141
|
+
name: q.name,
|
|
6142
|
+
source: "quarantine",
|
|
6143
|
+
status: "quarantined",
|
|
6144
|
+
path: q.originalPath,
|
|
6145
|
+
description: void 0
|
|
6146
|
+
})),
|
|
6147
|
+
// Workspace: on disk but not approved or quarantined
|
|
6148
|
+
...workspaceNames.map((name) => ({
|
|
6149
|
+
name,
|
|
6150
|
+
source: "workspace",
|
|
6151
|
+
status: "workspace",
|
|
6152
|
+
path: path12.join(skillsDir2 ?? "", name),
|
|
6153
|
+
description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, name)) : void 0
|
|
6154
|
+
})),
|
|
6155
|
+
// Downloaded (not installed) → available
|
|
6156
|
+
...availableDownloads.map((d) => ({
|
|
6157
|
+
name: d.slug,
|
|
6158
|
+
source: "marketplace",
|
|
6159
|
+
status: "downloaded",
|
|
6160
|
+
description: d.description,
|
|
6161
|
+
path: "",
|
|
6162
|
+
publisher: d.author
|
|
6163
|
+
}))
|
|
6164
|
+
];
|
|
6165
|
+
return reply.send({ data });
|
|
6166
|
+
});
|
|
6167
|
+
app.get("/skills/quarantined", async (_request, reply) => {
|
|
6168
|
+
const quarantined = listQuarantined();
|
|
6169
|
+
return reply.send({ quarantined });
|
|
6170
|
+
});
|
|
6171
|
+
app.get(
|
|
6172
|
+
"/skills/:name",
|
|
6173
|
+
async (request, reply) => {
|
|
6174
|
+
const { name } = request.params;
|
|
6175
|
+
if (!name || typeof name !== "string") {
|
|
6176
|
+
return reply.code(400).send({ error: "Skill name is required" });
|
|
6177
|
+
}
|
|
6178
|
+
const analysis = getCachedAnalysis(name);
|
|
6179
|
+
const approved = listApproved();
|
|
6180
|
+
const quarantined = listQuarantined();
|
|
6181
|
+
const entry = approved.find((s) => s.name === name);
|
|
6182
|
+
const qEntry = quarantined.find((q) => q.name === name);
|
|
6183
|
+
const skillsDir2 = getSkillsDir();
|
|
6184
|
+
let source = "user";
|
|
6185
|
+
let status = "active";
|
|
6186
|
+
let skillPath = "";
|
|
6187
|
+
if (qEntry) {
|
|
6188
|
+
source = "quarantine";
|
|
6189
|
+
status = "quarantined";
|
|
6190
|
+
skillPath = qEntry.originalPath;
|
|
6191
|
+
} else if (entry) {
|
|
6192
|
+
source = "user";
|
|
6193
|
+
status = "active";
|
|
6194
|
+
skillPath = skillsDir2 ? path12.join(skillsDir2, name) : "";
|
|
6195
|
+
} else if (skillsDir2) {
|
|
6196
|
+
source = "workspace";
|
|
6197
|
+
status = "workspace";
|
|
6198
|
+
skillPath = path12.join(skillsDir2, name);
|
|
6199
|
+
}
|
|
6200
|
+
let content = "";
|
|
6201
|
+
let metadata;
|
|
6202
|
+
const dirToRead = skillPath || (skillsDir2 ? path12.join(skillsDir2, name) : "");
|
|
6203
|
+
if (dirToRead) {
|
|
6204
|
+
try {
|
|
6205
|
+
const mdPath = findSkillMdRecursive(dirToRead);
|
|
6206
|
+
if (mdPath) {
|
|
6207
|
+
content = fs13.readFileSync(mdPath, "utf-8");
|
|
6208
|
+
const parsed = parseSkillMd(content);
|
|
6209
|
+
metadata = parsed?.metadata;
|
|
6210
|
+
}
|
|
6211
|
+
} catch {
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
if (!content) {
|
|
6215
|
+
try {
|
|
6216
|
+
const localMeta = getDownloadedSkillMeta(name);
|
|
6217
|
+
if (localMeta) {
|
|
6218
|
+
const localFiles = getDownloadedSkillFiles(name);
|
|
6219
|
+
const readmeFile = localFiles.find((f) => /readme|skill\.md/i.test(f.name));
|
|
6220
|
+
if (readmeFile?.content) {
|
|
6221
|
+
content = readmeFile.content;
|
|
6222
|
+
if (!metadata) {
|
|
6223
|
+
const parsed = parseSkillMd(content);
|
|
6224
|
+
metadata = parsed?.metadata;
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
} catch {
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
const description = dirToRead ? readSkillDescription(dirToRead) : void 0;
|
|
6232
|
+
if (content) {
|
|
6233
|
+
try {
|
|
6234
|
+
const cachedFiles = getDownloadedSkillFiles(name);
|
|
6235
|
+
if (cachedFiles.length > 0) {
|
|
6236
|
+
content = inlineImagesInMarkdown(content, cachedFiles);
|
|
6237
|
+
}
|
|
6238
|
+
} catch {
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
return reply.send({
|
|
6242
|
+
success: true,
|
|
6243
|
+
data: {
|
|
6244
|
+
name,
|
|
6245
|
+
source,
|
|
6246
|
+
status,
|
|
6247
|
+
path: skillPath,
|
|
6248
|
+
description,
|
|
6249
|
+
content,
|
|
6250
|
+
metadata: metadata ?? null,
|
|
6251
|
+
analysis: analysis ?? null,
|
|
6252
|
+
publisher: entry?.publisher ?? null
|
|
6253
|
+
}
|
|
6254
|
+
});
|
|
6255
|
+
}
|
|
6256
|
+
);
|
|
6257
|
+
app.post(
|
|
6258
|
+
"/skills/:name/analyze",
|
|
6259
|
+
async (request, reply) => {
|
|
6260
|
+
const { name } = request.params;
|
|
6261
|
+
const { content, metadata } = request.body ?? {};
|
|
6262
|
+
if (!name || typeof name !== "string") {
|
|
6263
|
+
return reply.code(400).send({ error: "Skill name is required" });
|
|
6264
|
+
}
|
|
6265
|
+
clearCachedAnalysis(name);
|
|
6266
|
+
if (content) {
|
|
6267
|
+
const analysis = analyzeSkill(name, content, metadata);
|
|
6268
|
+
return reply.send({ success: true, data: { analysis } });
|
|
6269
|
+
}
|
|
6270
|
+
return reply.send({
|
|
6271
|
+
success: true,
|
|
6272
|
+
data: {
|
|
6273
|
+
analysis: {
|
|
6274
|
+
status: "pending",
|
|
6275
|
+
analyzerId: "agenshield",
|
|
6276
|
+
commands: []
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
});
|
|
6280
|
+
}
|
|
6281
|
+
);
|
|
6282
|
+
app.post(
|
|
6283
|
+
"/skills/:name/approve",
|
|
6284
|
+
async (request, reply) => {
|
|
5873
6285
|
const { name } = request.params;
|
|
5874
6286
|
if (!name || typeof name !== "string") {
|
|
5875
6287
|
return reply.code(400).send({ error: "Skill name is required" });
|
|
@@ -5927,8 +6339,16 @@ async function skillsRoutes(app) {
|
|
|
5927
6339
|
const isInstalled = fs13.existsSync(skillDir);
|
|
5928
6340
|
if (isInstalled) {
|
|
5929
6341
|
try {
|
|
5930
|
-
|
|
5931
|
-
|
|
6342
|
+
const brokerAvailable = await isBrokerAvailable();
|
|
6343
|
+
if (brokerAvailable) {
|
|
6344
|
+
await uninstallSkillViaBroker(name, {
|
|
6345
|
+
removeWrapper: true,
|
|
6346
|
+
agentHome
|
|
6347
|
+
});
|
|
6348
|
+
} else {
|
|
6349
|
+
fs13.rmSync(skillDir, { recursive: true, force: true });
|
|
6350
|
+
removeSkillWrapper(name, binDir);
|
|
6351
|
+
}
|
|
5932
6352
|
removeSkillEntry(name);
|
|
5933
6353
|
removeSkillPolicy(name);
|
|
5934
6354
|
removeFromApprovedList(name);
|
|
@@ -5949,18 +6369,30 @@ async function skillsRoutes(app) {
|
|
|
5949
6369
|
}
|
|
5950
6370
|
try {
|
|
5951
6371
|
addToApprovedList(name, meta.author);
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
const
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
6372
|
+
const brokerAvailable = await isBrokerAvailable();
|
|
6373
|
+
if (brokerAvailable) {
|
|
6374
|
+
const brokerResult = await installSkillViaBroker(
|
|
6375
|
+
name,
|
|
6376
|
+
files.map((f) => ({ name: f.name, content: f.content })),
|
|
6377
|
+
{ createWrapper: true, agentHome, socketGroup }
|
|
6378
|
+
);
|
|
6379
|
+
if (!brokerResult.installed) {
|
|
6380
|
+
throw new Error("Broker failed to install skill files");
|
|
6381
|
+
}
|
|
6382
|
+
} else {
|
|
6383
|
+
fs13.mkdirSync(skillDir, { recursive: true });
|
|
6384
|
+
for (const file of files) {
|
|
6385
|
+
const filePath = path12.join(skillDir, file.name);
|
|
6386
|
+
fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
|
|
6387
|
+
fs13.writeFileSync(filePath, file.content, "utf-8");
|
|
6388
|
+
}
|
|
6389
|
+
try {
|
|
6390
|
+
execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
6391
|
+
execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
|
|
6392
|
+
} catch {
|
|
6393
|
+
}
|
|
6394
|
+
createSkillWrapper(name, binDir);
|
|
5962
6395
|
}
|
|
5963
|
-
createSkillWrapper(name, binDir);
|
|
5964
6396
|
addSkillEntry(name);
|
|
5965
6397
|
addSkillPolicy(name);
|
|
5966
6398
|
console.log(`[Skills] Enabled marketplace skill: ${name}`);
|
|
@@ -6403,47 +6835,21 @@ async function authRoutes(app) {
|
|
|
6403
6835
|
);
|
|
6404
6836
|
app.post(
|
|
6405
6837
|
"/auth/enable",
|
|
6406
|
-
async (request, reply) => {
|
|
6407
|
-
const passcodeSet = await isPasscodeSet();
|
|
6408
|
-
if (!passcodeSet) {
|
|
6409
|
-
reply.code(400);
|
|
6410
|
-
return {
|
|
6411
|
-
success: false,
|
|
6412
|
-
error: "Passcode not configured. Use /auth/setup first."
|
|
6413
|
-
};
|
|
6414
|
-
}
|
|
6415
|
-
setProtectionEnabled(true);
|
|
6416
|
-
return { success: true };
|
|
6417
|
-
}
|
|
6418
|
-
);
|
|
6419
|
-
app.post(
|
|
6420
|
-
"/auth/disable",
|
|
6421
|
-
async (request, reply) => {
|
|
6422
|
-
if (!isRunningAsRoot()) {
|
|
6423
|
-
const token = extractToken(request);
|
|
6424
|
-
if (!token) {
|
|
6425
|
-
reply.code(401);
|
|
6426
|
-
return {
|
|
6427
|
-
success: false,
|
|
6428
|
-
error: "Authentication required to disable protection"
|
|
6429
|
-
};
|
|
6430
|
-
}
|
|
6431
|
-
const sessionManager = getSessionManager();
|
|
6432
|
-
const session = sessionManager.validateSession(token);
|
|
6433
|
-
if (!session) {
|
|
6434
|
-
reply.code(401);
|
|
6435
|
-
return {
|
|
6436
|
-
success: false,
|
|
6437
|
-
error: "Invalid or expired token"
|
|
6438
|
-
};
|
|
6439
|
-
}
|
|
6838
|
+
async (request, reply) => {
|
|
6839
|
+
const passcodeSet = await isPasscodeSet();
|
|
6840
|
+
if (!passcodeSet) {
|
|
6841
|
+
reply.code(400);
|
|
6842
|
+
return {
|
|
6843
|
+
success: false,
|
|
6844
|
+
error: "Passcode not configured. Use /auth/setup first."
|
|
6845
|
+
};
|
|
6440
6846
|
}
|
|
6441
|
-
setProtectionEnabled(
|
|
6847
|
+
setProtectionEnabled(true);
|
|
6442
6848
|
return { success: true };
|
|
6443
6849
|
}
|
|
6444
6850
|
);
|
|
6445
6851
|
app.post(
|
|
6446
|
-
"/auth/
|
|
6852
|
+
"/auth/disable",
|
|
6447
6853
|
async (request, reply) => {
|
|
6448
6854
|
if (!isRunningAsRoot()) {
|
|
6449
6855
|
const token = extractToken(request);
|
|
@@ -6451,391 +6857,150 @@ async function authRoutes(app) {
|
|
|
6451
6857
|
reply.code(401);
|
|
6452
6858
|
return {
|
|
6453
6859
|
success: false,
|
|
6454
|
-
error: "Authentication required to
|
|
6455
|
-
};
|
|
6456
|
-
}
|
|
6457
|
-
const sessionManager = getSessionManager();
|
|
6458
|
-
const session = sessionManager.validateSession(token);
|
|
6459
|
-
if (!session) {
|
|
6460
|
-
reply.code(401);
|
|
6461
|
-
return {
|
|
6462
|
-
success: false,
|
|
6463
|
-
error: "Invalid or expired token"
|
|
6860
|
+
error: "Authentication required to disable protection"
|
|
6861
|
+
};
|
|
6862
|
+
}
|
|
6863
|
+
const sessionManager = getSessionManager();
|
|
6864
|
+
const session = sessionManager.validateSession(token);
|
|
6865
|
+
if (!session) {
|
|
6866
|
+
reply.code(401);
|
|
6867
|
+
return {
|
|
6868
|
+
success: false,
|
|
6869
|
+
error: "Invalid or expired token"
|
|
6464
6870
|
};
|
|
6465
6871
|
}
|
|
6466
6872
|
}
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
reply.code(400);
|
|
6470
|
-
return {
|
|
6471
|
-
success: false,
|
|
6472
|
-
error: 'Invalid request: "allowed" must be a boolean'
|
|
6473
|
-
};
|
|
6474
|
-
}
|
|
6475
|
-
setAnonymousReadOnly(allowed);
|
|
6476
|
-
return { success: true, allowAnonymousReadOnly: allowed };
|
|
6477
|
-
}
|
|
6478
|
-
);
|
|
6479
|
-
}
|
|
6480
|
-
|
|
6481
|
-
// libs/shield-daemon/src/routes/secrets.ts
|
|
6482
|
-
import { isSecretEnvVar } from "@agenshield/sandbox";
|
|
6483
|
-
import crypto4 from "node:crypto";
|
|
6484
|
-
function maskValue(value) {
|
|
6485
|
-
if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
6486
|
-
return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
|
|
6487
|
-
}
|
|
6488
|
-
function toMasked(secret) {
|
|
6489
|
-
return {
|
|
6490
|
-
id: secret.id,
|
|
6491
|
-
name: secret.name,
|
|
6492
|
-
policyIds: secret.policyIds,
|
|
6493
|
-
maskedValue: maskValue(secret.value),
|
|
6494
|
-
createdAt: secret.createdAt
|
|
6495
|
-
};
|
|
6496
|
-
}
|
|
6497
|
-
async function secretsRoutes(app) {
|
|
6498
|
-
app.get("/secrets", async () => {
|
|
6499
|
-
const vault = getVault();
|
|
6500
|
-
const secrets = await vault.get("secrets") ?? [];
|
|
6501
|
-
return { data: secrets.map(toMasked) };
|
|
6502
|
-
});
|
|
6503
|
-
app.get("/secrets/env", async () => {
|
|
6504
|
-
const names = /* @__PURE__ */ new Set();
|
|
6505
|
-
for (const key of Object.keys(process.env)) {
|
|
6506
|
-
if (process.env[key] && isSecretEnvVar(key)) {
|
|
6507
|
-
names.add(key);
|
|
6508
|
-
}
|
|
6509
|
-
}
|
|
6510
|
-
const userSecrets = process.env["AGENSHIELD_USER_SECRETS"];
|
|
6511
|
-
if (userSecrets) {
|
|
6512
|
-
for (const name of userSecrets.split(",").filter(Boolean)) {
|
|
6513
|
-
names.add(name);
|
|
6514
|
-
}
|
|
6515
|
-
}
|
|
6516
|
-
return { data: Array.from(names).sort((a, b) => a.localeCompare(b)) };
|
|
6517
|
-
});
|
|
6518
|
-
app.post(
|
|
6519
|
-
"/secrets",
|
|
6520
|
-
async (request) => {
|
|
6521
|
-
const { name, value, policyIds } = request.body;
|
|
6522
|
-
if (!name?.trim() || !value?.trim()) {
|
|
6523
|
-
return { success: false, error: "Name and value are required" };
|
|
6524
|
-
}
|
|
6525
|
-
const vault = getVault();
|
|
6526
|
-
const secrets = await vault.get("secrets") ?? [];
|
|
6527
|
-
const newSecret = {
|
|
6528
|
-
id: crypto4.randomUUID(),
|
|
6529
|
-
name: name.trim(),
|
|
6530
|
-
value,
|
|
6531
|
-
policyIds: policyIds ?? [],
|
|
6532
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6533
|
-
};
|
|
6534
|
-
secrets.push(newSecret);
|
|
6535
|
-
await vault.set("secrets", secrets);
|
|
6536
|
-
return { data: toMasked(newSecret) };
|
|
6537
|
-
}
|
|
6538
|
-
);
|
|
6539
|
-
app.patch(
|
|
6540
|
-
"/secrets/:id",
|
|
6541
|
-
async (request) => {
|
|
6542
|
-
const { id } = request.params;
|
|
6543
|
-
const { policyIds } = request.body;
|
|
6544
|
-
const vault = getVault();
|
|
6545
|
-
const secrets = await vault.get("secrets") ?? [];
|
|
6546
|
-
const idx = secrets.findIndex((s) => s.id === id);
|
|
6547
|
-
if (idx === -1) return { success: false, error: "Secret not found" };
|
|
6548
|
-
secrets[idx].policyIds = policyIds ?? [];
|
|
6549
|
-
await vault.set("secrets", secrets);
|
|
6550
|
-
return { data: toMasked(secrets[idx]) };
|
|
6551
|
-
}
|
|
6552
|
-
);
|
|
6553
|
-
app.delete(
|
|
6554
|
-
"/secrets/:id",
|
|
6555
|
-
async (request) => {
|
|
6556
|
-
const { id } = request.params;
|
|
6557
|
-
const vault = getVault();
|
|
6558
|
-
const secrets = await vault.get("secrets") ?? [];
|
|
6559
|
-
const filtered = secrets.filter((s) => s.id !== id);
|
|
6560
|
-
if (filtered.length === secrets.length) {
|
|
6561
|
-
return { success: false, error: "Secret not found" };
|
|
6562
|
-
}
|
|
6563
|
-
await vault.set("secrets", filtered);
|
|
6564
|
-
return { deleted: true };
|
|
6565
|
-
}
|
|
6566
|
-
);
|
|
6567
|
-
}
|
|
6568
|
-
|
|
6569
|
-
// libs/shield-daemon/src/routes/marketplace.ts
|
|
6570
|
-
import * as fs15 from "node:fs";
|
|
6571
|
-
import * as path14 from "node:path";
|
|
6572
|
-
|
|
6573
|
-
// libs/shield-broker/dist/index.js
|
|
6574
|
-
import { exec } from "node:child_process";
|
|
6575
|
-
import { promisify } from "node:util";
|
|
6576
|
-
import * as net2 from "node:net";
|
|
6577
|
-
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
6578
|
-
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
6579
|
-
var execAsync = promisify(exec);
|
|
6580
|
-
var BrokerClient = class {
|
|
6581
|
-
socketPath;
|
|
6582
|
-
httpHost;
|
|
6583
|
-
httpPort;
|
|
6584
|
-
timeout;
|
|
6585
|
-
preferSocket;
|
|
6586
|
-
constructor(options = {}) {
|
|
6587
|
-
this.socketPath = options.socketPath || "/var/run/agenshield/agenshield.sock";
|
|
6588
|
-
this.httpHost = options.httpHost || "localhost";
|
|
6589
|
-
this.httpPort = options.httpPort || 5201;
|
|
6590
|
-
this.timeout = options.timeout || 3e4;
|
|
6591
|
-
this.preferSocket = options.preferSocket ?? true;
|
|
6592
|
-
}
|
|
6593
|
-
/**
|
|
6594
|
-
* Make an HTTP request through the broker
|
|
6595
|
-
*/
|
|
6596
|
-
async httpRequest(params, options) {
|
|
6597
|
-
return this.request("http_request", params, options);
|
|
6598
|
-
}
|
|
6599
|
-
/**
|
|
6600
|
-
* Read a file through the broker
|
|
6601
|
-
*/
|
|
6602
|
-
async fileRead(params, options) {
|
|
6603
|
-
return this.request("file_read", params, options);
|
|
6604
|
-
}
|
|
6605
|
-
/**
|
|
6606
|
-
* Write a file through the broker
|
|
6607
|
-
*/
|
|
6608
|
-
async fileWrite(params, options) {
|
|
6609
|
-
return this.request("file_write", params, {
|
|
6610
|
-
...options,
|
|
6611
|
-
channel: "socket"
|
|
6612
|
-
// file_write only allowed via socket
|
|
6613
|
-
});
|
|
6614
|
-
}
|
|
6615
|
-
/**
|
|
6616
|
-
* List files through the broker
|
|
6617
|
-
*/
|
|
6618
|
-
async fileList(params, options) {
|
|
6619
|
-
return this.request("file_list", params, options);
|
|
6620
|
-
}
|
|
6621
|
-
/**
|
|
6622
|
-
* Execute a command through the broker
|
|
6623
|
-
*/
|
|
6624
|
-
async exec(params, options) {
|
|
6625
|
-
return this.request("exec", params, {
|
|
6626
|
-
...options,
|
|
6627
|
-
channel: "socket"
|
|
6628
|
-
// exec only allowed via socket
|
|
6629
|
-
});
|
|
6630
|
-
}
|
|
6631
|
-
/**
|
|
6632
|
-
* Open a URL through the broker
|
|
6633
|
-
*/
|
|
6634
|
-
async openUrl(params, options) {
|
|
6635
|
-
return this.request("open_url", params, options);
|
|
6636
|
-
}
|
|
6637
|
-
/**
|
|
6638
|
-
* Inject a secret through the broker
|
|
6639
|
-
*/
|
|
6640
|
-
async secretInject(params, options) {
|
|
6641
|
-
return this.request("secret_inject", params, {
|
|
6642
|
-
...options,
|
|
6643
|
-
channel: "socket"
|
|
6644
|
-
// secret_inject only allowed via socket
|
|
6645
|
-
});
|
|
6646
|
-
}
|
|
6647
|
-
/**
|
|
6648
|
-
* Ping the broker
|
|
6649
|
-
*/
|
|
6650
|
-
async ping(echo, options) {
|
|
6651
|
-
return this.request("ping", { echo }, options);
|
|
6652
|
-
}
|
|
6653
|
-
/**
|
|
6654
|
-
* Install a skill through the broker
|
|
6655
|
-
* Socket-only operation due to privileged file operations
|
|
6656
|
-
*/
|
|
6657
|
-
async skillInstall(params, options) {
|
|
6658
|
-
return this.request("skill_install", params, {
|
|
6659
|
-
...options,
|
|
6660
|
-
channel: "socket"
|
|
6661
|
-
// skill_install only allowed via socket
|
|
6662
|
-
});
|
|
6663
|
-
}
|
|
6664
|
-
/**
|
|
6665
|
-
* Uninstall a skill through the broker
|
|
6666
|
-
* Socket-only operation due to privileged file operations
|
|
6667
|
-
*/
|
|
6668
|
-
async skillUninstall(params, options) {
|
|
6669
|
-
return this.request("skill_uninstall", params, {
|
|
6670
|
-
...options,
|
|
6671
|
-
channel: "socket"
|
|
6672
|
-
// skill_uninstall only allowed via socket
|
|
6673
|
-
});
|
|
6674
|
-
}
|
|
6675
|
-
/**
|
|
6676
|
-
* Check if the broker is available
|
|
6677
|
-
*/
|
|
6678
|
-
async isAvailable() {
|
|
6679
|
-
try {
|
|
6680
|
-
await this.ping();
|
|
6681
|
-
return true;
|
|
6682
|
-
} catch {
|
|
6683
|
-
return false;
|
|
6684
|
-
}
|
|
6685
|
-
}
|
|
6686
|
-
/**
|
|
6687
|
-
* Make a request to the broker
|
|
6688
|
-
*/
|
|
6689
|
-
async request(method, params, options) {
|
|
6690
|
-
const channel = options?.channel || (this.preferSocket ? "socket" : "http");
|
|
6691
|
-
const timeout = options?.timeout || this.timeout;
|
|
6692
|
-
if (channel === "socket") {
|
|
6693
|
-
try {
|
|
6694
|
-
return await this.socketRequest(method, params, timeout);
|
|
6695
|
-
} catch (error) {
|
|
6696
|
-
if (!options?.channel) {
|
|
6697
|
-
return await this.httpRequest_internal(method, params, timeout);
|
|
6698
|
-
}
|
|
6699
|
-
throw error;
|
|
6700
|
-
}
|
|
6701
|
-
} else {
|
|
6702
|
-
return await this.httpRequest_internal(method, params, timeout);
|
|
6873
|
+
setProtectionEnabled(false);
|
|
6874
|
+
return { success: true };
|
|
6703
6875
|
}
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
}, timeout);
|
|
6726
|
-
});
|
|
6727
|
-
socket.on("data", (data) => {
|
|
6728
|
-
responseData += data.toString();
|
|
6729
|
-
const newlineIndex = responseData.indexOf("\n");
|
|
6730
|
-
if (newlineIndex !== -1) {
|
|
6731
|
-
clearTimeout(timeoutId);
|
|
6732
|
-
socket.end();
|
|
6733
|
-
try {
|
|
6734
|
-
const response = JSON.parse(
|
|
6735
|
-
responseData.slice(0, newlineIndex)
|
|
6736
|
-
);
|
|
6737
|
-
if (response.error) {
|
|
6738
|
-
const error = new Error(response.error.message);
|
|
6739
|
-
error.code = response.error.code;
|
|
6740
|
-
reject(error);
|
|
6741
|
-
} else {
|
|
6742
|
-
resolve3(response.result);
|
|
6743
|
-
}
|
|
6744
|
-
} catch (error) {
|
|
6745
|
-
reject(new Error("Invalid response from broker"));
|
|
6746
|
-
}
|
|
6876
|
+
);
|
|
6877
|
+
app.post(
|
|
6878
|
+
"/auth/anonymous-readonly",
|
|
6879
|
+
async (request, reply) => {
|
|
6880
|
+
if (!isRunningAsRoot()) {
|
|
6881
|
+
const token = extractToken(request);
|
|
6882
|
+
if (!token) {
|
|
6883
|
+
reply.code(401);
|
|
6884
|
+
return {
|
|
6885
|
+
success: false,
|
|
6886
|
+
error: "Authentication required to change anonymous access"
|
|
6887
|
+
};
|
|
6888
|
+
}
|
|
6889
|
+
const sessionManager = getSessionManager();
|
|
6890
|
+
const session = sessionManager.validateSession(token);
|
|
6891
|
+
if (!session) {
|
|
6892
|
+
reply.code(401);
|
|
6893
|
+
return {
|
|
6894
|
+
success: false,
|
|
6895
|
+
error: "Invalid or expired token"
|
|
6896
|
+
};
|
|
6747
6897
|
}
|
|
6748
|
-
});
|
|
6749
|
-
socket.on("error", (error) => {
|
|
6750
|
-
clearTimeout(timeoutId);
|
|
6751
|
-
reject(error);
|
|
6752
|
-
});
|
|
6753
|
-
});
|
|
6754
|
-
}
|
|
6755
|
-
/**
|
|
6756
|
-
* Make a request via HTTP
|
|
6757
|
-
*/
|
|
6758
|
-
async httpRequest_internal(method, params, timeout) {
|
|
6759
|
-
const url = `http://${this.httpHost}:${this.httpPort}/rpc`;
|
|
6760
|
-
const id = randomUUID3();
|
|
6761
|
-
const request = {
|
|
6762
|
-
jsonrpc: "2.0",
|
|
6763
|
-
id,
|
|
6764
|
-
method,
|
|
6765
|
-
params
|
|
6766
|
-
};
|
|
6767
|
-
const controller = new AbortController();
|
|
6768
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
6769
|
-
try {
|
|
6770
|
-
const response = await fetch(url, {
|
|
6771
|
-
method: "POST",
|
|
6772
|
-
headers: { "Content-Type": "application/json" },
|
|
6773
|
-
body: JSON.stringify(request),
|
|
6774
|
-
signal: controller.signal
|
|
6775
|
-
});
|
|
6776
|
-
clearTimeout(timeoutId);
|
|
6777
|
-
if (!response.ok) {
|
|
6778
|
-
throw new Error(`HTTP error: ${response.status}`);
|
|
6779
|
-
}
|
|
6780
|
-
const jsonResponse = await response.json();
|
|
6781
|
-
if (jsonResponse.error) {
|
|
6782
|
-
const error = new Error(jsonResponse.error.message);
|
|
6783
|
-
error.code = jsonResponse.error.code;
|
|
6784
|
-
throw error;
|
|
6785
6898
|
}
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6899
|
+
const { allowed } = request.body;
|
|
6900
|
+
if (typeof allowed !== "boolean") {
|
|
6901
|
+
reply.code(400);
|
|
6902
|
+
return {
|
|
6903
|
+
success: false,
|
|
6904
|
+
error: 'Invalid request: "allowed" must be a boolean'
|
|
6905
|
+
};
|
|
6791
6906
|
}
|
|
6792
|
-
|
|
6907
|
+
setAnonymousReadOnly(allowed);
|
|
6908
|
+
return { success: true, allowAnonymousReadOnly: allowed };
|
|
6793
6909
|
}
|
|
6794
|
-
|
|
6795
|
-
}
|
|
6910
|
+
);
|
|
6911
|
+
}
|
|
6796
6912
|
|
|
6797
|
-
// libs/shield-daemon/src/
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
httpHost: "localhost",
|
|
6804
|
-
httpPort: 5201,
|
|
6805
|
-
// Broker uses 5201, daemon uses 5200
|
|
6806
|
-
timeout: 6e4,
|
|
6807
|
-
// 60s timeout for file operations
|
|
6808
|
-
preferSocket: true
|
|
6809
|
-
});
|
|
6810
|
-
}
|
|
6811
|
-
return brokerClient;
|
|
6913
|
+
// libs/shield-daemon/src/routes/secrets.ts
|
|
6914
|
+
import { isSecretEnvVar } from "@agenshield/sandbox";
|
|
6915
|
+
import crypto4 from "node:crypto";
|
|
6916
|
+
function maskValue(value) {
|
|
6917
|
+
if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
6918
|
+
return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
|
|
6812
6919
|
}
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6920
|
+
function toMasked(secret) {
|
|
6921
|
+
return {
|
|
6922
|
+
id: secret.id,
|
|
6923
|
+
name: secret.name,
|
|
6924
|
+
policyIds: secret.policyIds,
|
|
6925
|
+
maskedValue: maskValue(secret.value),
|
|
6926
|
+
createdAt: secret.createdAt
|
|
6927
|
+
};
|
|
6820
6928
|
}
|
|
6821
|
-
async function
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
base64: false
|
|
6827
|
-
}));
|
|
6828
|
-
const result = await client.skillInstall({
|
|
6829
|
-
slug,
|
|
6830
|
-
files: brokerFiles,
|
|
6831
|
-
createWrapper: options.createWrapper ?? true,
|
|
6832
|
-
agentHome: options.agentHome,
|
|
6833
|
-
socketGroup: options.socketGroup
|
|
6929
|
+
async function secretsRoutes(app) {
|
|
6930
|
+
app.get("/secrets", async () => {
|
|
6931
|
+
const vault = getVault();
|
|
6932
|
+
const secrets = await vault.get("secrets") ?? [];
|
|
6933
|
+
return { data: secrets.map(toMasked) };
|
|
6834
6934
|
});
|
|
6835
|
-
|
|
6935
|
+
app.get("/secrets/env", async () => {
|
|
6936
|
+
const names = /* @__PURE__ */ new Set();
|
|
6937
|
+
for (const key of Object.keys(process.env)) {
|
|
6938
|
+
if (process.env[key] && isSecretEnvVar(key)) {
|
|
6939
|
+
names.add(key);
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
const userSecrets = process.env["AGENSHIELD_USER_SECRETS"];
|
|
6943
|
+
if (userSecrets) {
|
|
6944
|
+
for (const name of userSecrets.split(",").filter(Boolean)) {
|
|
6945
|
+
names.add(name);
|
|
6946
|
+
}
|
|
6947
|
+
}
|
|
6948
|
+
return { data: Array.from(names).sort((a, b) => a.localeCompare(b)) };
|
|
6949
|
+
});
|
|
6950
|
+
app.post(
|
|
6951
|
+
"/secrets",
|
|
6952
|
+
async (request) => {
|
|
6953
|
+
const { name, value, policyIds } = request.body;
|
|
6954
|
+
if (!name?.trim() || !value?.trim()) {
|
|
6955
|
+
return { success: false, error: "Name and value are required" };
|
|
6956
|
+
}
|
|
6957
|
+
const vault = getVault();
|
|
6958
|
+
const secrets = await vault.get("secrets") ?? [];
|
|
6959
|
+
const newSecret = {
|
|
6960
|
+
id: crypto4.randomUUID(),
|
|
6961
|
+
name: name.trim(),
|
|
6962
|
+
value,
|
|
6963
|
+
policyIds: policyIds ?? [],
|
|
6964
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6965
|
+
};
|
|
6966
|
+
secrets.push(newSecret);
|
|
6967
|
+
await vault.set("secrets", secrets);
|
|
6968
|
+
return { data: toMasked(newSecret) };
|
|
6969
|
+
}
|
|
6970
|
+
);
|
|
6971
|
+
app.patch(
|
|
6972
|
+
"/secrets/:id",
|
|
6973
|
+
async (request) => {
|
|
6974
|
+
const { id } = request.params;
|
|
6975
|
+
const { policyIds } = request.body;
|
|
6976
|
+
const vault = getVault();
|
|
6977
|
+
const secrets = await vault.get("secrets") ?? [];
|
|
6978
|
+
const idx = secrets.findIndex((s) => s.id === id);
|
|
6979
|
+
if (idx === -1) return { success: false, error: "Secret not found" };
|
|
6980
|
+
secrets[idx].policyIds = policyIds ?? [];
|
|
6981
|
+
await vault.set("secrets", secrets);
|
|
6982
|
+
return { data: toMasked(secrets[idx]) };
|
|
6983
|
+
}
|
|
6984
|
+
);
|
|
6985
|
+
app.delete(
|
|
6986
|
+
"/secrets/:id",
|
|
6987
|
+
async (request) => {
|
|
6988
|
+
const { id } = request.params;
|
|
6989
|
+
const vault = getVault();
|
|
6990
|
+
const secrets = await vault.get("secrets") ?? [];
|
|
6991
|
+
const filtered = secrets.filter((s) => s.id !== id);
|
|
6992
|
+
if (filtered.length === secrets.length) {
|
|
6993
|
+
return { success: false, error: "Secret not found" };
|
|
6994
|
+
}
|
|
6995
|
+
await vault.set("secrets", filtered);
|
|
6996
|
+
return { deleted: true };
|
|
6997
|
+
}
|
|
6998
|
+
);
|
|
6836
6999
|
}
|
|
6837
7000
|
|
|
6838
7001
|
// libs/shield-daemon/src/routes/marketplace.ts
|
|
7002
|
+
import * as fs15 from "node:fs";
|
|
7003
|
+
import * as path14 from "node:path";
|
|
6839
7004
|
async function marketplaceRoutes(app) {
|
|
6840
7005
|
app.get(
|
|
6841
7006
|
"/marketplace/search",
|
|
@@ -6871,6 +7036,10 @@ async function marketplaceRoutes(app) {
|
|
|
6871
7036
|
if (localMeta) {
|
|
6872
7037
|
const localFiles = getDownloadedSkillFiles(slug);
|
|
6873
7038
|
const readmeFile = localFiles.find((f) => /readme|skill\.md/i.test(f.name));
|
|
7039
|
+
let readme = readmeFile?.content;
|
|
7040
|
+
if (readme) {
|
|
7041
|
+
readme = inlineImagesInMarkdown(readme, localFiles);
|
|
7042
|
+
}
|
|
6874
7043
|
const skill2 = {
|
|
6875
7044
|
name: localMeta.name,
|
|
6876
7045
|
slug: localMeta.slug,
|
|
@@ -6880,7 +7049,7 @@ async function marketplaceRoutes(app) {
|
|
|
6880
7049
|
installs: 0,
|
|
6881
7050
|
// Not stored locally
|
|
6882
7051
|
tags: localMeta.tags,
|
|
6883
|
-
readme
|
|
7052
|
+
readme,
|
|
6884
7053
|
files: localFiles
|
|
6885
7054
|
};
|
|
6886
7055
|
if (localMeta.analysis) {
|