@agenshield/daemon 0.4.3 → 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 +1287 -1041
- package/main.js +1287 -1041
- package/package.json +4 -4
- package/routes/activity.d.ts +6 -0
- package/routes/activity.d.ts.map +1 -0
- package/routes/index.d.ts.map +1 -1
- package/routes/marketplace.d.ts.map +1 -1
- package/routes/skills.d.ts.map +1 -1
- package/services/activity-log.d.ts +4 -0
- package/services/activity-log.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-Chp3YFDr.js → index-C4yZ-JLI.js} +159 -164
- 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,1063 +5804,1203 @@ async function getCachedAnalysis2(skillName, publisher) {
|
|
|
5749
5804
|
};
|
|
5750
5805
|
}
|
|
5751
5806
|
|
|
5752
|
-
// libs/shield-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
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;
|
|
5826
|
+
}
|
|
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;
|
|
5813
5918
|
}
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
}
|
|
5828
|
-
return reply.send({
|
|
5829
|
-
success: true,
|
|
5830
|
-
data: {
|
|
5831
|
-
analysis: {
|
|
5832
|
-
status: "pending",
|
|
5833
|
-
analyzerId: "agenshield",
|
|
5834
|
-
commands: []
|
|
5835
|
-
}
|
|
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);
|
|
5836
5932
|
}
|
|
5837
|
-
|
|
5838
|
-
}
|
|
5839
|
-
);
|
|
5840
|
-
app.post(
|
|
5841
|
-
"/skills/:name/approve",
|
|
5842
|
-
async (request, reply) => {
|
|
5843
|
-
const { name } = request.params;
|
|
5844
|
-
if (!name || typeof name !== "string") {
|
|
5845
|
-
return reply.code(400).send({ error: "Skill name is required" });
|
|
5846
|
-
}
|
|
5847
|
-
const result = approveSkill(name);
|
|
5848
|
-
if (!result.success) {
|
|
5849
|
-
return reply.code(404).send({ error: result.error });
|
|
5850
|
-
}
|
|
5851
|
-
return reply.send({ success: true, message: `Skill "${name}" approved` });
|
|
5852
|
-
}
|
|
5853
|
-
);
|
|
5854
|
-
app.delete(
|
|
5855
|
-
"/skills/:name",
|
|
5856
|
-
async (request, reply) => {
|
|
5857
|
-
const { name } = request.params;
|
|
5858
|
-
if (!name || typeof name !== "string") {
|
|
5859
|
-
return reply.code(400).send({ error: "Skill name is required" });
|
|
5860
|
-
}
|
|
5861
|
-
const result = rejectSkill(name);
|
|
5862
|
-
if (!result.success) {
|
|
5863
|
-
return reply.code(404).send({ error: result.error });
|
|
5933
|
+
throw error;
|
|
5864
5934
|
}
|
|
5865
|
-
|
|
5935
|
+
} else {
|
|
5936
|
+
return await this.httpRequest_internal(method, params, timeout);
|
|
5866
5937
|
}
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
const skillDir = path12.join(skillsDir2, name);
|
|
5897
|
-
const isInstalled = fs13.existsSync(skillDir);
|
|
5898
|
-
if (isInstalled) {
|
|
5899
|
-
try {
|
|
5900
|
-
fs13.rmSync(skillDir, { recursive: true, force: true });
|
|
5901
|
-
removeSkillWrapper(name, binDir);
|
|
5902
|
-
removeSkillEntry(name);
|
|
5903
|
-
removeSkillPolicy(name);
|
|
5904
|
-
removeFromApprovedList(name);
|
|
5905
|
-
console.log(`[Skills] Disabled marketplace skill: ${name}`);
|
|
5906
|
-
return reply.send({ success: true, action: "disabled", name });
|
|
5907
|
-
} catch (err) {
|
|
5908
|
-
console.error("[Skills] Disable failed:", err.message);
|
|
5909
|
-
return reply.code(500).send({ error: `Disable failed: ${err.message}` });
|
|
5910
|
-
}
|
|
5911
|
-
} else {
|
|
5912
|
-
const meta = getDownloadedSkillMeta(name);
|
|
5913
|
-
if (!meta) {
|
|
5914
|
-
return reply.code(404).send({ error: "Skill not found in download cache" });
|
|
5915
|
-
}
|
|
5916
|
-
const files = getDownloadedSkillFiles(name);
|
|
5917
|
-
if (files.length === 0) {
|
|
5918
|
-
return reply.code(404).send({ error: "No files in download cache for this skill" });
|
|
5919
|
-
}
|
|
5920
|
-
try {
|
|
5921
|
-
addToApprovedList(name, meta.author);
|
|
5922
|
-
fs13.mkdirSync(skillDir, { recursive: true });
|
|
5923
|
-
for (const file of files) {
|
|
5924
|
-
const filePath = path12.join(skillDir, file.name);
|
|
5925
|
-
fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
|
|
5926
|
-
fs13.writeFileSync(filePath, file.content, "utf-8");
|
|
5927
|
-
}
|
|
5928
|
-
try {
|
|
5929
|
-
execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
5930
|
-
execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
|
|
5931
|
-
} catch {
|
|
5932
|
-
}
|
|
5933
|
-
createSkillWrapper(name, binDir);
|
|
5934
|
-
addSkillEntry(name);
|
|
5935
|
-
addSkillPolicy(name);
|
|
5936
|
-
console.log(`[Skills] Enabled marketplace skill: ${name}`);
|
|
5937
|
-
return reply.send({ success: true, action: "enabled", name });
|
|
5938
|
-
} catch (err) {
|
|
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();
|
|
5939
5967
|
try {
|
|
5940
|
-
|
|
5941
|
-
|
|
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);
|
|
5942
5977
|
}
|
|
5943
|
-
|
|
5944
|
-
|
|
5978
|
+
} catch (error) {
|
|
5979
|
+
reject(new Error("Invalid response from broker"));
|
|
5945
5980
|
}
|
|
5946
|
-
console.error("[Skills] Enable failed:", err.message);
|
|
5947
|
-
return reply.code(500).send({ error: `Enable failed: ${err.message}` });
|
|
5948
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}`);
|
|
5949
6013
|
}
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
async (request, reply) => {
|
|
5956
|
-
const { name, files, publisher } = request.body ?? {};
|
|
5957
|
-
if (!name || typeof name !== "string") {
|
|
5958
|
-
return reply.code(400).send({ error: "Skill name is required" });
|
|
5959
|
-
}
|
|
5960
|
-
if (!Array.isArray(files) || files.length === 0) {
|
|
5961
|
-
return reply.code(400).send({ error: "Files array is required" });
|
|
5962
|
-
}
|
|
5963
|
-
const combinedContent = files.map((f) => f.content).join("\n");
|
|
5964
|
-
const skillMdFile = files.find((f) => f.name === "SKILL.md");
|
|
5965
|
-
let metadata;
|
|
5966
|
-
if (skillMdFile) {
|
|
5967
|
-
const parsed = parseSkillMd(skillMdFile.content);
|
|
5968
|
-
metadata = parsed?.metadata;
|
|
5969
|
-
}
|
|
5970
|
-
const analysis = analyzeSkill(name, combinedContent, metadata);
|
|
5971
|
-
if (analysis.vulnerability?.level === "critical") {
|
|
5972
|
-
return reply.code(400).send({ error: "Critical vulnerability detected", analysis });
|
|
5973
|
-
}
|
|
5974
|
-
const skillsDir2 = getSkillsDir();
|
|
5975
|
-
if (!skillsDir2) {
|
|
5976
|
-
return reply.code(500).send({ error: "Skills directory not configured" });
|
|
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;
|
|
5977
6019
|
}
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
addToApprovedList(name, publisher);
|
|
5984
|
-
fs13.mkdirSync(skillDir, { recursive: true });
|
|
5985
|
-
for (const file of files) {
|
|
5986
|
-
const filePath = path12.join(skillDir, file.name);
|
|
5987
|
-
fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
|
|
5988
|
-
fs13.writeFileSync(filePath, file.content, "utf-8");
|
|
5989
|
-
}
|
|
5990
|
-
try {
|
|
5991
|
-
execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
5992
|
-
execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
|
|
5993
|
-
} catch {
|
|
5994
|
-
}
|
|
5995
|
-
createSkillWrapper(name, binDir);
|
|
5996
|
-
addSkillPolicy(name);
|
|
5997
|
-
return reply.send({ success: true, name, analysis });
|
|
5998
|
-
} catch (err) {
|
|
5999
|
-
try {
|
|
6000
|
-
if (fs13.existsSync(skillDir)) {
|
|
6001
|
-
fs13.rmSync(skillDir, { recursive: true, force: true });
|
|
6002
|
-
}
|
|
6003
|
-
removeFromApprovedList(name);
|
|
6004
|
-
} catch {
|
|
6005
|
-
}
|
|
6006
|
-
console.error("[Skills] Install failed:", err.message);
|
|
6007
|
-
return reply.code(500).send({
|
|
6008
|
-
error: `Installation failed: ${err.message}`
|
|
6009
|
-
});
|
|
6020
|
+
return jsonResponse.result;
|
|
6021
|
+
} catch (error) {
|
|
6022
|
+
clearTimeout(timeoutId);
|
|
6023
|
+
if (error.name === "AbortError") {
|
|
6024
|
+
throw new Error("Request timeout");
|
|
6010
6025
|
}
|
|
6026
|
+
throw error;
|
|
6011
6027
|
}
|
|
6012
|
-
|
|
6013
|
-
}
|
|
6028
|
+
}
|
|
6029
|
+
};
|
|
6014
6030
|
|
|
6015
|
-
// libs/shield-daemon/src/
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
6029
|
-
function loadConfig2() {
|
|
6030
|
-
if (!fs14.existsSync(ALLOWED_COMMANDS_PATH2)) {
|
|
6031
|
-
return { version: "1.0.0", commands: [] };
|
|
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
|
+
});
|
|
6032
6044
|
}
|
|
6045
|
+
return brokerClient;
|
|
6046
|
+
}
|
|
6047
|
+
async function isBrokerAvailable() {
|
|
6033
6048
|
try {
|
|
6034
|
-
const
|
|
6035
|
-
return
|
|
6049
|
+
const client = getBrokerClient();
|
|
6050
|
+
return await client.isAvailable();
|
|
6036
6051
|
} catch {
|
|
6037
|
-
return
|
|
6052
|
+
return false;
|
|
6038
6053
|
}
|
|
6039
6054
|
}
|
|
6040
|
-
function
|
|
6041
|
-
const
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
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;
|
|
6046
6070
|
}
|
|
6047
|
-
function
|
|
6048
|
-
const
|
|
6049
|
-
const
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
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;
|
|
6069
6094
|
}
|
|
6095
|
+
} catch {
|
|
6070
6096
|
}
|
|
6071
|
-
return
|
|
6097
|
+
return null;
|
|
6072
6098
|
}
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
const
|
|
6076
|
-
if (
|
|
6077
|
-
|
|
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
|
+
}
|
|
6078
6125
|
}
|
|
6079
|
-
const
|
|
6080
|
-
|
|
6081
|
-
|
|
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 });
|
|
6082
6166
|
});
|
|
6083
|
-
app.get("/
|
|
6084
|
-
const
|
|
6085
|
-
return {
|
|
6086
|
-
success: true,
|
|
6087
|
-
data: {
|
|
6088
|
-
commands: config.commands
|
|
6089
|
-
}
|
|
6090
|
-
};
|
|
6167
|
+
app.get("/skills/quarantined", async (_request, reply) => {
|
|
6168
|
+
const quarantined = listQuarantined();
|
|
6169
|
+
return reply.send({ quarantined });
|
|
6091
6170
|
});
|
|
6092
|
-
app.
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
error:
|
|
6098
|
-
|
|
6099
|
-
|
|
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 {
|
|
6100
6212
|
}
|
|
6101
|
-
}
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
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 {
|
|
6109
6229
|
}
|
|
6110
|
-
}
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
code: "INVALID_PATH",
|
|
6118
|
-
message: `Path must be absolute: ${p}`
|
|
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);
|
|
6119
6237
|
}
|
|
6120
|
-
}
|
|
6238
|
+
} catch {
|
|
6239
|
+
}
|
|
6121
6240
|
}
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
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
|
|
6131
6253
|
}
|
|
6132
|
-
};
|
|
6254
|
+
});
|
|
6133
6255
|
}
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
code: "NOT_FOUND",
|
|
6157
|
-
message: `Command '${name}' not found in dynamic allowlist`
|
|
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
|
+
}
|
|
6158
6278
|
}
|
|
6159
|
-
};
|
|
6160
|
-
}
|
|
6161
|
-
config.commands.splice(index, 1);
|
|
6162
|
-
saveConfig2(config);
|
|
6163
|
-
return {
|
|
6164
|
-
success: true,
|
|
6165
|
-
data: { removed: name }
|
|
6166
|
-
};
|
|
6167
|
-
});
|
|
6168
|
-
}
|
|
6169
|
-
|
|
6170
|
-
// libs/shield-daemon/src/routes/discovery.ts
|
|
6171
|
-
import { scanDiscovery } from "@agenshield/sandbox";
|
|
6172
|
-
var CACHE_TTL = 6e4;
|
|
6173
|
-
var cache2 = null;
|
|
6174
|
-
async function discoveryRoutes(app) {
|
|
6175
|
-
app.get("/discovery/scan", async (request) => {
|
|
6176
|
-
const refresh = request.query.refresh === "true";
|
|
6177
|
-
const scanSkills2 = request.query.scanSkills !== "false";
|
|
6178
|
-
const now = Date.now();
|
|
6179
|
-
if (!refresh && cache2 && now - cache2.cachedAt < CACHE_TTL) {
|
|
6180
|
-
return { success: true, data: cache2.result };
|
|
6279
|
+
});
|
|
6181
6280
|
}
|
|
6182
|
-
|
|
6183
|
-
const result = scanDiscovery({
|
|
6184
|
-
agentHome,
|
|
6185
|
-
workspaceDir: agentHome ? `${agentHome}/workspace` : void 0,
|
|
6186
|
-
scanSkills: scanSkills2 && !!agentHome
|
|
6187
|
-
});
|
|
6188
|
-
cache2 = { result, cachedAt: Date.now() };
|
|
6189
|
-
return { success: true, data: result };
|
|
6190
|
-
});
|
|
6191
|
-
}
|
|
6192
|
-
|
|
6193
|
-
// libs/shield-daemon/src/routes/auth.ts
|
|
6194
|
-
import {
|
|
6195
|
-
UnlockRequestSchema,
|
|
6196
|
-
SetupPasscodeRequestSchema,
|
|
6197
|
-
ChangePasscodeRequestSchema
|
|
6198
|
-
} from "@agenshield/ipc";
|
|
6199
|
-
async function authRoutes(app) {
|
|
6200
|
-
app.get("/auth/status", async () => {
|
|
6201
|
-
const passcodeSet = await isPasscodeSet();
|
|
6202
|
-
const protectionEnabled = isProtectionEnabled();
|
|
6203
|
-
const allowAnonymousReadOnly = isAnonymousReadOnlyAllowed();
|
|
6204
|
-
const lockoutStatus = isLockedOut();
|
|
6205
|
-
return {
|
|
6206
|
-
passcodeSet,
|
|
6207
|
-
protectionEnabled,
|
|
6208
|
-
allowAnonymousReadOnly,
|
|
6209
|
-
lockedOut: lockoutStatus.locked,
|
|
6210
|
-
lockedUntil: lockoutStatus.lockedUntil
|
|
6211
|
-
};
|
|
6212
|
-
});
|
|
6281
|
+
);
|
|
6213
6282
|
app.post(
|
|
6214
|
-
"/
|
|
6283
|
+
"/skills/:name/approve",
|
|
6215
6284
|
async (request, reply) => {
|
|
6216
|
-
const
|
|
6217
|
-
if (!
|
|
6218
|
-
reply.code(400);
|
|
6219
|
-
return {
|
|
6220
|
-
success: false,
|
|
6221
|
-
error: "Invalid request: " + parseResult.error.message
|
|
6222
|
-
};
|
|
6223
|
-
}
|
|
6224
|
-
const { passcode } = parseResult.data;
|
|
6225
|
-
const lockoutStatus = isLockedOut();
|
|
6226
|
-
if (lockoutStatus.locked) {
|
|
6227
|
-
reply.code(429);
|
|
6228
|
-
return {
|
|
6229
|
-
success: false,
|
|
6230
|
-
error: "Too many failed attempts. Try again later.",
|
|
6231
|
-
remainingAttempts: 0
|
|
6232
|
-
};
|
|
6233
|
-
}
|
|
6234
|
-
const passcodeSet = await isPasscodeSet();
|
|
6235
|
-
if (!passcodeSet) {
|
|
6236
|
-
reply.code(400);
|
|
6237
|
-
return {
|
|
6238
|
-
success: false,
|
|
6239
|
-
error: "Passcode not configured. Use /auth/setup first."
|
|
6240
|
-
};
|
|
6285
|
+
const { name } = request.params;
|
|
6286
|
+
if (!name || typeof name !== "string") {
|
|
6287
|
+
return reply.code(400).send({ error: "Skill name is required" });
|
|
6241
6288
|
}
|
|
6242
|
-
const
|
|
6243
|
-
if (!
|
|
6244
|
-
|
|
6245
|
-
reply.code(401);
|
|
6246
|
-
return {
|
|
6247
|
-
success: false,
|
|
6248
|
-
error: "Invalid passcode",
|
|
6249
|
-
remainingAttempts
|
|
6250
|
-
};
|
|
6289
|
+
const result = approveSkill(name);
|
|
6290
|
+
if (!result.success) {
|
|
6291
|
+
return reply.code(404).send({ error: result.error });
|
|
6251
6292
|
}
|
|
6252
|
-
|
|
6253
|
-
const sessionManager = getSessionManager();
|
|
6254
|
-
const session = sessionManager.createSession();
|
|
6255
|
-
return {
|
|
6256
|
-
success: true,
|
|
6257
|
-
token: session.token,
|
|
6258
|
-
expiresAt: session.expiresAt
|
|
6259
|
-
};
|
|
6293
|
+
return reply.send({ success: true, message: `Skill "${name}" approved` });
|
|
6260
6294
|
}
|
|
6261
6295
|
);
|
|
6262
|
-
app.
|
|
6263
|
-
"/
|
|
6296
|
+
app.delete(
|
|
6297
|
+
"/skills/:name",
|
|
6264
6298
|
async (request, reply) => {
|
|
6265
|
-
const
|
|
6266
|
-
if (!
|
|
6267
|
-
reply.code(400);
|
|
6268
|
-
return { success: false };
|
|
6299
|
+
const { name } = request.params;
|
|
6300
|
+
if (!name || typeof name !== "string") {
|
|
6301
|
+
return reply.code(400).send({ error: "Skill name is required" });
|
|
6269
6302
|
}
|
|
6270
|
-
const
|
|
6271
|
-
|
|
6272
|
-
|
|
6303
|
+
const result = rejectSkill(name);
|
|
6304
|
+
if (!result.success) {
|
|
6305
|
+
return reply.code(404).send({ error: result.error });
|
|
6306
|
+
}
|
|
6307
|
+
return reply.send({ success: true, message: `Skill "${name}" rejected and deleted` });
|
|
6273
6308
|
}
|
|
6274
6309
|
);
|
|
6275
6310
|
app.post(
|
|
6276
|
-
"/
|
|
6311
|
+
"/skills/:name/revoke",
|
|
6277
6312
|
async (request, reply) => {
|
|
6278
|
-
const
|
|
6279
|
-
if (!
|
|
6280
|
-
reply.code(400);
|
|
6281
|
-
return {
|
|
6282
|
-
success: false,
|
|
6283
|
-
error: "Invalid request: " + parseResult.error.message
|
|
6284
|
-
};
|
|
6285
|
-
}
|
|
6286
|
-
const { passcode, enableProtection = true } = parseResult.data;
|
|
6287
|
-
const alreadySet = await isPasscodeSet();
|
|
6288
|
-
if (alreadySet) {
|
|
6289
|
-
reply.code(409);
|
|
6290
|
-
return {
|
|
6291
|
-
success: false,
|
|
6292
|
-
error: "Passcode already configured. Use /auth/change to update."
|
|
6293
|
-
};
|
|
6313
|
+
const { name } = request.params;
|
|
6314
|
+
if (!name || typeof name !== "string") {
|
|
6315
|
+
return reply.code(400).send({ error: "Skill name is required" });
|
|
6294
6316
|
}
|
|
6295
|
-
|
|
6296
|
-
if (
|
|
6297
|
-
|
|
6317
|
+
const result = revokeSkill(name);
|
|
6318
|
+
if (!result.success) {
|
|
6319
|
+
return reply.code(500).send({ error: result.error });
|
|
6298
6320
|
}
|
|
6299
|
-
return { success: true };
|
|
6321
|
+
return reply.send({ success: true, message: `Skill "${name}" approval revoked` });
|
|
6300
6322
|
}
|
|
6301
6323
|
);
|
|
6302
|
-
app.
|
|
6303
|
-
"/
|
|
6324
|
+
app.put(
|
|
6325
|
+
"/skills/:name/toggle",
|
|
6304
6326
|
async (request, reply) => {
|
|
6305
|
-
const
|
|
6306
|
-
if (!
|
|
6307
|
-
reply.code(400);
|
|
6308
|
-
return {
|
|
6309
|
-
success: false,
|
|
6310
|
-
error: "Invalid request: " + parseResult.error.message
|
|
6311
|
-
};
|
|
6327
|
+
const { name } = request.params;
|
|
6328
|
+
if (!name || typeof name !== "string") {
|
|
6329
|
+
return reply.code(400).send({ error: "Skill name is required" });
|
|
6312
6330
|
}
|
|
6313
|
-
const
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
reply.code(400);
|
|
6317
|
-
return {
|
|
6318
|
-
success: false,
|
|
6319
|
-
error: "Passcode not configured. Use /auth/setup first."
|
|
6320
|
-
};
|
|
6331
|
+
const skillsDir2 = getSkillsDir();
|
|
6332
|
+
if (!skillsDir2) {
|
|
6333
|
+
return reply.code(500).send({ error: "Skills directory not configured" });
|
|
6321
6334
|
}
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6335
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
6336
|
+
const binDir = path12.join(agentHome, "bin");
|
|
6337
|
+
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield";
|
|
6338
|
+
const skillDir = path12.join(skillsDir2, name);
|
|
6339
|
+
const isInstalled = fs13.existsSync(skillDir);
|
|
6340
|
+
if (isInstalled) {
|
|
6341
|
+
try {
|
|
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
|
+
}
|
|
6352
|
+
removeSkillEntry(name);
|
|
6353
|
+
removeSkillPolicy(name);
|
|
6354
|
+
removeFromApprovedList(name);
|
|
6355
|
+
console.log(`[Skills] Disabled marketplace skill: ${name}`);
|
|
6356
|
+
return reply.send({ success: true, action: "disabled", name });
|
|
6357
|
+
} catch (err) {
|
|
6358
|
+
console.error("[Skills] Disable failed:", err.message);
|
|
6359
|
+
return reply.code(500).send({ error: `Disable failed: ${err.message}` });
|
|
6329
6360
|
}
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
reply.code(
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
};
|
|
6361
|
+
} else {
|
|
6362
|
+
const meta = getDownloadedSkillMeta(name);
|
|
6363
|
+
if (!meta) {
|
|
6364
|
+
return reply.code(404).send({ error: "Skill not found in download cache" });
|
|
6365
|
+
}
|
|
6366
|
+
const files = getDownloadedSkillFiles(name);
|
|
6367
|
+
if (files.length === 0) {
|
|
6368
|
+
return reply.code(404).send({ error: "No files in download cache for this skill" });
|
|
6369
|
+
}
|
|
6370
|
+
try {
|
|
6371
|
+
addToApprovedList(name, meta.author);
|
|
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);
|
|
6395
|
+
}
|
|
6396
|
+
addSkillEntry(name);
|
|
6397
|
+
addSkillPolicy(name);
|
|
6398
|
+
console.log(`[Skills] Enabled marketplace skill: ${name}`);
|
|
6399
|
+
return reply.send({ success: true, action: "enabled", name });
|
|
6400
|
+
} catch (err) {
|
|
6401
|
+
try {
|
|
6402
|
+
if (fs13.existsSync(skillDir)) {
|
|
6403
|
+
fs13.rmSync(skillDir, { recursive: true, force: true });
|
|
6404
|
+
}
|
|
6405
|
+
removeFromApprovedList(name);
|
|
6406
|
+
} catch {
|
|
6407
|
+
}
|
|
6408
|
+
console.error("[Skills] Enable failed:", err.message);
|
|
6409
|
+
return reply.code(500).send({ error: `Enable failed: ${err.message}` });
|
|
6338
6410
|
}
|
|
6339
|
-
clearFailedAttempts();
|
|
6340
6411
|
}
|
|
6341
|
-
await setPasscode(newPasscode);
|
|
6342
|
-
const sessionManager = getSessionManager();
|
|
6343
|
-
sessionManager.clearAllSessions();
|
|
6344
|
-
return { success: true };
|
|
6345
6412
|
}
|
|
6346
6413
|
);
|
|
6347
6414
|
app.post(
|
|
6348
|
-
"/
|
|
6415
|
+
"/skills/install",
|
|
6416
|
+
{ preHandler: [requireAuth] },
|
|
6349
6417
|
async (request, reply) => {
|
|
6350
|
-
const
|
|
6351
|
-
if (!
|
|
6352
|
-
reply.code(
|
|
6353
|
-
return {
|
|
6354
|
-
success: false,
|
|
6355
|
-
error: "No token provided"
|
|
6356
|
-
};
|
|
6418
|
+
const { name, files, publisher } = request.body ?? {};
|
|
6419
|
+
if (!name || typeof name !== "string") {
|
|
6420
|
+
return reply.code(400).send({ error: "Skill name is required" });
|
|
6357
6421
|
}
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
if (!refreshed) {
|
|
6361
|
-
reply.code(401);
|
|
6362
|
-
return {
|
|
6363
|
-
success: false,
|
|
6364
|
-
error: "Invalid or expired token"
|
|
6365
|
-
};
|
|
6422
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
6423
|
+
return reply.code(400).send({ error: "Files array is required" });
|
|
6366
6424
|
}
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
);
|
|
6374
|
-
app.post(
|
|
6375
|
-
"/auth/enable",
|
|
6376
|
-
async (request, reply) => {
|
|
6377
|
-
const passcodeSet = await isPasscodeSet();
|
|
6378
|
-
if (!passcodeSet) {
|
|
6379
|
-
reply.code(400);
|
|
6380
|
-
return {
|
|
6381
|
-
success: false,
|
|
6382
|
-
error: "Passcode not configured. Use /auth/setup first."
|
|
6383
|
-
};
|
|
6425
|
+
const combinedContent = files.map((f) => f.content).join("\n");
|
|
6426
|
+
const skillMdFile = files.find((f) => f.name === "SKILL.md");
|
|
6427
|
+
let metadata;
|
|
6428
|
+
if (skillMdFile) {
|
|
6429
|
+
const parsed = parseSkillMd(skillMdFile.content);
|
|
6430
|
+
metadata = parsed?.metadata;
|
|
6384
6431
|
}
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
);
|
|
6389
|
-
app.post(
|
|
6390
|
-
"/auth/disable",
|
|
6391
|
-
async (request, reply) => {
|
|
6392
|
-
if (!isRunningAsRoot()) {
|
|
6393
|
-
const token = extractToken(request);
|
|
6394
|
-
if (!token) {
|
|
6395
|
-
reply.code(401);
|
|
6396
|
-
return {
|
|
6397
|
-
success: false,
|
|
6398
|
-
error: "Authentication required to disable protection"
|
|
6399
|
-
};
|
|
6400
|
-
}
|
|
6401
|
-
const sessionManager = getSessionManager();
|
|
6402
|
-
const session = sessionManager.validateSession(token);
|
|
6403
|
-
if (!session) {
|
|
6404
|
-
reply.code(401);
|
|
6405
|
-
return {
|
|
6406
|
-
success: false,
|
|
6407
|
-
error: "Invalid or expired token"
|
|
6408
|
-
};
|
|
6409
|
-
}
|
|
6432
|
+
const analysis = analyzeSkill(name, combinedContent, metadata);
|
|
6433
|
+
if (analysis.vulnerability?.level === "critical") {
|
|
6434
|
+
return reply.code(400).send({ error: "Critical vulnerability detected", analysis });
|
|
6410
6435
|
}
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6436
|
+
const skillsDir2 = getSkillsDir();
|
|
6437
|
+
if (!skillsDir2) {
|
|
6438
|
+
return reply.code(500).send({ error: "Skills directory not configured" });
|
|
6439
|
+
}
|
|
6440
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
6441
|
+
const binDir = path12.join(agentHome, "bin");
|
|
6442
|
+
const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield";
|
|
6443
|
+
const skillDir = path12.join(skillsDir2, name);
|
|
6444
|
+
try {
|
|
6445
|
+
addToApprovedList(name, publisher);
|
|
6446
|
+
fs13.mkdirSync(skillDir, { recursive: true });
|
|
6447
|
+
for (const file of files) {
|
|
6448
|
+
const filePath = path12.join(skillDir, file.name);
|
|
6449
|
+
fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
|
|
6450
|
+
fs13.writeFileSync(filePath, file.content, "utf-8");
|
|
6426
6451
|
}
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
return {
|
|
6432
|
-
success: false,
|
|
6433
|
-
error: "Invalid or expired token"
|
|
6434
|
-
};
|
|
6452
|
+
try {
|
|
6453
|
+
execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
|
|
6454
|
+
execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
|
|
6455
|
+
} catch {
|
|
6435
6456
|
}
|
|
6457
|
+
createSkillWrapper(name, binDir);
|
|
6458
|
+
addSkillPolicy(name);
|
|
6459
|
+
return reply.send({ success: true, name, analysis });
|
|
6460
|
+
} catch (err) {
|
|
6461
|
+
try {
|
|
6462
|
+
if (fs13.existsSync(skillDir)) {
|
|
6463
|
+
fs13.rmSync(skillDir, { recursive: true, force: true });
|
|
6464
|
+
}
|
|
6465
|
+
removeFromApprovedList(name);
|
|
6466
|
+
} catch {
|
|
6467
|
+
}
|
|
6468
|
+
console.error("[Skills] Install failed:", err.message);
|
|
6469
|
+
return reply.code(500).send({
|
|
6470
|
+
error: `Installation failed: ${err.message}`
|
|
6471
|
+
});
|
|
6436
6472
|
}
|
|
6437
|
-
const { allowed } = request.body;
|
|
6438
|
-
if (typeof allowed !== "boolean") {
|
|
6439
|
-
reply.code(400);
|
|
6440
|
-
return {
|
|
6441
|
-
success: false,
|
|
6442
|
-
error: 'Invalid request: "allowed" must be a boolean'
|
|
6443
|
-
};
|
|
6444
|
-
}
|
|
6445
|
-
setAnonymousReadOnly(allowed);
|
|
6446
|
-
return { success: true, allowAnonymousReadOnly: allowed };
|
|
6447
6473
|
}
|
|
6448
6474
|
);
|
|
6449
6475
|
}
|
|
6450
6476
|
|
|
6451
|
-
// libs/shield-daemon/src/routes/
|
|
6452
|
-
import
|
|
6453
|
-
import
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6477
|
+
// libs/shield-daemon/src/routes/exec.ts
|
|
6478
|
+
import * as fs14 from "node:fs";
|
|
6479
|
+
import * as path13 from "node:path";
|
|
6480
|
+
var ALLOWED_COMMANDS_PATH2 = "/opt/agenshield/config/allowed-commands.json";
|
|
6481
|
+
var BIN_DIRS = [
|
|
6482
|
+
"/usr/bin",
|
|
6483
|
+
"/usr/local/bin",
|
|
6484
|
+
"/opt/homebrew/bin",
|
|
6485
|
+
"/usr/sbin",
|
|
6486
|
+
"/usr/local/sbin"
|
|
6487
|
+
];
|
|
6488
|
+
var binCache = null;
|
|
6489
|
+
var BIN_CACHE_TTL = 6e4;
|
|
6490
|
+
var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
6491
|
+
function loadConfig2() {
|
|
6492
|
+
if (!fs14.existsSync(ALLOWED_COMMANDS_PATH2)) {
|
|
6493
|
+
return { version: "1.0.0", commands: [] };
|
|
6494
|
+
}
|
|
6495
|
+
try {
|
|
6496
|
+
const content = fs14.readFileSync(ALLOWED_COMMANDS_PATH2, "utf-8");
|
|
6497
|
+
return JSON.parse(content);
|
|
6498
|
+
} catch {
|
|
6499
|
+
return { version: "1.0.0", commands: [] };
|
|
6500
|
+
}
|
|
6457
6501
|
}
|
|
6458
|
-
function
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
createdAt: secret.createdAt
|
|
6465
|
-
};
|
|
6502
|
+
function saveConfig2(config) {
|
|
6503
|
+
const dir = path13.dirname(ALLOWED_COMMANDS_PATH2);
|
|
6504
|
+
if (!fs14.existsSync(dir)) {
|
|
6505
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
6506
|
+
}
|
|
6507
|
+
fs14.writeFileSync(ALLOWED_COMMANDS_PATH2, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6466
6508
|
}
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6509
|
+
function scanSystemBins() {
|
|
6510
|
+
const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
6511
|
+
const allDirs = [.../* @__PURE__ */ new Set([...BIN_DIRS, ...pathDirs])];
|
|
6512
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6513
|
+
const results = [];
|
|
6514
|
+
for (const dir of allDirs) {
|
|
6515
|
+
try {
|
|
6516
|
+
if (!fs14.existsSync(dir)) continue;
|
|
6517
|
+
const entries = fs14.readdirSync(dir);
|
|
6518
|
+
for (const entry of entries) {
|
|
6519
|
+
if (seen.has(entry)) continue;
|
|
6520
|
+
const fullPath = path13.join(dir, entry);
|
|
6521
|
+
try {
|
|
6522
|
+
const stat = fs14.statSync(fullPath);
|
|
6523
|
+
if (stat.isFile() && (stat.mode & 73) !== 0) {
|
|
6524
|
+
seen.add(entry);
|
|
6525
|
+
results.push({ name: entry, path: fullPath });
|
|
6526
|
+
}
|
|
6527
|
+
} catch {
|
|
6528
|
+
}
|
|
6478
6529
|
}
|
|
6530
|
+
} catch {
|
|
6479
6531
|
}
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6532
|
+
}
|
|
6533
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
6534
|
+
}
|
|
6535
|
+
async function execRoutes(app) {
|
|
6536
|
+
app.get("/exec/system-bins", async () => {
|
|
6537
|
+
const now = Date.now();
|
|
6538
|
+
if (binCache && now - binCache.cachedAt < BIN_CACHE_TTL) {
|
|
6539
|
+
return { success: true, data: { bins: binCache.bins } };
|
|
6485
6540
|
}
|
|
6486
|
-
|
|
6541
|
+
const bins = scanSystemBins();
|
|
6542
|
+
binCache = { bins, cachedAt: now };
|
|
6543
|
+
return { success: true, data: { bins } };
|
|
6487
6544
|
});
|
|
6488
|
-
app.
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6545
|
+
app.get("/exec/allowed-commands", async () => {
|
|
6546
|
+
const config = loadConfig2();
|
|
6547
|
+
return {
|
|
6548
|
+
success: true,
|
|
6549
|
+
data: {
|
|
6550
|
+
commands: config.commands
|
|
6494
6551
|
}
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6552
|
+
};
|
|
6553
|
+
});
|
|
6554
|
+
app.post("/exec/allowed-commands", async (request) => {
|
|
6555
|
+
const { name, paths, category } = request.body;
|
|
6556
|
+
if (!name || !VALID_NAME.test(name)) {
|
|
6557
|
+
return {
|
|
6558
|
+
success: false,
|
|
6559
|
+
error: {
|
|
6560
|
+
code: "INVALID_NAME",
|
|
6561
|
+
message: "Command name must be alphanumeric with hyphens/underscores only"
|
|
6562
|
+
}
|
|
6503
6563
|
};
|
|
6504
|
-
secrets.push(newSecret);
|
|
6505
|
-
await vault.set("secrets", secrets);
|
|
6506
|
-
return { data: toMasked(newSecret) };
|
|
6507
6564
|
}
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
const idx = secrets.findIndex((s) => s.id === id);
|
|
6517
|
-
if (idx === -1) return { success: false, error: "Secret not found" };
|
|
6518
|
-
secrets[idx].policyIds = policyIds ?? [];
|
|
6519
|
-
await vault.set("secrets", secrets);
|
|
6520
|
-
return { data: toMasked(secrets[idx]) };
|
|
6565
|
+
if (!paths || !Array.isArray(paths) || paths.length === 0) {
|
|
6566
|
+
return {
|
|
6567
|
+
success: false,
|
|
6568
|
+
error: {
|
|
6569
|
+
code: "INVALID_PATHS",
|
|
6570
|
+
message: "At least one absolute path is required"
|
|
6571
|
+
}
|
|
6572
|
+
};
|
|
6521
6573
|
}
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
return { success: false, error: "Secret not found" };
|
|
6574
|
+
for (const p of paths) {
|
|
6575
|
+
if (!path13.isAbsolute(p)) {
|
|
6576
|
+
return {
|
|
6577
|
+
success: false,
|
|
6578
|
+
error: {
|
|
6579
|
+
code: "INVALID_PATH",
|
|
6580
|
+
message: `Path must be absolute: ${p}`
|
|
6581
|
+
}
|
|
6582
|
+
};
|
|
6532
6583
|
}
|
|
6533
|
-
await vault.set("secrets", filtered);
|
|
6534
|
-
return { deleted: true };
|
|
6535
6584
|
}
|
|
6536
|
-
|
|
6585
|
+
const config = loadConfig2();
|
|
6586
|
+
const existing = config.commands.find((c) => c.name === name);
|
|
6587
|
+
if (existing) {
|
|
6588
|
+
return {
|
|
6589
|
+
success: false,
|
|
6590
|
+
error: {
|
|
6591
|
+
code: "ALREADY_EXISTS",
|
|
6592
|
+
message: `Command '${name}' already exists in dynamic allowlist`
|
|
6593
|
+
}
|
|
6594
|
+
};
|
|
6595
|
+
}
|
|
6596
|
+
const newCommand = {
|
|
6597
|
+
name,
|
|
6598
|
+
paths,
|
|
6599
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6600
|
+
addedBy: "admin",
|
|
6601
|
+
...category ? { category } : {}
|
|
6602
|
+
};
|
|
6603
|
+
config.commands.push(newCommand);
|
|
6604
|
+
saveConfig2(config);
|
|
6605
|
+
return {
|
|
6606
|
+
success: true,
|
|
6607
|
+
data: newCommand
|
|
6608
|
+
};
|
|
6609
|
+
});
|
|
6610
|
+
app.delete("/exec/allowed-commands/:name", async (request) => {
|
|
6611
|
+
const { name } = request.params;
|
|
6612
|
+
const config = loadConfig2();
|
|
6613
|
+
const index = config.commands.findIndex((c) => c.name === name);
|
|
6614
|
+
if (index === -1) {
|
|
6615
|
+
return {
|
|
6616
|
+
success: false,
|
|
6617
|
+
error: {
|
|
6618
|
+
code: "NOT_FOUND",
|
|
6619
|
+
message: `Command '${name}' not found in dynamic allowlist`
|
|
6620
|
+
}
|
|
6621
|
+
};
|
|
6622
|
+
}
|
|
6623
|
+
config.commands.splice(index, 1);
|
|
6624
|
+
saveConfig2(config);
|
|
6625
|
+
return {
|
|
6626
|
+
success: true,
|
|
6627
|
+
data: { removed: name }
|
|
6628
|
+
};
|
|
6629
|
+
});
|
|
6537
6630
|
}
|
|
6538
6631
|
|
|
6539
|
-
// libs/shield-daemon/src/routes/
|
|
6540
|
-
import
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
constructor(options = {}) {
|
|
6557
|
-
this.socketPath = options.socketPath || "/var/run/agenshield/agenshield.sock";
|
|
6558
|
-
this.httpHost = options.httpHost || "localhost";
|
|
6559
|
-
this.httpPort = options.httpPort || 5201;
|
|
6560
|
-
this.timeout = options.timeout || 3e4;
|
|
6561
|
-
this.preferSocket = options.preferSocket ?? true;
|
|
6562
|
-
}
|
|
6563
|
-
/**
|
|
6564
|
-
* Make an HTTP request through the broker
|
|
6565
|
-
*/
|
|
6566
|
-
async httpRequest(params, options) {
|
|
6567
|
-
return this.request("http_request", params, options);
|
|
6568
|
-
}
|
|
6569
|
-
/**
|
|
6570
|
-
* Read a file through the broker
|
|
6571
|
-
*/
|
|
6572
|
-
async fileRead(params, options) {
|
|
6573
|
-
return this.request("file_read", params, options);
|
|
6574
|
-
}
|
|
6575
|
-
/**
|
|
6576
|
-
* Write a file through the broker
|
|
6577
|
-
*/
|
|
6578
|
-
async fileWrite(params, options) {
|
|
6579
|
-
return this.request("file_write", params, {
|
|
6580
|
-
...options,
|
|
6581
|
-
channel: "socket"
|
|
6582
|
-
// file_write only allowed via socket
|
|
6583
|
-
});
|
|
6584
|
-
}
|
|
6585
|
-
/**
|
|
6586
|
-
* List files through the broker
|
|
6587
|
-
*/
|
|
6588
|
-
async fileList(params, options) {
|
|
6589
|
-
return this.request("file_list", params, options);
|
|
6590
|
-
}
|
|
6591
|
-
/**
|
|
6592
|
-
* Execute a command through the broker
|
|
6593
|
-
*/
|
|
6594
|
-
async exec(params, options) {
|
|
6595
|
-
return this.request("exec", params, {
|
|
6596
|
-
...options,
|
|
6597
|
-
channel: "socket"
|
|
6598
|
-
// exec only allowed via socket
|
|
6599
|
-
});
|
|
6600
|
-
}
|
|
6601
|
-
/**
|
|
6602
|
-
* Open a URL through the broker
|
|
6603
|
-
*/
|
|
6604
|
-
async openUrl(params, options) {
|
|
6605
|
-
return this.request("open_url", params, options);
|
|
6606
|
-
}
|
|
6607
|
-
/**
|
|
6608
|
-
* Inject a secret through the broker
|
|
6609
|
-
*/
|
|
6610
|
-
async secretInject(params, options) {
|
|
6611
|
-
return this.request("secret_inject", params, {
|
|
6612
|
-
...options,
|
|
6613
|
-
channel: "socket"
|
|
6614
|
-
// secret_inject only allowed via socket
|
|
6615
|
-
});
|
|
6616
|
-
}
|
|
6617
|
-
/**
|
|
6618
|
-
* Ping the broker
|
|
6619
|
-
*/
|
|
6620
|
-
async ping(echo, options) {
|
|
6621
|
-
return this.request("ping", { echo }, options);
|
|
6622
|
-
}
|
|
6623
|
-
/**
|
|
6624
|
-
* Install a skill through the broker
|
|
6625
|
-
* Socket-only operation due to privileged file operations
|
|
6626
|
-
*/
|
|
6627
|
-
async skillInstall(params, options) {
|
|
6628
|
-
return this.request("skill_install", params, {
|
|
6629
|
-
...options,
|
|
6630
|
-
channel: "socket"
|
|
6631
|
-
// skill_install only allowed via socket
|
|
6632
|
-
});
|
|
6633
|
-
}
|
|
6634
|
-
/**
|
|
6635
|
-
* Uninstall a skill through the broker
|
|
6636
|
-
* Socket-only operation due to privileged file operations
|
|
6637
|
-
*/
|
|
6638
|
-
async skillUninstall(params, options) {
|
|
6639
|
-
return this.request("skill_uninstall", params, {
|
|
6640
|
-
...options,
|
|
6641
|
-
channel: "socket"
|
|
6642
|
-
// skill_uninstall only allowed via socket
|
|
6632
|
+
// libs/shield-daemon/src/routes/discovery.ts
|
|
6633
|
+
import { scanDiscovery } from "@agenshield/sandbox";
|
|
6634
|
+
var CACHE_TTL = 6e4;
|
|
6635
|
+
var cache2 = null;
|
|
6636
|
+
async function discoveryRoutes(app) {
|
|
6637
|
+
app.get("/discovery/scan", async (request) => {
|
|
6638
|
+
const refresh = request.query.refresh === "true";
|
|
6639
|
+
const scanSkills2 = request.query.scanSkills !== "false";
|
|
6640
|
+
const now = Date.now();
|
|
6641
|
+
if (!refresh && cache2 && now - cache2.cachedAt < CACHE_TTL) {
|
|
6642
|
+
return { success: true, data: cache2.result };
|
|
6643
|
+
}
|
|
6644
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || void 0;
|
|
6645
|
+
const result = scanDiscovery({
|
|
6646
|
+
agentHome,
|
|
6647
|
+
workspaceDir: agentHome ? `${agentHome}/workspace` : void 0,
|
|
6648
|
+
scanSkills: scanSkills2 && !!agentHome
|
|
6643
6649
|
});
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6650
|
+
cache2 = { result, cachedAt: Date.now() };
|
|
6651
|
+
return { success: true, data: result };
|
|
6652
|
+
});
|
|
6653
|
+
}
|
|
6654
|
+
|
|
6655
|
+
// libs/shield-daemon/src/routes/auth.ts
|
|
6656
|
+
import {
|
|
6657
|
+
UnlockRequestSchema,
|
|
6658
|
+
SetupPasscodeRequestSchema,
|
|
6659
|
+
ChangePasscodeRequestSchema
|
|
6660
|
+
} from "@agenshield/ipc";
|
|
6661
|
+
async function authRoutes(app) {
|
|
6662
|
+
app.get("/auth/status", async () => {
|
|
6663
|
+
const passcodeSet = await isPasscodeSet();
|
|
6664
|
+
const protectionEnabled = isProtectionEnabled();
|
|
6665
|
+
const allowAnonymousReadOnly = isAnonymousReadOnlyAllowed();
|
|
6666
|
+
const lockoutStatus = isLockedOut();
|
|
6667
|
+
return {
|
|
6668
|
+
passcodeSet,
|
|
6669
|
+
protectionEnabled,
|
|
6670
|
+
allowAnonymousReadOnly,
|
|
6671
|
+
lockedOut: lockoutStatus.locked,
|
|
6672
|
+
lockedUntil: lockoutStatus.lockedUntil
|
|
6673
|
+
};
|
|
6674
|
+
});
|
|
6675
|
+
app.post(
|
|
6676
|
+
"/auth/unlock",
|
|
6677
|
+
async (request, reply) => {
|
|
6678
|
+
const parseResult = UnlockRequestSchema.safeParse(request.body);
|
|
6679
|
+
if (!parseResult.success) {
|
|
6680
|
+
reply.code(400);
|
|
6681
|
+
return {
|
|
6682
|
+
success: false,
|
|
6683
|
+
error: "Invalid request: " + parseResult.error.message
|
|
6684
|
+
};
|
|
6685
|
+
}
|
|
6686
|
+
const { passcode } = parseResult.data;
|
|
6687
|
+
const lockoutStatus = isLockedOut();
|
|
6688
|
+
if (lockoutStatus.locked) {
|
|
6689
|
+
reply.code(429);
|
|
6690
|
+
return {
|
|
6691
|
+
success: false,
|
|
6692
|
+
error: "Too many failed attempts. Try again later.",
|
|
6693
|
+
remainingAttempts: 0
|
|
6694
|
+
};
|
|
6695
|
+
}
|
|
6696
|
+
const passcodeSet = await isPasscodeSet();
|
|
6697
|
+
if (!passcodeSet) {
|
|
6698
|
+
reply.code(400);
|
|
6699
|
+
return {
|
|
6700
|
+
success: false,
|
|
6701
|
+
error: "Passcode not configured. Use /auth/setup first."
|
|
6702
|
+
};
|
|
6703
|
+
}
|
|
6704
|
+
const valid = await checkPasscode(passcode);
|
|
6705
|
+
if (!valid) {
|
|
6706
|
+
const remainingAttempts = recordFailedAttempt();
|
|
6707
|
+
reply.code(401);
|
|
6708
|
+
return {
|
|
6709
|
+
success: false,
|
|
6710
|
+
error: "Invalid passcode",
|
|
6711
|
+
remainingAttempts
|
|
6712
|
+
};
|
|
6713
|
+
}
|
|
6714
|
+
clearFailedAttempts();
|
|
6715
|
+
const sessionManager = getSessionManager();
|
|
6716
|
+
const session = sessionManager.createSession();
|
|
6717
|
+
return {
|
|
6718
|
+
success: true,
|
|
6719
|
+
token: session.token,
|
|
6720
|
+
expiresAt: session.expiresAt
|
|
6721
|
+
};
|
|
6722
|
+
}
|
|
6723
|
+
);
|
|
6724
|
+
app.post(
|
|
6725
|
+
"/auth/lock",
|
|
6726
|
+
async (request, reply) => {
|
|
6727
|
+
const token = request.body?.token || extractToken(request);
|
|
6728
|
+
if (!token) {
|
|
6729
|
+
reply.code(400);
|
|
6730
|
+
return { success: false };
|
|
6731
|
+
}
|
|
6732
|
+
const sessionManager = getSessionManager();
|
|
6733
|
+
const invalidated = sessionManager.invalidateSession(token);
|
|
6734
|
+
return { success: invalidated };
|
|
6735
|
+
}
|
|
6736
|
+
);
|
|
6737
|
+
app.post(
|
|
6738
|
+
"/auth/setup",
|
|
6739
|
+
async (request, reply) => {
|
|
6740
|
+
const parseResult = SetupPasscodeRequestSchema.safeParse(request.body);
|
|
6741
|
+
if (!parseResult.success) {
|
|
6742
|
+
reply.code(400);
|
|
6743
|
+
return {
|
|
6744
|
+
success: false,
|
|
6745
|
+
error: "Invalid request: " + parseResult.error.message
|
|
6746
|
+
};
|
|
6747
|
+
}
|
|
6748
|
+
const { passcode, enableProtection = true } = parseResult.data;
|
|
6749
|
+
const alreadySet = await isPasscodeSet();
|
|
6750
|
+
if (alreadySet) {
|
|
6751
|
+
reply.code(409);
|
|
6752
|
+
return {
|
|
6753
|
+
success: false,
|
|
6754
|
+
error: "Passcode already configured. Use /auth/change to update."
|
|
6755
|
+
};
|
|
6756
|
+
}
|
|
6757
|
+
await setPasscode(passcode);
|
|
6758
|
+
if (enableProtection) {
|
|
6759
|
+
setProtectionEnabled(true);
|
|
6760
|
+
}
|
|
6761
|
+
return { success: true };
|
|
6654
6762
|
}
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6763
|
+
);
|
|
6764
|
+
app.post(
|
|
6765
|
+
"/auth/change",
|
|
6766
|
+
async (request, reply) => {
|
|
6767
|
+
const parseResult = ChangePasscodeRequestSchema.safeParse(request.body);
|
|
6768
|
+
if (!parseResult.success) {
|
|
6769
|
+
reply.code(400);
|
|
6770
|
+
return {
|
|
6771
|
+
success: false,
|
|
6772
|
+
error: "Invalid request: " + parseResult.error.message
|
|
6773
|
+
};
|
|
6774
|
+
}
|
|
6775
|
+
const { oldPasscode, newPasscode } = parseResult.data;
|
|
6776
|
+
const passcodeSet = await isPasscodeSet();
|
|
6777
|
+
if (!passcodeSet) {
|
|
6778
|
+
reply.code(400);
|
|
6779
|
+
return {
|
|
6780
|
+
success: false,
|
|
6781
|
+
error: "Passcode not configured. Use /auth/setup first."
|
|
6782
|
+
};
|
|
6783
|
+
}
|
|
6784
|
+
if (!isRunningAsRoot()) {
|
|
6785
|
+
if (!oldPasscode) {
|
|
6786
|
+
reply.code(400);
|
|
6787
|
+
return {
|
|
6788
|
+
success: false,
|
|
6789
|
+
error: "Old passcode required"
|
|
6790
|
+
};
|
|
6668
6791
|
}
|
|
6669
|
-
|
|
6792
|
+
const valid = await checkPasscode(oldPasscode);
|
|
6793
|
+
if (!valid) {
|
|
6794
|
+
const remainingAttempts = recordFailedAttempt();
|
|
6795
|
+
reply.code(401);
|
|
6796
|
+
return {
|
|
6797
|
+
success: false,
|
|
6798
|
+
error: "Invalid old passcode"
|
|
6799
|
+
};
|
|
6800
|
+
}
|
|
6801
|
+
clearFailedAttempts();
|
|
6670
6802
|
}
|
|
6671
|
-
|
|
6672
|
-
|
|
6803
|
+
await setPasscode(newPasscode);
|
|
6804
|
+
const sessionManager = getSessionManager();
|
|
6805
|
+
sessionManager.clearAllSessions();
|
|
6806
|
+
return { success: true };
|
|
6673
6807
|
}
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
socket.on("connect", () => {
|
|
6685
|
-
const request = {
|
|
6686
|
-
jsonrpc: "2.0",
|
|
6687
|
-
id,
|
|
6688
|
-
method,
|
|
6689
|
-
params
|
|
6808
|
+
);
|
|
6809
|
+
app.post(
|
|
6810
|
+
"/auth/refresh",
|
|
6811
|
+
async (request, reply) => {
|
|
6812
|
+
const token = extractToken(request);
|
|
6813
|
+
if (!token) {
|
|
6814
|
+
reply.code(401);
|
|
6815
|
+
return {
|
|
6816
|
+
success: false,
|
|
6817
|
+
error: "No token provided"
|
|
6690
6818
|
};
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6819
|
+
}
|
|
6820
|
+
const sessionManager = getSessionManager();
|
|
6821
|
+
const refreshed = sessionManager.refreshSession(token);
|
|
6822
|
+
if (!refreshed) {
|
|
6823
|
+
reply.code(401);
|
|
6824
|
+
return {
|
|
6825
|
+
success: false,
|
|
6826
|
+
error: "Invalid or expired token"
|
|
6827
|
+
};
|
|
6828
|
+
}
|
|
6829
|
+
return {
|
|
6830
|
+
success: true,
|
|
6831
|
+
token: refreshed.token,
|
|
6832
|
+
expiresAt: refreshed.expiresAt
|
|
6833
|
+
};
|
|
6834
|
+
}
|
|
6835
|
+
);
|
|
6836
|
+
app.post(
|
|
6837
|
+
"/auth/enable",
|
|
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
|
+
};
|
|
6846
|
+
}
|
|
6847
|
+
setProtectionEnabled(true);
|
|
6848
|
+
return { success: true };
|
|
6849
|
+
}
|
|
6850
|
+
);
|
|
6851
|
+
app.post(
|
|
6852
|
+
"/auth/disable",
|
|
6853
|
+
async (request, reply) => {
|
|
6854
|
+
if (!isRunningAsRoot()) {
|
|
6855
|
+
const token = extractToken(request);
|
|
6856
|
+
if (!token) {
|
|
6857
|
+
reply.code(401);
|
|
6858
|
+
return {
|
|
6859
|
+
success: false,
|
|
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"
|
|
6870
|
+
};
|
|
6717
6871
|
}
|
|
6718
|
-
});
|
|
6719
|
-
socket.on("error", (error) => {
|
|
6720
|
-
clearTimeout(timeoutId);
|
|
6721
|
-
reject(error);
|
|
6722
|
-
});
|
|
6723
|
-
});
|
|
6724
|
-
}
|
|
6725
|
-
/**
|
|
6726
|
-
* Make a request via HTTP
|
|
6727
|
-
*/
|
|
6728
|
-
async httpRequest_internal(method, params, timeout) {
|
|
6729
|
-
const url = `http://${this.httpHost}:${this.httpPort}/rpc`;
|
|
6730
|
-
const id = randomUUID3();
|
|
6731
|
-
const request = {
|
|
6732
|
-
jsonrpc: "2.0",
|
|
6733
|
-
id,
|
|
6734
|
-
method,
|
|
6735
|
-
params
|
|
6736
|
-
};
|
|
6737
|
-
const controller = new AbortController();
|
|
6738
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
6739
|
-
try {
|
|
6740
|
-
const response = await fetch(url, {
|
|
6741
|
-
method: "POST",
|
|
6742
|
-
headers: { "Content-Type": "application/json" },
|
|
6743
|
-
body: JSON.stringify(request),
|
|
6744
|
-
signal: controller.signal
|
|
6745
|
-
});
|
|
6746
|
-
clearTimeout(timeoutId);
|
|
6747
|
-
if (!response.ok) {
|
|
6748
|
-
throw new Error(`HTTP error: ${response.status}`);
|
|
6749
6872
|
}
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6873
|
+
setProtectionEnabled(false);
|
|
6874
|
+
return { success: true };
|
|
6875
|
+
}
|
|
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
|
+
};
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
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
|
+
};
|
|
6906
|
+
}
|
|
6907
|
+
setAnonymousReadOnly(allowed);
|
|
6908
|
+
return { success: true, allowAnonymousReadOnly: allowed };
|
|
6909
|
+
}
|
|
6910
|
+
);
|
|
6911
|
+
}
|
|
6912
|
+
|
|
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);
|
|
6919
|
+
}
|
|
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
|
+
};
|
|
6928
|
+
}
|
|
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) };
|
|
6934
|
+
});
|
|
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);
|
|
6755
6940
|
}
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
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);
|
|
6761
6946
|
}
|
|
6762
|
-
throw error;
|
|
6763
6947
|
}
|
|
6764
|
-
|
|
6765
|
-
};
|
|
6766
|
-
|
|
6767
|
-
// libs/shield-daemon/src/services/broker-bridge.ts
|
|
6768
|
-
var brokerClient = null;
|
|
6769
|
-
function getBrokerClient() {
|
|
6770
|
-
if (!brokerClient) {
|
|
6771
|
-
brokerClient = new BrokerClient({
|
|
6772
|
-
socketPath: process.env["AGENSHIELD_SOCKET"] || "/var/run/agenshield/agenshield.sock",
|
|
6773
|
-
httpHost: "localhost",
|
|
6774
|
-
httpPort: 5201,
|
|
6775
|
-
// Broker uses 5201, daemon uses 5200
|
|
6776
|
-
timeout: 6e4,
|
|
6777
|
-
// 60s timeout for file operations
|
|
6778
|
-
preferSocket: true
|
|
6779
|
-
});
|
|
6780
|
-
}
|
|
6781
|
-
return brokerClient;
|
|
6782
|
-
}
|
|
6783
|
-
async function isBrokerAvailable() {
|
|
6784
|
-
try {
|
|
6785
|
-
const client = getBrokerClient();
|
|
6786
|
-
return await client.isAvailable();
|
|
6787
|
-
} catch {
|
|
6788
|
-
return false;
|
|
6789
|
-
}
|
|
6790
|
-
}
|
|
6791
|
-
async function installSkillViaBroker(slug, files, options = {}) {
|
|
6792
|
-
const client = getBrokerClient();
|
|
6793
|
-
const brokerFiles = files.map((file) => ({
|
|
6794
|
-
name: file.name,
|
|
6795
|
-
content: file.content,
|
|
6796
|
-
base64: false
|
|
6797
|
-
}));
|
|
6798
|
-
const result = await client.skillInstall({
|
|
6799
|
-
slug,
|
|
6800
|
-
files: brokerFiles,
|
|
6801
|
-
createWrapper: options.createWrapper ?? true,
|
|
6802
|
-
agentHome: options.agentHome,
|
|
6803
|
-
socketGroup: options.socketGroup
|
|
6948
|
+
return { data: Array.from(names).sort((a, b) => a.localeCompare(b)) };
|
|
6804
6949
|
});
|
|
6805
|
-
|
|
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
|
+
);
|
|
6806
6999
|
}
|
|
6807
7000
|
|
|
6808
7001
|
// libs/shield-daemon/src/routes/marketplace.ts
|
|
7002
|
+
import * as fs15 from "node:fs";
|
|
7003
|
+
import * as path14 from "node:path";
|
|
6809
7004
|
async function marketplaceRoutes(app) {
|
|
6810
7005
|
app.get(
|
|
6811
7006
|
"/marketplace/search",
|
|
@@ -6841,6 +7036,10 @@ async function marketplaceRoutes(app) {
|
|
|
6841
7036
|
if (localMeta) {
|
|
6842
7037
|
const localFiles = getDownloadedSkillFiles(slug);
|
|
6843
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
|
+
}
|
|
6844
7043
|
const skill2 = {
|
|
6845
7044
|
name: localMeta.name,
|
|
6846
7045
|
slug: localMeta.slug,
|
|
@@ -6850,7 +7049,7 @@ async function marketplaceRoutes(app) {
|
|
|
6850
7049
|
installs: 0,
|
|
6851
7050
|
// Not stored locally
|
|
6852
7051
|
tags: localMeta.tags,
|
|
6853
|
-
readme
|
|
7052
|
+
readme,
|
|
6854
7053
|
files: localFiles
|
|
6855
7054
|
};
|
|
6856
7055
|
if (localMeta.analysis) {
|
|
@@ -7099,6 +7298,120 @@ async function fsRoutes(app) {
|
|
|
7099
7298
|
});
|
|
7100
7299
|
}
|
|
7101
7300
|
|
|
7301
|
+
// libs/shield-daemon/src/services/activity-log.ts
|
|
7302
|
+
import * as fs17 from "node:fs";
|
|
7303
|
+
import * as path16 from "node:path";
|
|
7304
|
+
var ACTIVITY_FILE = "activity.jsonl";
|
|
7305
|
+
var MAX_SIZE_BYTES = 100 * 1024 * 1024;
|
|
7306
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7307
|
+
var PRUNE_INTERVAL = 1e3;
|
|
7308
|
+
var instance = null;
|
|
7309
|
+
function getActivityLog() {
|
|
7310
|
+
if (!instance) {
|
|
7311
|
+
instance = new ActivityLog();
|
|
7312
|
+
}
|
|
7313
|
+
return instance;
|
|
7314
|
+
}
|
|
7315
|
+
var ActivityLog = class {
|
|
7316
|
+
filePath;
|
|
7317
|
+
writeCount = 0;
|
|
7318
|
+
unsubscribe;
|
|
7319
|
+
constructor() {
|
|
7320
|
+
this.filePath = path16.join(getConfigDir(), ACTIVITY_FILE);
|
|
7321
|
+
}
|
|
7322
|
+
/** Read historical events from the JSONL file, newest first */
|
|
7323
|
+
getHistory(limit = 500) {
|
|
7324
|
+
if (!fs17.existsSync(this.filePath)) return [];
|
|
7325
|
+
const content = fs17.readFileSync(this.filePath, "utf-8");
|
|
7326
|
+
const lines = content.split("\n").filter(Boolean);
|
|
7327
|
+
const events = [];
|
|
7328
|
+
for (const line of lines) {
|
|
7329
|
+
try {
|
|
7330
|
+
const evt = JSON.parse(line);
|
|
7331
|
+
if (evt.type === "heartbeat") continue;
|
|
7332
|
+
events.push(evt);
|
|
7333
|
+
} catch {
|
|
7334
|
+
}
|
|
7335
|
+
}
|
|
7336
|
+
events.reverse();
|
|
7337
|
+
return events.slice(0, limit);
|
|
7338
|
+
}
|
|
7339
|
+
start() {
|
|
7340
|
+
this.pruneOldEntries();
|
|
7341
|
+
this.unsubscribe = daemonEvents.subscribe((event) => {
|
|
7342
|
+
this.append(event);
|
|
7343
|
+
});
|
|
7344
|
+
}
|
|
7345
|
+
stop() {
|
|
7346
|
+
this.unsubscribe?.();
|
|
7347
|
+
}
|
|
7348
|
+
append(event) {
|
|
7349
|
+
const line = JSON.stringify(event) + "\n";
|
|
7350
|
+
fs17.appendFileSync(this.filePath, line, "utf-8");
|
|
7351
|
+
this.writeCount++;
|
|
7352
|
+
if (this.writeCount % PRUNE_INTERVAL === 0) {
|
|
7353
|
+
this.rotate();
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
rotate() {
|
|
7357
|
+
try {
|
|
7358
|
+
const stat = fs17.statSync(this.filePath);
|
|
7359
|
+
if (stat.size > MAX_SIZE_BYTES) {
|
|
7360
|
+
this.truncateBySize();
|
|
7361
|
+
}
|
|
7362
|
+
} catch {
|
|
7363
|
+
}
|
|
7364
|
+
this.pruneOldEntries();
|
|
7365
|
+
}
|
|
7366
|
+
/** Keep newest half of lines when file exceeds size limit */
|
|
7367
|
+
truncateBySize() {
|
|
7368
|
+
const content = fs17.readFileSync(this.filePath, "utf-8");
|
|
7369
|
+
const lines = content.split("\n").filter(Boolean);
|
|
7370
|
+
const keep = lines.slice(Math.floor(lines.length / 2));
|
|
7371
|
+
fs17.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
|
|
7372
|
+
}
|
|
7373
|
+
/** Remove entries older than 24 hours */
|
|
7374
|
+
pruneOldEntries() {
|
|
7375
|
+
if (!fs17.existsSync(this.filePath)) return;
|
|
7376
|
+
const content = fs17.readFileSync(this.filePath, "utf-8");
|
|
7377
|
+
const lines = content.split("\n").filter(Boolean);
|
|
7378
|
+
const cutoff = Date.now() - MAX_AGE_MS;
|
|
7379
|
+
const kept = lines.filter((line) => {
|
|
7380
|
+
try {
|
|
7381
|
+
const evt = JSON.parse(line);
|
|
7382
|
+
return new Date(evt.timestamp).getTime() >= cutoff;
|
|
7383
|
+
} catch {
|
|
7384
|
+
return false;
|
|
7385
|
+
}
|
|
7386
|
+
});
|
|
7387
|
+
if (kept.length < lines.length) {
|
|
7388
|
+
fs17.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
|
|
7389
|
+
}
|
|
7390
|
+
}
|
|
7391
|
+
};
|
|
7392
|
+
|
|
7393
|
+
// libs/shield-daemon/src/routes/activity.ts
|
|
7394
|
+
async function activityRoutes(app) {
|
|
7395
|
+
app.get(
|
|
7396
|
+
"/activity",
|
|
7397
|
+
async (request) => {
|
|
7398
|
+
const authenticated = isAuthenticated(request);
|
|
7399
|
+
const raw = Number(request.query.limit) || 500;
|
|
7400
|
+
const limit = Math.min(Math.max(raw, 1), 1e4);
|
|
7401
|
+
const events = getActivityLog().getHistory(limit);
|
|
7402
|
+
if (authenticated) {
|
|
7403
|
+
return { data: events };
|
|
7404
|
+
}
|
|
7405
|
+
const stripped = events.map((e) => ({
|
|
7406
|
+
type: e.type,
|
|
7407
|
+
timestamp: e.timestamp,
|
|
7408
|
+
data: {}
|
|
7409
|
+
}));
|
|
7410
|
+
return { data: stripped };
|
|
7411
|
+
}
|
|
7412
|
+
);
|
|
7413
|
+
}
|
|
7414
|
+
|
|
7102
7415
|
// libs/shield-daemon/src/routes/rpc.ts
|
|
7103
7416
|
function globToRegex(pattern) {
|
|
7104
7417
|
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
|
|
@@ -7297,7 +7610,7 @@ async function registerRoutes(app) {
|
|
|
7297
7610
|
});
|
|
7298
7611
|
app.addHook("onResponse", (request, reply, done) => {
|
|
7299
7612
|
if (!request.url.startsWith("/sse") && !request.url.startsWith("/rpc") && !request.url.includes(".") && !request.url.endsWith("/health")) {
|
|
7300
|
-
if (request.method === "GET" && request.url.startsWith("/api/status") && reply.statusCode === 200) {
|
|
7613
|
+
if (request.method === "GET" && (request.url.startsWith("/api/status") || request.url.startsWith("/api/activity")) && reply.statusCode === 200) {
|
|
7301
7614
|
done();
|
|
7302
7615
|
return;
|
|
7303
7616
|
}
|
|
@@ -7350,28 +7663,29 @@ async function registerRoutes(app) {
|
|
|
7350
7663
|
await api.register(secretsRoutes);
|
|
7351
7664
|
await api.register(marketplaceRoutes);
|
|
7352
7665
|
await api.register(fsRoutes);
|
|
7666
|
+
await api.register(activityRoutes);
|
|
7353
7667
|
},
|
|
7354
7668
|
{ prefix: API_PREFIX }
|
|
7355
7669
|
);
|
|
7356
7670
|
}
|
|
7357
7671
|
|
|
7358
7672
|
// libs/shield-daemon/src/static.ts
|
|
7359
|
-
import * as
|
|
7360
|
-
import * as
|
|
7673
|
+
import * as fs18 from "node:fs";
|
|
7674
|
+
import * as path17 from "node:path";
|
|
7361
7675
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7362
7676
|
var __filename = fileURLToPath2(import.meta.url);
|
|
7363
|
-
var __dirname =
|
|
7677
|
+
var __dirname = path17.dirname(__filename);
|
|
7364
7678
|
function getUiAssetsPath() {
|
|
7365
|
-
const pkgRootPath =
|
|
7366
|
-
if (
|
|
7679
|
+
const pkgRootPath = path17.join(__dirname, "..", "ui-assets");
|
|
7680
|
+
if (fs18.existsSync(pkgRootPath)) {
|
|
7367
7681
|
return pkgRootPath;
|
|
7368
7682
|
}
|
|
7369
|
-
const bundledPath =
|
|
7370
|
-
if (
|
|
7683
|
+
const bundledPath = path17.join(__dirname, "ui-assets");
|
|
7684
|
+
if (fs18.existsSync(bundledPath)) {
|
|
7371
7685
|
return bundledPath;
|
|
7372
7686
|
}
|
|
7373
|
-
const devPath =
|
|
7374
|
-
if (
|
|
7687
|
+
const devPath = path17.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
|
|
7688
|
+
if (fs18.existsSync(devPath)) {
|
|
7375
7689
|
return devPath;
|
|
7376
7690
|
}
|
|
7377
7691
|
return null;
|
|
@@ -7430,74 +7744,6 @@ function stopSecurityWatcher() {
|
|
|
7430
7744
|
}
|
|
7431
7745
|
}
|
|
7432
7746
|
|
|
7433
|
-
// libs/shield-daemon/src/services/activity-log.ts
|
|
7434
|
-
import * as fs18 from "node:fs";
|
|
7435
|
-
import * as path17 from "node:path";
|
|
7436
|
-
var ACTIVITY_FILE = "activity.jsonl";
|
|
7437
|
-
var MAX_SIZE_BYTES = 100 * 1024 * 1024;
|
|
7438
|
-
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7439
|
-
var PRUNE_INTERVAL = 1e3;
|
|
7440
|
-
var ActivityLog = class {
|
|
7441
|
-
filePath;
|
|
7442
|
-
writeCount = 0;
|
|
7443
|
-
unsubscribe;
|
|
7444
|
-
constructor() {
|
|
7445
|
-
this.filePath = path17.join(getConfigDir(), ACTIVITY_FILE);
|
|
7446
|
-
}
|
|
7447
|
-
start() {
|
|
7448
|
-
this.pruneOldEntries();
|
|
7449
|
-
this.unsubscribe = daemonEvents.subscribe((event) => {
|
|
7450
|
-
this.append(event);
|
|
7451
|
-
});
|
|
7452
|
-
}
|
|
7453
|
-
stop() {
|
|
7454
|
-
this.unsubscribe?.();
|
|
7455
|
-
}
|
|
7456
|
-
append(event) {
|
|
7457
|
-
const line = JSON.stringify(event) + "\n";
|
|
7458
|
-
fs18.appendFileSync(this.filePath, line, "utf-8");
|
|
7459
|
-
this.writeCount++;
|
|
7460
|
-
if (this.writeCount % PRUNE_INTERVAL === 0) {
|
|
7461
|
-
this.rotate();
|
|
7462
|
-
}
|
|
7463
|
-
}
|
|
7464
|
-
rotate() {
|
|
7465
|
-
try {
|
|
7466
|
-
const stat = fs18.statSync(this.filePath);
|
|
7467
|
-
if (stat.size > MAX_SIZE_BYTES) {
|
|
7468
|
-
this.truncateBySize();
|
|
7469
|
-
}
|
|
7470
|
-
} catch {
|
|
7471
|
-
}
|
|
7472
|
-
this.pruneOldEntries();
|
|
7473
|
-
}
|
|
7474
|
-
/** Keep newest half of lines when file exceeds size limit */
|
|
7475
|
-
truncateBySize() {
|
|
7476
|
-
const content = fs18.readFileSync(this.filePath, "utf-8");
|
|
7477
|
-
const lines = content.split("\n").filter(Boolean);
|
|
7478
|
-
const keep = lines.slice(Math.floor(lines.length / 2));
|
|
7479
|
-
fs18.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
|
|
7480
|
-
}
|
|
7481
|
-
/** Remove entries older than 24 hours */
|
|
7482
|
-
pruneOldEntries() {
|
|
7483
|
-
if (!fs18.existsSync(this.filePath)) return;
|
|
7484
|
-
const content = fs18.readFileSync(this.filePath, "utf-8");
|
|
7485
|
-
const lines = content.split("\n").filter(Boolean);
|
|
7486
|
-
const cutoff = Date.now() - MAX_AGE_MS;
|
|
7487
|
-
const kept = lines.filter((line) => {
|
|
7488
|
-
try {
|
|
7489
|
-
const evt = JSON.parse(line);
|
|
7490
|
-
return new Date(evt.timestamp).getTime() >= cutoff;
|
|
7491
|
-
} catch {
|
|
7492
|
-
return false;
|
|
7493
|
-
}
|
|
7494
|
-
});
|
|
7495
|
-
if (kept.length < lines.length) {
|
|
7496
|
-
fs18.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
|
|
7497
|
-
}
|
|
7498
|
-
}
|
|
7499
|
-
};
|
|
7500
|
-
|
|
7501
7747
|
// libs/shield-daemon/src/server.ts
|
|
7502
7748
|
async function createServer(config) {
|
|
7503
7749
|
const app = Fastify({
|
|
@@ -7536,7 +7782,7 @@ async function startServer(config) {
|
|
|
7536
7782
|
onQuarantined: (info) => emitSkillQuarantined(info.name, info.reason),
|
|
7537
7783
|
onApproved: (name) => emitSkillApproved(name)
|
|
7538
7784
|
}, 3e4);
|
|
7539
|
-
const activityLog =
|
|
7785
|
+
const activityLog = getActivityLog();
|
|
7540
7786
|
activityLog.start();
|
|
7541
7787
|
try {
|
|
7542
7788
|
const vault = getVault();
|