Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@agenshield/interceptor

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agenshield/interceptor - npm Package Compare versions

Comparing version
0.6.2
to
0.7.0
+36
seatbelt/profile-manager.d.ts
/**
* Seatbelt Profile Manager
*
* Generates macOS seatbelt (sandbox-exec) SBPL profiles from SandboxConfig,
* writes them to disk with content-hash naming for caching, and manages cleanup.
*
* Uses captured-original fs functions to avoid interception loops.
*/
import type { SandboxConfig } from '@agenshield/ipc';
export declare class ProfileManager {
private profileDir;
private ensuredDir;
constructor(profileDir: string);
/**
* Get or create a profile file on disk. Returns the absolute path.
* Uses content-hash naming so identical configs reuse the same file.
*/
getOrCreateProfile(content: string): string;
/**
* Generate an SBPL profile from a SandboxConfig.
*/
generateProfile(sandbox: SandboxConfig): string;
/**
* Remove stale profile files older than maxAgeMs.
*/
cleanup(maxAgeMs: number): void;
/**
* Escape a string for safe inclusion in SBPL
*/
private escapeSbpl;
/**
* Ensure the profile directory exists
*/
private ensureDir;
}
//# sourceMappingURL=profile-manager.d.ts.map
{"version":3,"file":"profile-manager.d.ts","sourceRoot":"","sources":["../../src/seatbelt/profile-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAarD,qBAAa,cAAc;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM;IAI9B;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAc3C;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM;IAgL/C;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAwB/B;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;IACH,OAAO,CAAC,SAAS;CAgBlB"}
+6
-0

@@ -18,4 +18,10 @@ /**

private timeout;
private socketFailCount;
private socketSkipUntil;
constructor(options: SyncClientOptions);
/**
* Remove stale /tmp/agenshield-sync-*.json files from previous runs
*/
private cleanupStaleTmpFiles;
/**
* Send a synchronous request to the broker

@@ -22,0 +28,0 @@ */

+1
-1

@@ -1,1 +0,1 @@

{"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../../src/client/sync-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,iBAAiB;IAOtC;;OAEG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC;IAgB9D;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAsFzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAwCvB;;OAEG;IACH,IAAI,IAAI,OAAO;CAQhB"}
{"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../../src/client/sync-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmBH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAAK;gBAEhB,OAAO,EAAE,iBAAiB;IAQtC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;OAEG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC;IAiC9D;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAkGzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAwCvB;;OAEG;IACH,IAAI,IAAI,OAAO;CAQhB"}

@@ -29,2 +29,12 @@ /**

timeout: number;
/** Execution context type: 'agent' or 'skill' */
contextType: 'agent' | 'skill';
/** Skill slug (when contextType is 'skill') */
contextSkillSlug?: string;
/** Agent identifier */
contextAgentId?: string;
/** Enable macOS seatbelt wrapping for exec operations */
enableSeatbelt: boolean;
/** Directory for generated seatbelt profiles */
seatbeltProfileDir: string;
}

@@ -31,0 +41,0 @@ /**

@@ -1,1 +0,1 @@

{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IAEnB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IAEjB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAElB,gBAAgB;IAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAE9C,gCAAgC;IAChC,cAAc,EAAE,OAAO,CAAC;IAExB,4CAA4C;IAC5C,aAAa,EAAE,OAAO,CAAC;IAEvB,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IAErB,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IAErB,wCAAwC;IACxC,aAAa,EAAE,OAAO,CAAC;IAEvB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAiBtF;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,iBAAkC,CAAC"}
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IAEnB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IAEjB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAElB,gBAAgB;IAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAE9C,gCAAgC;IAChC,cAAc,EAAE,OAAO,CAAC;IAExB,4CAA4C;IAC5C,aAAa,EAAE,OAAO,CAAC;IAEvB,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IAErB,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IAErB,wCAAwC;IACxC,aAAa,EAAE,OAAO,CAAC;IAEvB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAEhB,iDAAiD;IACjD,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC;IAE/B,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,uBAAuB;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,yDAAyD;IACzD,cAAc,EAAE,OAAO,CAAC;IAExB,gDAAgD;IAChD,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAsBtF;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,iBAAkC,CAAC"}

@@ -6,4 +6,5 @@ /**

* a captured (pre-patch) appendFileSync. Safe from interception.
* Falls back to /tmp/agenshield-interceptor.log if primary path is not writable.
*/
export declare function debugLog(msg: string): void;
//# sourceMappingURL=debug-log.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"debug-log.d.ts","sourceRoot":"","sources":["../src/debug-log.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAM1C"}
{"version":3,"file":"debug-log.d.ts","sourceRoot":"","sources":["../src/debug-log.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwBH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAO1C"}

@@ -1,1 +0,1 @@

{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/events/reporter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,gBAAgB,CAAK;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAO;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IAExC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAK5B;gBAEU,OAAO,EAAE,oBAAoB;IASzC;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAqBrC;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IASlD;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAWpF;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAWjF;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAU7D;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAUZ;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,OAAO,CAAC,SAAS;CAGlB"}
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/events/reporter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,gBAAgB,CAAK;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAO;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IAExC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAK5B;gBAEU,OAAO,EAAE,oBAAoB;IASzC;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAyBrC;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IASlD;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAWpF;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAWjF;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAU7D;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAUZ;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,OAAO,CAAC,SAAS;CAGlB"}
+677
-166

@@ -64,3 +64,8 @@ "use strict";

interceptExec: env["AGENSHIELD_INTERCEPT_EXEC"] !== "false",
timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "30000", 10),
timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "5000", 10),
contextType: env["AGENSHIELD_CONTEXT_TYPE"] || "agent",
contextSkillSlug: env["AGENSHIELD_SKILL_SLUG"],
contextAgentId: env["AGENSHIELD_AGENT_ID"],
enableSeatbelt: env["AGENSHIELD_SEATBELT"] !== "false" && process.platform === "darwin",
seatbeltProfileDir: env["AGENSHIELD_SEATBELT_DIR"] || "/tmp/agenshield-profiles",
...overrides

@@ -109,9 +114,30 @@ };

var _appendFileSync = fs.appendFileSync.bind(fs);
var _writeSync = fs.writeSync.bind(fs);
var LOG_PATH = "/var/log/agenshield/interceptor.log";
var FALLBACK_LOG_PATH = "/tmp/agenshield-interceptor.log";
var resolvedLogPath = null;
function getLogPath() {
if (resolvedLogPath !== null) return resolvedLogPath;
try {
_appendFileSync(LOG_PATH, "");
resolvedLogPath = LOG_PATH;
} catch {
resolvedLogPath = FALLBACK_LOG_PATH;
}
return resolvedLogPath;
}
function debugLog(msg) {
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
`;
try {
_appendFileSync(LOG_PATH, `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
`);
_appendFileSync(getLogPath(), line);
} catch {
}
if (process.env["AGENSHIELD_LOG_LEVEL"] === "debug") {
try {
_writeSync(2, `[AgenShield:debug] ${msg}
`);
} catch {
}
}
}

@@ -126,2 +152,3 @@

installed = false;
interceptorConfig;
brokerHttpPort;

@@ -134,2 +161,3 @@ constructor(options) {

this.brokerHttpPort = options.brokerHttpPort ?? 5201;
this.interceptorConfig = options.config;
}

@@ -160,5 +188,18 @@ /**

/**
* Build execution context from config
*/
getBasePolicyExecutionContext() {
const config = this.interceptorConfig;
if (!config) return void 0;
return {
callerType: config.contextType || "agent",
skillSlug: config.contextSkillSlug,
agentId: config.contextAgentId,
depth: 0
};
}
/**
* Check policy and handle the result
*/
async checkPolicy(operation, target) {
async checkPolicy(operation, target, context) {
const startTime = Date.now();

@@ -168,3 +209,3 @@ debugLog(`base.checkPolicy START op=${operation} target=${target}`);

this.eventReporter.intercept(operation, target);
const result = await this.policyEvaluator.check(operation, target);
const result = await this.policyEvaluator.check(operation, target, context);
debugLog(`base.checkPolicy evaluator result op=${operation} target=${target} allowed=${result.allowed} policyId=${result.policyId}`);

@@ -216,2 +257,15 @@ if (!result.allowed) {

}
/**
* Build execution context from config
*/
getPolicyExecutionContext() {
const config = this.interceptorConfig;
if (!config) return void 0;
return {
callerType: config.contextType || "agent",
skillSlug: config.contextSkillSlug,
agentId: config.contextAgentId,
depth: 0
};
}
install() {

@@ -247,44 +301,6 @@ if (this.installed) return;

debugLog(`fetch checkPolicy START url=${url}`);
await this.checkPolicy("http_request", url);
debugLog(`fetch checkPolicy DONE url=${url}`);
try {
const method = init?.method || "GET";
const headers = {};
if (init?.headers) {
if (init.headers instanceof Headers) {
init.headers.forEach((value, key) => {
headers[key] = value;
});
} else if (Array.isArray(init.headers)) {
for (const [key, value] of init.headers) {
headers[key] = value;
}
} else {
Object.assign(headers, init.headers);
}
}
let body;
if (init?.body) {
if (typeof init.body === "string") {
body = init.body;
} else if (init.body instanceof ArrayBuffer) {
body = Buffer.from(init.body).toString("base64");
} else {
body = String(init.body);
}
}
const result = await this.client.request("http_request", {
url,
method,
headers,
body
});
const responseHeaders = new Headers(result.headers);
return new Response(result.body, {
status: result.status,
statusText: result.statusText,
headers: responseHeaders
});
await this.checkPolicy("http_request", url, this.getPolicyExecutionContext());
debugLog(`fetch checkPolicy DONE url=${url}`);
} catch (error) {
debugLog(`fetch ERROR url=${url} error=${error.message}`);
if (error.name === "PolicyDeniedError") {

@@ -299,2 +315,3 @@ throw error;

}
return this.originalFetch(input, init);
}

@@ -444,2 +461,4 @@ };

var _unlinkSync = fs2.unlinkSync.bind(fs2);
var _readdirSync = fs2.readdirSync.bind(fs2);
var _statSync = fs2.statSync.bind(fs2);
var _spawnSync = import_node_child_process.spawnSync;

@@ -452,2 +471,4 @@ var _execSync = import_node_child_process.execSync;

timeout;
socketFailCount = 0;
socketSkipUntil = 0;
constructor(options) {

@@ -458,4 +479,26 @@ this.socketPath = options.socketPath;

this.timeout = options.timeout;
this.cleanupStaleTmpFiles();
}
/**
* Remove stale /tmp/agenshield-sync-*.json files from previous runs
*/
cleanupStaleTmpFiles() {
try {
const tmpDir = "/tmp";
const files = _readdirSync(tmpDir);
const cutoff = Date.now() - 5 * 60 * 1e3;
for (const f of files) {
if (f.startsWith("agenshield-sync-") && f.endsWith(".json")) {
const fp = `${tmpDir}/${f}`;
try {
const stat = _statSync(fp);
if (stat.mtimeMs < cutoff) _unlinkSync(fp);
} catch {
}
}
}
} catch {
}
}
/**
* Send a synchronous request to the broker

@@ -465,7 +508,20 @@ */

debugLog(`syncClient.request START method=${method}`);
const now = Date.now();
if (now < this.socketSkipUntil) {
debugLog(`syncClient.request SKIP socket (circuit open for ${this.socketSkipUntil - now}ms), using HTTP`);
const result = this.httpRequestSync(method, params);
debugLog(`syncClient.request http OK method=${method}`);
return result;
}
try {
const result = this.socketRequestSync(method, params);
this.socketFailCount = 0;
debugLog(`syncClient.request socket OK method=${method}`);
return result;
} catch (socketErr) {
this.socketFailCount++;
if (this.socketFailCount >= 2) {
this.socketSkipUntil = Date.now() + 6e4;
debugLog(`syncClient.request socket circuit OPEN (${this.socketFailCount} failures)`);
}
debugLog(`syncClient.request socket FAILED: ${socketErr.message}, trying HTTP`);

@@ -495,5 +551,14 @@ const result = this.httpRequestSync(method, params);

let done = false;
const socket = net.createConnection('${this.socketPath}');
let data = '';
const timer = setTimeout(() => {
if (done) return;
done = true;
socket.destroy();
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
process.exit(1);
}, ${this.timeout});
socket.on('connect', () => {

@@ -505,5 +570,8 @@ socket.write(${JSON.stringify(request)});

data += chunk.toString();
if (data.includes('\\n')) {
if (data.includes('\\n') && !done) {
done = true;
clearTimeout(timer);
socket.end();
fs.writeFileSync('${tmpFile}', data.split('\\n')[0]);
process.exit(0);
}

@@ -513,9 +581,8 @@ });

socket.on('error', (err) => {
if (done) return;
done = true;
clearTimeout(timer);
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: err.message }));
process.exit(1);
});
setTimeout(() => {
socket.destroy();
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
}, ${this.timeout});
`;

@@ -597,2 +664,215 @@ try {

// libs/shield-interceptor/src/seatbelt/profile-manager.ts
var fs3 = __toESM(require("node:fs"), 1);
var crypto = __toESM(require("node:crypto"), 1);
var path = __toESM(require("node:path"), 1);
var _mkdirSync = fs3.mkdirSync.bind(fs3);
var _writeFileSync = fs3.writeFileSync.bind(fs3);
var _existsSync2 = fs3.existsSync.bind(fs3);
var _readFileSync2 = fs3.readFileSync.bind(fs3);
var _readdirSync2 = fs3.readdirSync.bind(fs3);
var _statSync2 = fs3.statSync.bind(fs3);
var _unlinkSync2 = fs3.unlinkSync.bind(fs3);
var _chmodSync = fs3.chmodSync.bind(fs3);
var ProfileManager = class {
profileDir;
ensuredDir = false;
constructor(profileDir) {
this.profileDir = profileDir;
}
/**
* Get or create a profile file on disk. Returns the absolute path.
* Uses content-hash naming so identical configs reuse the same file.
*/
getOrCreateProfile(content) {
this.ensureDir();
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
const profilePath = path.join(this.profileDir, `sb-${hash}.sb`);
if (!_existsSync2(profilePath)) {
debugLog(`profile-manager: writing new profile ${profilePath} (${content.length} bytes)`);
_writeFileSync(profilePath, content, { mode: 420 });
}
return profilePath;
}
/**
* Generate an SBPL profile from a SandboxConfig.
*/
generateProfile(sandbox) {
if (sandbox.profileContent) {
return sandbox.profileContent;
}
const lines = [
";; AgenShield dynamic seatbelt profile",
`;; Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
"(version 1)",
"(deny default)",
""
];
lines.push(
";; Filesystem: reads allowed, writes restricted",
"(allow file-read*)",
""
);
const writePaths = ["/tmp", "/private/tmp", "/var/folders"];
if (sandbox.allowedWritePaths.length > 0) {
writePaths.push(...sandbox.allowedWritePaths);
}
lines.push("(allow file-write*");
for (const p of writePaths) {
lines.push(` (subpath "${this.escapeSbpl(p)}")`);
}
lines.push(")");
lines.push("");
lines.push("(allow file-write*");
lines.push(' (literal "/dev/null")');
lines.push(' (literal "/dev/zero")');
lines.push(' (literal "/dev/random")');
lines.push(' (literal "/dev/urandom")');
lines.push(")");
lines.push("");
if (sandbox.deniedPaths.length > 0) {
lines.push(";; Denied paths");
for (const p of sandbox.deniedPaths) {
lines.push(`(deny file-read* file-write* (subpath "${this.escapeSbpl(p)}"))`);
}
lines.push("");
}
lines.push(";; Binary execution (system directories allowed as subpaths)");
lines.push("(allow process-exec");
lines.push(' (subpath "/bin")');
lines.push(' (subpath "/sbin")');
lines.push(' (subpath "/usr/bin")');
lines.push(' (subpath "/usr/sbin")');
lines.push(' (subpath "/usr/local/bin")');
lines.push(' (subpath "/opt/agenshield/bin")');
const coveredSubpaths = ["/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/", "/opt/agenshield/bin/"];
const home = process.env["HOME"];
if (home) {
lines.push(` (subpath "${this.escapeSbpl(home)}/bin")`);
lines.push(` (subpath "${this.escapeSbpl(home)}/homebrew")`);
coveredSubpaths.push(`${home}/bin/`, `${home}/homebrew/`);
}
const nvmDir = process.env["NVM_DIR"] || (home ? `${home}/.nvm` : null);
if (nvmDir) {
lines.push(` (subpath "${this.escapeSbpl(nvmDir)}")`);
coveredSubpaths.push(`${nvmDir}/`);
}
const brewPrefix = process.env["HOMEBREW_PREFIX"];
if (brewPrefix && (!home || !brewPrefix.startsWith(home))) {
lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/bin")`);
lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/lib")`);
coveredSubpaths.push(`${brewPrefix}/bin/`, `${brewPrefix}/lib/`);
}
const uniqueBinaries = [...new Set(sandbox.allowedBinaries)];
for (const bin of uniqueBinaries) {
if (coveredSubpaths.some((dir) => bin === dir || bin.startsWith(dir))) continue;
if (bin.endsWith("/")) {
lines.push(` (subpath "${this.escapeSbpl(bin)}")`);
} else {
lines.push(` (literal "${this.escapeSbpl(bin)}")`);
}
}
lines.push(")");
lines.push("");
const uniqueDenied = [...new Set(sandbox.deniedBinaries)];
if (uniqueDenied.length > 0) {
lines.push(";; Denied binaries");
for (const bin of uniqueDenied) {
lines.push(`(deny process-exec (literal "${this.escapeSbpl(bin)}"))`);
}
lines.push("");
}
lines.push(";; Network");
if (sandbox.networkAllowed) {
if (sandbox.allowedHosts.length > 0 || sandbox.allowedPorts.length > 0) {
lines.push(";; Allow specific network targets");
for (const host of sandbox.allowedHosts) {
lines.push(`(allow network-outbound (remote tcp "${this.escapeSbpl(host)}:*"))`);
}
for (const port of sandbox.allowedPorts) {
lines.push(`(allow network-outbound (remote tcp "*:${port}"))`);
}
const isLocalhostOnly = sandbox.allowedHosts.length > 0 && sandbox.allowedHosts.every((h) => h === "localhost" || h === "127.0.0.1");
if (!isLocalhostOnly) {
lines.push('(allow network-outbound (remote udp "*:53") (remote tcp "*:53"))');
}
} else {
lines.push("(allow network*)");
}
} else {
lines.push("(deny network*)");
}
lines.push("");
lines.push(
";; Broker / local unix sockets",
"(allow network-outbound (remote unix))",
"(allow network-inbound (local unix))",
"(allow file-read* file-write*",
' (subpath "/var/run/agenshield")',
' (subpath "/private/var/run/agenshield"))',
""
);
lines.push(
";; Process management",
"(allow process-fork)",
"(allow signal (target self))",
"(allow sysctl-read)",
""
);
lines.push(
";; Mach IPC",
"(allow mach-lookup)",
""
);
return lines.join("\n");
}
/**
* Remove stale profile files older than maxAgeMs.
*/
cleanup(maxAgeMs) {
if (!_existsSync2(this.profileDir)) return;
try {
const now = Date.now();
const entries = _readdirSync2(this.profileDir);
for (const entry of entries) {
if (!entry.endsWith(".sb")) continue;
const filePath = path.join(this.profileDir, entry);
try {
const stat = _statSync2(filePath);
if (now - stat.mtimeMs > maxAgeMs) {
_unlinkSync2(filePath);
debugLog(`profile-manager: cleaned up stale profile ${filePath}`);
}
} catch {
}
}
} catch {
}
}
/**
* Escape a string for safe inclusion in SBPL
*/
escapeSbpl(s) {
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
/**
* Ensure the profile directory exists
*/
ensureDir() {
if (this.ensuredDir) return;
if (!_existsSync2(this.profileDir)) {
_mkdirSync(this.profileDir, { recursive: true, mode: 1023 });
} else {
try {
const stat = _statSync2(this.profileDir);
if ((stat.mode & 511) !== 511) {
_chmodSync(this.profileDir, 1023);
}
} catch {
}
}
this.ensuredDir = true;
}
};
// libs/shield-interceptor/src/interceptors/child-process.ts

@@ -603,2 +883,5 @@ var childProcessModule = require("node:child_process");

_checking = false;
_executing = false;
// Guards exec→execFile re-entrancy
profileManager = null;
originalExec = null;

@@ -612,9 +895,14 @@ originalExecSync = null;

super(options);
const config = this.interceptorConfig;
this.syncClient = new SyncClient({
socketPath: "/var/run/agenshield/agenshield.sock",
httpHost: "localhost",
httpPort: 5201,
// Broker uses 5201
timeout: 3e4
socketPath: config?.socketPath || "/var/run/agenshield/agenshield.sock",
httpHost: config?.httpHost || "localhost",
httpPort: config?.httpPort || 5201,
timeout: config?.timeout || 3e4
});
if (config?.enableSeatbelt && process.platform === "darwin") {
this.profileManager = new ProfileManager(
config.seatbeltProfileDir || "/tmp/agenshield-profiles"
);
}
}

@@ -653,2 +941,146 @@ install() {

}
/**
* Build execution context from config for RPC calls
*/
getPolicyExecutionContext() {
const config = this.interceptorConfig;
return {
callerType: config?.contextType || "agent",
skillSlug: config?.contextSkillSlug,
agentId: config?.contextAgentId,
depth: 0
};
}
/**
* Synchronous policy check via SyncClient.
* Returns the full policy result (with sandbox config) or null if broker
* is unavailable and failOpen is true.
*/
syncPolicyCheck(fullCommand) {
this._checking = true;
try {
debugLog(`cp.syncPolicyCheck START command=${fullCommand}`);
const context = this.getPolicyExecutionContext();
const result = this.syncClient.request(
"policy_check",
{ operation: "exec", target: fullCommand, context }
);
debugLog(`cp.syncPolicyCheck DONE allowed=${result.allowed} command=${fullCommand}`);
if (!result.allowed) {
throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
operation: "exec",
target: fullCommand,
policyId: result.policyId
});
}
return result;
} catch (error) {
if (error instanceof PolicyDeniedError) {
throw error;
}
debugLog(`cp.syncPolicyCheck ERROR: ${error.message} command=${fullCommand}`);
if (!this.failOpen) {
throw error;
}
return null;
} finally {
this._checking = false;
}
}
/**
* Create a restrictive default sandbox config for fail-open scenarios.
* No network, minimal fs — better than running completely unsandboxed.
*/
getFailOpenSandbox() {
return {
enabled: true,
allowedReadPaths: [],
allowedWritePaths: [],
deniedPaths: [],
networkAllowed: false,
allowedHosts: [],
allowedPorts: [],
allowedBinaries: [],
deniedBinaries: [],
envInjection: {},
envDeny: []
};
}
/**
* Resolve the sandbox config to use: from policy result, fail-open default, or null.
*/
resolveSandbox(policyResult) {
if (policyResult?.sandbox?.enabled) {
return policyResult.sandbox;
}
if (policyResult === null && this.profileManager) {
return this.getFailOpenSandbox();
}
return null;
}
/**
* Wrap a command with sandbox-exec if seatbelt is enabled and sandbox config is present.
* Returns modified { command, args, options } for spawn-style calls.
*/
wrapWithSeatbelt(command, args, options, policyResult) {
const sandbox = this.resolveSandbox(policyResult);
if (!this.profileManager || !sandbox || process.platform !== "darwin") {
return { command, args, options };
}
if (command === "/opt/agenshield/bin/node-bin" || command.endsWith("/node-bin")) {
debugLog(`cp.wrapWithSeatbelt: SKIP node-bin (already intercepted) command=${command}`);
return { command, args, options };
}
if (command === "/usr/bin/sandbox-exec" || command.endsWith("/sandbox-exec")) {
debugLog(`cp.wrapWithSeatbelt: SKIP already sandbox-exec command=${command}`);
return { command, args, options };
}
debugLog(`cp.wrapWithSeatbelt: wrapping command=${command}`);
const profileContent = this.profileManager.generateProfile(sandbox);
const profilePath = this.profileManager.getOrCreateProfile(profileContent);
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
return {
command: "/usr/bin/sandbox-exec",
args: ["-f", profilePath, command, ...args],
options: { ...options, env }
};
}
/**
* Wrap a shell command string with sandbox-exec.
* For exec/execSync which take a full command string.
*/
wrapCommandStringWithSeatbelt(command, options, policyResult) {
const sandbox = this.resolveSandbox(policyResult);
if (!this.profileManager || !sandbox || process.platform !== "darwin") {
return { command, options };
}
if (command.startsWith("/usr/bin/sandbox-exec ") || command.startsWith("sandbox-exec ")) {
debugLog(`cp.wrapCommandStringWithSeatbelt: SKIP already sandbox-exec command=${command}`);
return { command, options };
}
debugLog(`cp.wrapCommandStringWithSeatbelt: wrapping command=${command}`);
const profileContent = this.profileManager.generateProfile(sandbox);
const profilePath = this.profileManager.getOrCreateProfile(profileContent);
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
return {
command: `/usr/bin/sandbox-exec -f ${profilePath} ${command}`,
options: { ...options, env }
};
}
createInterceptedExec() {

@@ -659,4 +1091,5 @@ const self = this;

const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
debugLog(`cp.exec ENTER command=${command} _checking=${self._checking}`);
if (self._checking) {
const options = args[0];
debugLog(`cp.exec ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.exec SKIP (re-entrancy) command=${command}`);

@@ -666,10 +1099,22 @@ return original(command, ...args, callback);

self.eventReporter.intercept("exec", command);
self.checkPolicy("exec", command).then(() => {
original(command, ...args, callback);
}).catch((error) => {
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(command);
} catch (error) {
if (callback) {
callback(error, "", "");
process.nextTick(() => callback(error, "", ""));
}
});
return original('echo ""');
return original('echo ""');
}
const wrapped = self.wrapCommandStringWithSeatbelt(command, options, policyResult);
debugLog(`cp.exec calling original command=${wrapped.command}`);
self._executing = true;
try {
if (wrapped.options) {
return original(wrapped.command, wrapped.options, callback);
}
return original(wrapped.command, callback);
} finally {
self._executing = false;
}
};

@@ -681,35 +1126,21 @@ }

const interceptedExecSync = function(command, options) {
debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.execSync SKIP (re-entrancy) command=${command}`);
return original(command, options);
}
self._checking = true;
self.eventReporter.intercept("exec", command);
const policyResult = self.syncPolicyCheck(command);
const wrapped = self.wrapCommandStringWithSeatbelt(
command,
options,
policyResult
);
debugLog(`cp.execSync calling original command=${wrapped.command}`);
self._executing = true;
try {
self.eventReporter.intercept("exec", command);
debugLog(`cp.execSync policy_check START command=${command}`);
const result = self.syncClient.request(
"policy_check",
{ operation: "exec", target: command }
);
debugLog(`cp.execSync policy_check DONE allowed=${result.allowed} command=${command}`);
if (!result.allowed) {
throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
operation: "exec",
target: command
});
}
} catch (error) {
debugLog(`cp.execSync policy_check ERROR: ${error.message} command=${command}`);
if (error instanceof PolicyDeniedError) {
throw error;
}
if (!self.failOpen) {
throw error;
}
return original(wrapped.command, wrapped.options);
} finally {
self._checking = false;
self._executing = false;
}
debugLog(`cp.execSync calling original command=${command}`);
return original(command, options);
};

@@ -723,13 +1154,27 @@ return interceptedExecSync;

const fullCmd = args ? `${command} ${args.join(" ")}` : command;
debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.spawn SKIP (re-entrancy) command=${fullCmd}`);
return original(command, args, options || {});
}
const fullCommand = args ? `${command} ${args.join(" ")}` : command;
self.eventReporter.intercept("exec", fullCommand);
self.checkPolicy("exec", fullCommand).catch((error) => {
self.eventReporter.error("exec", fullCommand, error.message);
});
return original(command, args, options || {});
self.eventReporter.intercept("exec", fullCmd);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCmd);
} catch (error) {
debugLog(`cp.spawn DENIED command=${fullCmd}`);
const denied = original("false", [], { stdio: "pipe" });
process.nextTick(() => {
denied.emit("error", error);
});
return denied;
}
const wrapped = self.wrapWithSeatbelt(
command,
Array.from(args || []),
options,
policyResult
);
debugLog(`cp.spawn calling original command=${wrapped.command} args=${wrapped.args.join(" ")}`);
return original(wrapped.command, wrapped.args, wrapped.options || {});
};

@@ -743,45 +1188,37 @@ return interceptedSpawn;

const fullCommand = args ? `${command} ${args.join(" ")}` : command;
debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.spawnSync SKIP (re-entrancy) command=${fullCommand}`);
return original(command, args, options);
}
self._checking = true;
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
self.eventReporter.intercept("exec", fullCommand);
debugLog(`cp.spawnSync policy_check START command=${fullCommand}`);
const result = self.syncClient.request(
"policy_check",
{ operation: "exec", target: fullCommand }
);
debugLog(`cp.spawnSync policy_check DONE allowed=${result.allowed} command=${fullCommand}`);
if (!result.allowed) {
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(result.reason || "Policy denied"),
status: 1,
signal: null,
error: new PolicyDeniedError(result.reason || "Policy denied")
};
}
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
debugLog(`cp.spawnSync policy_check ERROR: ${error.message} command=${fullCommand}`);
if (!self.failOpen) {
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(error.message),
status: 1,
signal: null,
error
};
}
} finally {
self._checking = false;
debugLog(`cp.spawnSync DENIED command=${fullCommand}`);
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(
error instanceof PolicyDeniedError ? error.message || "Policy denied" : error.message
),
status: 1,
signal: null,
error
};
}
debugLog(`cp.spawnSync calling original command=${fullCommand}`);
return original(command, args, options);
const wrapped = self.wrapWithSeatbelt(
command,
Array.from(args || []),
options,
policyResult
);
debugLog(`cp.spawnSync calling original command=${wrapped.command}`);
return original(
wrapped.command,
wrapped.args,
wrapped.options
);
};

@@ -792,11 +1229,48 @@ }

const original = this.originalExecFile;
return function interceptedExecFile(file, ...args) {
if (self._checking) {
return original(file, ...args);
return function interceptedExecFile(file, ...rest) {
if (self._checking || self._executing) {
return original(file, ...rest);
}
self.eventReporter.intercept("exec", file);
self.checkPolicy("exec", file).catch((error) => {
self.eventReporter.error("exec", file, error.message);
});
return original(file, ...args);
let args = [];
let options;
let callback;
for (const arg of rest) {
if (typeof arg === "function") {
callback = arg;
} else if (Array.isArray(arg)) {
args = arg;
} else if (typeof arg === "object" && arg !== null) {
options = arg;
}
}
const fullCommand = args.length > 0 ? `${file} ${args.join(" ")}` : file;
debugLog(`cp.execFile ENTER command=${fullCommand}`);
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
if (callback) {
process.nextTick(() => callback(error, "", ""));
}
return original("false");
}
const wrapped = self.wrapWithSeatbelt(file, args, options, policyResult);
debugLog(`cp.execFile calling original command=${wrapped.command}`);
if (callback) {
return original(
wrapped.command,
wrapped.args,
wrapped.options,
callback
);
}
return original(
wrapped.command,
wrapped.args,
wrapped.options || {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
() => {
}
);
};

@@ -808,10 +1282,34 @@ }

const interceptedFork = function(modulePath, args, options) {
if (self._checking) {
if (self._checking || self._executing) {
return original(modulePath, args, options);
}
const pathStr = modulePath.toString();
self.eventReporter.intercept("exec", `fork:${pathStr}`);
self.checkPolicy("exec", `fork:${pathStr}`).catch((error) => {
self.eventReporter.error("exec", pathStr, error.message);
});
const fullCommand = `fork:${pathStr}`;
debugLog(`cp.fork ENTER command=${fullCommand}`);
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
debugLog(`cp.fork DENIED command=${fullCommand}`);
const denied = self.originalSpawn("false", [], { stdio: "pipe" });
process.nextTick(() => {
denied.emit("error", error);
});
return denied;
}
if (policyResult?.sandbox) {
const sandbox = policyResult.sandbox;
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
options = { ...options, env };
}
debugLog(`cp.fork calling original module=${pathStr}`);
return original(modulePath, args, options);

@@ -910,4 +1408,4 @@ };

const self = this;
safeOverride(module2, methodName, function intercepted(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, function intercepted(path2, ...args) {
const pathString = normalizePathArg(path2);
const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;

@@ -917,3 +1415,3 @@ debugLog(`fs.${methodName} ENTER (async) path=${pathString} _checking=${self._checking}`);

debugLog(`fs.${methodName} SKIP (re-entrancy, async) path=${pathString}`);
original.call(module2, path, ...args, callback);
original.call(module2, path2, ...args, callback);
return;

@@ -924,3 +1422,3 @@ }

debugLog(`fs.${methodName} policy OK (async) path=${pathString}`);
original.call(module2, path, ...args, callback);
original.call(module2, path2, ...args, callback);
}).catch((error) => {

@@ -940,8 +1438,8 @@ debugLog(`fs.${methodName} policy ERROR (async): ${error.message} path=${pathString}`);

const self = this;
safeOverride(module2, methodName, function interceptedSync(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, function interceptedSync(path2, ...args) {
const pathString = normalizePathArg(path2);
debugLog(`fs.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
if (self._checking) {
debugLog(`fs.${methodName} SKIP (re-entrancy) path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
}

@@ -975,3 +1473,3 @@ self._checking = true;

debugLog(`fs.${methodName} calling original path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
});

@@ -985,8 +1483,8 @@ }

const self = this;
safeOverride(module2, methodName, async function interceptedPromise(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, async function interceptedPromise(path2, ...args) {
const pathString = normalizePathArg(path2);
debugLog(`fsPromises.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
if (self._checking) {
debugLog(`fsPromises.${methodName} SKIP (re-entrancy) path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
}

@@ -996,3 +1494,3 @@ self.eventReporter.intercept(operation, pathString);

debugLog(`fsPromises.${methodName} policy OK path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
});

@@ -1141,7 +1639,7 @@ }

*/
async check(operation, target) {
async check(operation, target, context) {
try {
const result = await this.client.request(
"policy_check",
{ operation, target }
{ operation, target, context }
);

@@ -1190,3 +1688,7 @@ return result;

const prefix = event.type === "allow" ? "\u2713" : event.type === "deny" ? "\u2717" : "\u2022";
console[level](`[AgenShield] ${prefix} ${event.operation}: ${event.target}`);
let detail = `${prefix} ${event.operation}: ${event.target}`;
if (event.policyId) detail += ` [policy:${event.policyId}]`;
if (event.error) detail += ` [reason:${event.error}]`;
if (event.duration) detail += ` [${event.duration}ms]`;
console[level](`[AgenShield] ${detail}`);
}

@@ -1311,2 +1813,9 @@ if (this.queue.length >= 100) {

const config = createConfig(configOverrides);
if (config.logLevel === "debug") {
try {
const safeConfig = { ...config };
console.error("[AgenShield:config]", JSON.stringify(safeConfig, null, 2));
} catch {
}
}
client = new AsyncClient({

@@ -1332,3 +1841,4 @@ socketPath: config.socketPath,

failOpen: config.failOpen,
brokerHttpPort: config.httpPort
brokerHttpPort: config.httpPort,
config
});

@@ -1366,3 +1876,4 @@ installed.fetch.install();

failOpen: config.failOpen,
brokerHttpPort: config.httpPort
brokerHttpPort: config.httpPort,
config
});

@@ -1369,0 +1880,0 @@ installed.childProcess.install();

@@ -1,1 +0,1 @@

{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOrD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAiBtD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAC3C,IAAI,CA6FN;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CA6B5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,WAAW,GAAG,IAAI,CAE9C"}
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOrD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAiBtD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAC3C,IAAI,CAuGN;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CA6B5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,WAAW,GAAG,IAAI,CAE9C"}

@@ -9,2 +9,3 @@ /**

import type { EventReporter } from '../events/reporter.js';
import type { InterceptorConfig } from '../config.js';
export interface BaseInterceptorOptions {

@@ -17,2 +18,4 @@ client: AsyncClient;

brokerHttpPort?: number;
/** Full interceptor config (for seatbelt + context) */
config?: InterceptorConfig;
}

@@ -25,2 +28,3 @@ export declare abstract class BaseInterceptor {

protected installed: boolean;
protected interceptorConfig?: InterceptorConfig;
private brokerHttpPort;

@@ -45,5 +49,9 @@ constructor(options: BaseInterceptorOptions);

/**
* Build execution context from config
*/
protected getBasePolicyExecutionContext(): import('@agenshield/ipc').PolicyExecutionContext | undefined;
/**
* Check policy and handle the result
*/
protected checkPolicy(operation: string, target: string): Promise<void>;
protected checkPolicy(operation: string, target: string, context?: import('@agenshield/ipc').PolicyExecutionContext): Promise<void>;
/**

@@ -50,0 +58,0 @@ * Log a debug message

@@ -1,1 +0,1 @@

{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/interceptors/base.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,CAAC;IACpB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,8BAAsB,eAAe;IACnC,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC;IAC9B,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAC3C,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IACvC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,SAAS,EAAE,OAAO,CAAS;IACrC,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,EAAE,sBAAsB;IAQ3C;;OAEG;IACH,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAe3C;;OAEG;IACH,QAAQ,CAAC,OAAO,IAAI,IAAI;IAExB;;OAEG;IACH,QAAQ,CAAC,SAAS,IAAI,IAAI;IAE1B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;cACa,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IA8ChB;;OAEG;IACH,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAGvC"}
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/interceptors/base.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAItD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,CAAC;IACpB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,8BAAsB,eAAe;IACnC,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC;IAC9B,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAC3C,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IACvC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,SAAS,EAAE,OAAO,CAAS;IACrC,SAAS,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAChD,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,EAAE,sBAAsB;IAS3C;;OAEG;IACH,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAe3C;;OAEG;IACH,QAAQ,CAAC,OAAO,IAAI,IAAI;IAExB;;OAEG;IACH,QAAQ,CAAC,SAAS,IAAI,IAAI;IAE1B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,SAAS,CAAC,6BAA6B,IAAI,OAAO,iBAAiB,EAAE,sBAAsB,GAAG,SAAS;IAWvG;;OAEG;cACa,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,OAAO,iBAAiB,EAAE,sBAAsB,GACzD,OAAO,CAAC,IAAI,CAAC;IA8ChB;;OAEG;IACH,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAGvC"}
/**
* Child Process Interceptor
*
* Intercepts child_process module calls.
* Intercepts child_process module calls with synchronous policy checking
* and optional macOS seatbelt (sandbox-exec) wrapping for approved commands.
*
* ALL methods (spawn, exec, execFile, fork) now perform synchronous policy
* checks before execution. Previously, async methods would fire the original
* call immediately while the policy check ran in the background.
*/

@@ -10,2 +15,4 @@ import { BaseInterceptor, type BaseInterceptorOptions } from './base.js';

private _checking;
private _executing;
private profileManager;
private originalExec;

@@ -20,2 +27,31 @@ private originalExecSync;

uninstall(): void;
/**
* Build execution context from config for RPC calls
*/
private getPolicyExecutionContext;
/**
* Synchronous policy check via SyncClient.
* Returns the full policy result (with sandbox config) or null if broker
* is unavailable and failOpen is true.
*/
private syncPolicyCheck;
/**
* Create a restrictive default sandbox config for fail-open scenarios.
* No network, minimal fs — better than running completely unsandboxed.
*/
private getFailOpenSandbox;
/**
* Resolve the sandbox config to use: from policy result, fail-open default, or null.
*/
private resolveSandbox;
/**
* Wrap a command with sandbox-exec if seatbelt is enabled and sandbox config is present.
* Returns modified { command, args, options } for spawn-style calls.
*/
private wrapWithSeatbelt;
/**
* Wrap a shell command string with sandbox-exec.
* For exec/execSync which take a full command string.
*/
private wrapCommandStringWithSeatbelt;
private createInterceptedExec;

@@ -22,0 +58,0 @@ private createInterceptedExecSync;

@@ -1,1 +0,1 @@

{"version":3,"file":"child-process.d.ts","sourceRoot":"","sources":["../../src/interceptors/child-process.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,WAAW,CAAC;AASzE,qBAAa,uBAAwB,SAAQ,eAAe;IAC1D,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,gBAAgB,CAA6C;IACrE,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,iBAAiB,CAA8C;IACvE,OAAO,CAAC,gBAAgB,CAA6C;IACrE,OAAO,CAAC,YAAY,CAAyC;gBAEjD,OAAO,EAAE,sBAAsB;IAU3C,OAAO,IAAI,IAAI;IAsBf,SAAS,IAAI,IAAI;IAmBjB,OAAO,CAAC,qBAAqB;IAwC7B,OAAO,CAAC,yBAAyB;IAsDjC,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,0BAA0B;IA+DlC,OAAO,CAAC,yBAAyB;IAwBjC,OAAO,CAAC,qBAAqB;CA2B9B"}
{"version":3,"file":"child-process.d.ts","sourceRoot":"","sources":["../../src/interceptors/child-process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAYzE,qBAAa,uBAAwB,SAAQ,eAAe;IAC1D,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,gBAAgB,CAA6C;IACrE,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,iBAAiB,CAA8C;IACvE,OAAO,CAAC,gBAAgB,CAA6C;IACrE,OAAO,CAAC,YAAY,CAAyC;gBAEjD,OAAO,EAAE,sBAAsB;IAkB3C,OAAO,IAAI,IAAI;IAsBf,SAAS,IAAI,IAAI;IAmBjB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAUjC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAkCvB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAgB1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA+CxB;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAsCrC,OAAO,CAAC,qBAAqB;IAsD7B,OAAO,CAAC,yBAAyB;IAwCjC,OAAO,CAAC,sBAAsB;IAiD9B,OAAO,CAAC,0BAA0B;IA0DlC,OAAO,CAAC,yBAAyB;IAoEjC,OAAO,CAAC,qBAAqB;CAyD9B"}

@@ -10,2 +10,6 @@ /**

constructor(options: BaseInterceptorOptions);
/**
* Build execution context from config
*/
private getPolicyExecutionContext;
install(): void;

@@ -12,0 +16,0 @@ uninstall(): void;

@@ -1,1 +0,1 @@

{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/interceptors/fetch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAGzE,qBAAa,gBAAiB,SAAQ,eAAe;IACnD,OAAO,CAAC,aAAa,CAA6B;gBAEtC,OAAO,EAAE,sBAAsB;IAI3C,OAAO,IAAI,IAAI;IAYf,SAAS,IAAI,IAAI;YAQH,gBAAgB;CAiG/B"}
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/interceptors/fetch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAIzE,qBAAa,gBAAiB,SAAQ,eAAe;IACnD,OAAO,CAAC,aAAa,CAA6B;gBAEtC,OAAO,EAAE,sBAAsB;IAI3C;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAWjC,OAAO,IAAI,IAAI;IAYf,SAAS,IAAI,IAAI;YAQH,gBAAgB;CA+C/B"}
{
"name": "@agenshield/interceptor",
"version": "0.6.2",
"version": "0.7.0",
"type": "module",

@@ -28,3 +28,3 @@ "description": "Node.js runtime interception via ESM loader and CJS preload",

"dependencies": {
"@agenshield/ipc": "0.6.2"
"@agenshield/ipc": "0.7.0"
},

@@ -31,0 +31,0 @@ "devDependencies": {

@@ -8,2 +8,3 @@ /**

import type { AsyncClient } from '../client/http-client.js';
import type { SandboxConfig, PolicyExecutionContext } from '@agenshield/ipc';
export interface PolicyEvaluatorOptions {

@@ -16,2 +17,4 @@ client: AsyncClient;

reason?: string;
sandbox?: SandboxConfig;
executionContext?: PolicyExecutionContext;
}

@@ -25,4 +28,4 @@ export declare class PolicyEvaluator {

*/
check(operation: string, target: string): Promise<PolicyCheckResult>;
check(operation: string, target: string, context?: PolicyExecutionContext): Promise<PolicyCheckResult>;
}
//# sourceMappingURL=evaluator.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/policy/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAc;gBAEhB,OAAO,EAAE,sBAAsB;IAI3C;;;OAGG;IACG,KAAK,CACT,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,CAAC;CAgB9B"}
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/policy/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAE7E,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;CAC3C;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAc;gBAEhB,OAAO,EAAE,sBAAsB;IAI3C;;;OAGG;IACG,KAAK,CACT,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;CAgB9B"}
+677
-166

@@ -39,3 +39,8 @@ "use strict";

interceptExec: env["AGENSHIELD_INTERCEPT_EXEC"] !== "false",
timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "30000", 10),
timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "5000", 10),
contextType: env["AGENSHIELD_CONTEXT_TYPE"] || "agent",
contextSkillSlug: env["AGENSHIELD_SKILL_SLUG"],
contextAgentId: env["AGENSHIELD_AGENT_ID"],
enableSeatbelt: env["AGENSHIELD_SEATBELT"] !== "false" && process.platform === "darwin",
seatbeltProfileDir: env["AGENSHIELD_SEATBELT_DIR"] || "/tmp/agenshield-profiles",
...overrides

@@ -84,9 +89,30 @@ };

var _appendFileSync = fs.appendFileSync.bind(fs);
var _writeSync = fs.writeSync.bind(fs);
var LOG_PATH = "/var/log/agenshield/interceptor.log";
var FALLBACK_LOG_PATH = "/tmp/agenshield-interceptor.log";
var resolvedLogPath = null;
function getLogPath() {
if (resolvedLogPath !== null) return resolvedLogPath;
try {
_appendFileSync(LOG_PATH, "");
resolvedLogPath = LOG_PATH;
} catch {
resolvedLogPath = FALLBACK_LOG_PATH;
}
return resolvedLogPath;
}
function debugLog(msg) {
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
`;
try {
_appendFileSync(LOG_PATH, `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
`);
_appendFileSync(getLogPath(), line);
} catch {
}
if (process.env["AGENSHIELD_LOG_LEVEL"] === "debug") {
try {
_writeSync(2, `[AgenShield:debug] ${msg}
`);
} catch {
}
}
}

@@ -101,2 +127,3 @@

installed = false;
interceptorConfig;
brokerHttpPort;

@@ -109,2 +136,3 @@ constructor(options) {

this.brokerHttpPort = options.brokerHttpPort ?? 5201;
this.interceptorConfig = options.config;
}

@@ -135,5 +163,18 @@ /**

/**
* Build execution context from config
*/
getBasePolicyExecutionContext() {
const config = this.interceptorConfig;
if (!config) return void 0;
return {
callerType: config.contextType || "agent",
skillSlug: config.contextSkillSlug,
agentId: config.contextAgentId,
depth: 0
};
}
/**
* Check policy and handle the result
*/
async checkPolicy(operation, target) {
async checkPolicy(operation, target, context) {
const startTime = Date.now();

@@ -143,3 +184,3 @@ debugLog(`base.checkPolicy START op=${operation} target=${target}`);

this.eventReporter.intercept(operation, target);
const result = await this.policyEvaluator.check(operation, target);
const result = await this.policyEvaluator.check(operation, target, context);
debugLog(`base.checkPolicy evaluator result op=${operation} target=${target} allowed=${result.allowed} policyId=${result.policyId}`);

@@ -191,2 +232,15 @@ if (!result.allowed) {

}
/**
* Build execution context from config
*/
getPolicyExecutionContext() {
const config = this.interceptorConfig;
if (!config) return void 0;
return {
callerType: config.contextType || "agent",
skillSlug: config.contextSkillSlug,
agentId: config.contextAgentId,
depth: 0
};
}
install() {

@@ -222,44 +276,6 @@ if (this.installed) return;

debugLog(`fetch checkPolicy START url=${url}`);
await this.checkPolicy("http_request", url);
debugLog(`fetch checkPolicy DONE url=${url}`);
try {
const method = init?.method || "GET";
const headers = {};
if (init?.headers) {
if (init.headers instanceof Headers) {
init.headers.forEach((value, key) => {
headers[key] = value;
});
} else if (Array.isArray(init.headers)) {
for (const [key, value] of init.headers) {
headers[key] = value;
}
} else {
Object.assign(headers, init.headers);
}
}
let body;
if (init?.body) {
if (typeof init.body === "string") {
body = init.body;
} else if (init.body instanceof ArrayBuffer) {
body = Buffer.from(init.body).toString("base64");
} else {
body = String(init.body);
}
}
const result = await this.client.request("http_request", {
url,
method,
headers,
body
});
const responseHeaders = new Headers(result.headers);
return new Response(result.body, {
status: result.status,
statusText: result.statusText,
headers: responseHeaders
});
await this.checkPolicy("http_request", url, this.getPolicyExecutionContext());
debugLog(`fetch checkPolicy DONE url=${url}`);
} catch (error) {
debugLog(`fetch ERROR url=${url} error=${error.message}`);
if (error.name === "PolicyDeniedError") {

@@ -274,2 +290,3 @@ throw error;

}
return this.originalFetch(input, init);
}

@@ -419,2 +436,4 @@ };

var _unlinkSync = fs2.unlinkSync.bind(fs2);
var _readdirSync = fs2.readdirSync.bind(fs2);
var _statSync = fs2.statSync.bind(fs2);
var _spawnSync = import_node_child_process.spawnSync;

@@ -427,2 +446,4 @@ var _execSync = import_node_child_process.execSync;

timeout;
socketFailCount = 0;
socketSkipUntil = 0;
constructor(options) {

@@ -433,4 +454,26 @@ this.socketPath = options.socketPath;

this.timeout = options.timeout;
this.cleanupStaleTmpFiles();
}
/**
* Remove stale /tmp/agenshield-sync-*.json files from previous runs
*/
cleanupStaleTmpFiles() {
try {
const tmpDir = "/tmp";
const files = _readdirSync(tmpDir);
const cutoff = Date.now() - 5 * 60 * 1e3;
for (const f of files) {
if (f.startsWith("agenshield-sync-") && f.endsWith(".json")) {
const fp = `${tmpDir}/${f}`;
try {
const stat = _statSync(fp);
if (stat.mtimeMs < cutoff) _unlinkSync(fp);
} catch {
}
}
}
} catch {
}
}
/**
* Send a synchronous request to the broker

@@ -440,7 +483,20 @@ */

debugLog(`syncClient.request START method=${method}`);
const now = Date.now();
if (now < this.socketSkipUntil) {
debugLog(`syncClient.request SKIP socket (circuit open for ${this.socketSkipUntil - now}ms), using HTTP`);
const result = this.httpRequestSync(method, params);
debugLog(`syncClient.request http OK method=${method}`);
return result;
}
try {
const result = this.socketRequestSync(method, params);
this.socketFailCount = 0;
debugLog(`syncClient.request socket OK method=${method}`);
return result;
} catch (socketErr) {
this.socketFailCount++;
if (this.socketFailCount >= 2) {
this.socketSkipUntil = Date.now() + 6e4;
debugLog(`syncClient.request socket circuit OPEN (${this.socketFailCount} failures)`);
}
debugLog(`syncClient.request socket FAILED: ${socketErr.message}, trying HTTP`);

@@ -470,5 +526,14 @@ const result = this.httpRequestSync(method, params);

let done = false;
const socket = net.createConnection('${this.socketPath}');
let data = '';
const timer = setTimeout(() => {
if (done) return;
done = true;
socket.destroy();
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
process.exit(1);
}, ${this.timeout});
socket.on('connect', () => {

@@ -480,5 +545,8 @@ socket.write(${JSON.stringify(request)});

data += chunk.toString();
if (data.includes('\\n')) {
if (data.includes('\\n') && !done) {
done = true;
clearTimeout(timer);
socket.end();
fs.writeFileSync('${tmpFile}', data.split('\\n')[0]);
process.exit(0);
}

@@ -488,9 +556,8 @@ });

socket.on('error', (err) => {
if (done) return;
done = true;
clearTimeout(timer);
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: err.message }));
process.exit(1);
});
setTimeout(() => {
socket.destroy();
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
}, ${this.timeout});
`;

@@ -572,2 +639,215 @@ try {

// libs/shield-interceptor/src/seatbelt/profile-manager.ts
var fs3 = __toESM(require("node:fs"), 1);
var crypto = __toESM(require("node:crypto"), 1);
var path = __toESM(require("node:path"), 1);
var _mkdirSync = fs3.mkdirSync.bind(fs3);
var _writeFileSync = fs3.writeFileSync.bind(fs3);
var _existsSync2 = fs3.existsSync.bind(fs3);
var _readFileSync2 = fs3.readFileSync.bind(fs3);
var _readdirSync2 = fs3.readdirSync.bind(fs3);
var _statSync2 = fs3.statSync.bind(fs3);
var _unlinkSync2 = fs3.unlinkSync.bind(fs3);
var _chmodSync = fs3.chmodSync.bind(fs3);
var ProfileManager = class {
profileDir;
ensuredDir = false;
constructor(profileDir) {
this.profileDir = profileDir;
}
/**
* Get or create a profile file on disk. Returns the absolute path.
* Uses content-hash naming so identical configs reuse the same file.
*/
getOrCreateProfile(content) {
this.ensureDir();
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
const profilePath = path.join(this.profileDir, `sb-${hash}.sb`);
if (!_existsSync2(profilePath)) {
debugLog(`profile-manager: writing new profile ${profilePath} (${content.length} bytes)`);
_writeFileSync(profilePath, content, { mode: 420 });
}
return profilePath;
}
/**
* Generate an SBPL profile from a SandboxConfig.
*/
generateProfile(sandbox) {
if (sandbox.profileContent) {
return sandbox.profileContent;
}
const lines = [
";; AgenShield dynamic seatbelt profile",
`;; Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
"(version 1)",
"(deny default)",
""
];
lines.push(
";; Filesystem: reads allowed, writes restricted",
"(allow file-read*)",
""
);
const writePaths = ["/tmp", "/private/tmp", "/var/folders"];
if (sandbox.allowedWritePaths.length > 0) {
writePaths.push(...sandbox.allowedWritePaths);
}
lines.push("(allow file-write*");
for (const p of writePaths) {
lines.push(` (subpath "${this.escapeSbpl(p)}")`);
}
lines.push(")");
lines.push("");
lines.push("(allow file-write*");
lines.push(' (literal "/dev/null")');
lines.push(' (literal "/dev/zero")');
lines.push(' (literal "/dev/random")');
lines.push(' (literal "/dev/urandom")');
lines.push(")");
lines.push("");
if (sandbox.deniedPaths.length > 0) {
lines.push(";; Denied paths");
for (const p of sandbox.deniedPaths) {
lines.push(`(deny file-read* file-write* (subpath "${this.escapeSbpl(p)}"))`);
}
lines.push("");
}
lines.push(";; Binary execution (system directories allowed as subpaths)");
lines.push("(allow process-exec");
lines.push(' (subpath "/bin")');
lines.push(' (subpath "/sbin")');
lines.push(' (subpath "/usr/bin")');
lines.push(' (subpath "/usr/sbin")');
lines.push(' (subpath "/usr/local/bin")');
lines.push(' (subpath "/opt/agenshield/bin")');
const coveredSubpaths = ["/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/", "/opt/agenshield/bin/"];
const home = process.env["HOME"];
if (home) {
lines.push(` (subpath "${this.escapeSbpl(home)}/bin")`);
lines.push(` (subpath "${this.escapeSbpl(home)}/homebrew")`);
coveredSubpaths.push(`${home}/bin/`, `${home}/homebrew/`);
}
const nvmDir = process.env["NVM_DIR"] || (home ? `${home}/.nvm` : null);
if (nvmDir) {
lines.push(` (subpath "${this.escapeSbpl(nvmDir)}")`);
coveredSubpaths.push(`${nvmDir}/`);
}
const brewPrefix = process.env["HOMEBREW_PREFIX"];
if (brewPrefix && (!home || !brewPrefix.startsWith(home))) {
lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/bin")`);
lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/lib")`);
coveredSubpaths.push(`${brewPrefix}/bin/`, `${brewPrefix}/lib/`);
}
const uniqueBinaries = [...new Set(sandbox.allowedBinaries)];
for (const bin of uniqueBinaries) {
if (coveredSubpaths.some((dir) => bin === dir || bin.startsWith(dir))) continue;
if (bin.endsWith("/")) {
lines.push(` (subpath "${this.escapeSbpl(bin)}")`);
} else {
lines.push(` (literal "${this.escapeSbpl(bin)}")`);
}
}
lines.push(")");
lines.push("");
const uniqueDenied = [...new Set(sandbox.deniedBinaries)];
if (uniqueDenied.length > 0) {
lines.push(";; Denied binaries");
for (const bin of uniqueDenied) {
lines.push(`(deny process-exec (literal "${this.escapeSbpl(bin)}"))`);
}
lines.push("");
}
lines.push(";; Network");
if (sandbox.networkAllowed) {
if (sandbox.allowedHosts.length > 0 || sandbox.allowedPorts.length > 0) {
lines.push(";; Allow specific network targets");
for (const host of sandbox.allowedHosts) {
lines.push(`(allow network-outbound (remote tcp "${this.escapeSbpl(host)}:*"))`);
}
for (const port of sandbox.allowedPorts) {
lines.push(`(allow network-outbound (remote tcp "*:${port}"))`);
}
const isLocalhostOnly = sandbox.allowedHosts.length > 0 && sandbox.allowedHosts.every((h) => h === "localhost" || h === "127.0.0.1");
if (!isLocalhostOnly) {
lines.push('(allow network-outbound (remote udp "*:53") (remote tcp "*:53"))');
}
} else {
lines.push("(allow network*)");
}
} else {
lines.push("(deny network*)");
}
lines.push("");
lines.push(
";; Broker / local unix sockets",
"(allow network-outbound (remote unix))",
"(allow network-inbound (local unix))",
"(allow file-read* file-write*",
' (subpath "/var/run/agenshield")',
' (subpath "/private/var/run/agenshield"))',
""
);
lines.push(
";; Process management",
"(allow process-fork)",
"(allow signal (target self))",
"(allow sysctl-read)",
""
);
lines.push(
";; Mach IPC",
"(allow mach-lookup)",
""
);
return lines.join("\n");
}
/**
* Remove stale profile files older than maxAgeMs.
*/
cleanup(maxAgeMs) {
if (!_existsSync2(this.profileDir)) return;
try {
const now = Date.now();
const entries = _readdirSync2(this.profileDir);
for (const entry of entries) {
if (!entry.endsWith(".sb")) continue;
const filePath = path.join(this.profileDir, entry);
try {
const stat = _statSync2(filePath);
if (now - stat.mtimeMs > maxAgeMs) {
_unlinkSync2(filePath);
debugLog(`profile-manager: cleaned up stale profile ${filePath}`);
}
} catch {
}
}
} catch {
}
}
/**
* Escape a string for safe inclusion in SBPL
*/
escapeSbpl(s) {
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
/**
* Ensure the profile directory exists
*/
ensureDir() {
if (this.ensuredDir) return;
if (!_existsSync2(this.profileDir)) {
_mkdirSync(this.profileDir, { recursive: true, mode: 1023 });
} else {
try {
const stat = _statSync2(this.profileDir);
if ((stat.mode & 511) !== 511) {
_chmodSync(this.profileDir, 1023);
}
} catch {
}
}
this.ensuredDir = true;
}
};
// libs/shield-interceptor/src/interceptors/child-process.ts

@@ -578,2 +858,5 @@ var childProcessModule = require("node:child_process");

_checking = false;
_executing = false;
// Guards exec→execFile re-entrancy
profileManager = null;
originalExec = null;

@@ -587,9 +870,14 @@ originalExecSync = null;

super(options);
const config = this.interceptorConfig;
this.syncClient = new SyncClient({
socketPath: "/var/run/agenshield/agenshield.sock",
httpHost: "localhost",
httpPort: 5201,
// Broker uses 5201
timeout: 3e4
socketPath: config?.socketPath || "/var/run/agenshield/agenshield.sock",
httpHost: config?.httpHost || "localhost",
httpPort: config?.httpPort || 5201,
timeout: config?.timeout || 3e4
});
if (config?.enableSeatbelt && process.platform === "darwin") {
this.profileManager = new ProfileManager(
config.seatbeltProfileDir || "/tmp/agenshield-profiles"
);
}
}

@@ -628,2 +916,146 @@ install() {

}
/**
* Build execution context from config for RPC calls
*/
getPolicyExecutionContext() {
const config = this.interceptorConfig;
return {
callerType: config?.contextType || "agent",
skillSlug: config?.contextSkillSlug,
agentId: config?.contextAgentId,
depth: 0
};
}
/**
* Synchronous policy check via SyncClient.
* Returns the full policy result (with sandbox config) or null if broker
* is unavailable and failOpen is true.
*/
syncPolicyCheck(fullCommand) {
this._checking = true;
try {
debugLog(`cp.syncPolicyCheck START command=${fullCommand}`);
const context = this.getPolicyExecutionContext();
const result = this.syncClient.request(
"policy_check",
{ operation: "exec", target: fullCommand, context }
);
debugLog(`cp.syncPolicyCheck DONE allowed=${result.allowed} command=${fullCommand}`);
if (!result.allowed) {
throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
operation: "exec",
target: fullCommand,
policyId: result.policyId
});
}
return result;
} catch (error) {
if (error instanceof PolicyDeniedError) {
throw error;
}
debugLog(`cp.syncPolicyCheck ERROR: ${error.message} command=${fullCommand}`);
if (!this.failOpen) {
throw error;
}
return null;
} finally {
this._checking = false;
}
}
/**
* Create a restrictive default sandbox config for fail-open scenarios.
* No network, minimal fs — better than running completely unsandboxed.
*/
getFailOpenSandbox() {
return {
enabled: true,
allowedReadPaths: [],
allowedWritePaths: [],
deniedPaths: [],
networkAllowed: false,
allowedHosts: [],
allowedPorts: [],
allowedBinaries: [],
deniedBinaries: [],
envInjection: {},
envDeny: []
};
}
/**
* Resolve the sandbox config to use: from policy result, fail-open default, or null.
*/
resolveSandbox(policyResult) {
if (policyResult?.sandbox?.enabled) {
return policyResult.sandbox;
}
if (policyResult === null && this.profileManager) {
return this.getFailOpenSandbox();
}
return null;
}
/**
* Wrap a command with sandbox-exec if seatbelt is enabled and sandbox config is present.
* Returns modified { command, args, options } for spawn-style calls.
*/
wrapWithSeatbelt(command, args, options, policyResult) {
const sandbox = this.resolveSandbox(policyResult);
if (!this.profileManager || !sandbox || process.platform !== "darwin") {
return { command, args, options };
}
if (command === "/opt/agenshield/bin/node-bin" || command.endsWith("/node-bin")) {
debugLog(`cp.wrapWithSeatbelt: SKIP node-bin (already intercepted) command=${command}`);
return { command, args, options };
}
if (command === "/usr/bin/sandbox-exec" || command.endsWith("/sandbox-exec")) {
debugLog(`cp.wrapWithSeatbelt: SKIP already sandbox-exec command=${command}`);
return { command, args, options };
}
debugLog(`cp.wrapWithSeatbelt: wrapping command=${command}`);
const profileContent = this.profileManager.generateProfile(sandbox);
const profilePath = this.profileManager.getOrCreateProfile(profileContent);
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
return {
command: "/usr/bin/sandbox-exec",
args: ["-f", profilePath, command, ...args],
options: { ...options, env }
};
}
/**
* Wrap a shell command string with sandbox-exec.
* For exec/execSync which take a full command string.
*/
wrapCommandStringWithSeatbelt(command, options, policyResult) {
const sandbox = this.resolveSandbox(policyResult);
if (!this.profileManager || !sandbox || process.platform !== "darwin") {
return { command, options };
}
if (command.startsWith("/usr/bin/sandbox-exec ") || command.startsWith("sandbox-exec ")) {
debugLog(`cp.wrapCommandStringWithSeatbelt: SKIP already sandbox-exec command=${command}`);
return { command, options };
}
debugLog(`cp.wrapCommandStringWithSeatbelt: wrapping command=${command}`);
const profileContent = this.profileManager.generateProfile(sandbox);
const profilePath = this.profileManager.getOrCreateProfile(profileContent);
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
return {
command: `/usr/bin/sandbox-exec -f ${profilePath} ${command}`,
options: { ...options, env }
};
}
createInterceptedExec() {

@@ -634,4 +1066,5 @@ const self = this;

const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
debugLog(`cp.exec ENTER command=${command} _checking=${self._checking}`);
if (self._checking) {
const options = args[0];
debugLog(`cp.exec ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.exec SKIP (re-entrancy) command=${command}`);

@@ -641,10 +1074,22 @@ return original(command, ...args, callback);

self.eventReporter.intercept("exec", command);
self.checkPolicy("exec", command).then(() => {
original(command, ...args, callback);
}).catch((error) => {
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(command);
} catch (error) {
if (callback) {
callback(error, "", "");
process.nextTick(() => callback(error, "", ""));
}
});
return original('echo ""');
return original('echo ""');
}
const wrapped = self.wrapCommandStringWithSeatbelt(command, options, policyResult);
debugLog(`cp.exec calling original command=${wrapped.command}`);
self._executing = true;
try {
if (wrapped.options) {
return original(wrapped.command, wrapped.options, callback);
}
return original(wrapped.command, callback);
} finally {
self._executing = false;
}
};

@@ -656,35 +1101,21 @@ }

const interceptedExecSync = function(command, options) {
debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.execSync SKIP (re-entrancy) command=${command}`);
return original(command, options);
}
self._checking = true;
self.eventReporter.intercept("exec", command);
const policyResult = self.syncPolicyCheck(command);
const wrapped = self.wrapCommandStringWithSeatbelt(
command,
options,
policyResult
);
debugLog(`cp.execSync calling original command=${wrapped.command}`);
self._executing = true;
try {
self.eventReporter.intercept("exec", command);
debugLog(`cp.execSync policy_check START command=${command}`);
const result = self.syncClient.request(
"policy_check",
{ operation: "exec", target: command }
);
debugLog(`cp.execSync policy_check DONE allowed=${result.allowed} command=${command}`);
if (!result.allowed) {
throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
operation: "exec",
target: command
});
}
} catch (error) {
debugLog(`cp.execSync policy_check ERROR: ${error.message} command=${command}`);
if (error instanceof PolicyDeniedError) {
throw error;
}
if (!self.failOpen) {
throw error;
}
return original(wrapped.command, wrapped.options);
} finally {
self._checking = false;
self._executing = false;
}
debugLog(`cp.execSync calling original command=${command}`);
return original(command, options);
};

@@ -698,13 +1129,27 @@ return interceptedExecSync;

const fullCmd = args ? `${command} ${args.join(" ")}` : command;
debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.spawn SKIP (re-entrancy) command=${fullCmd}`);
return original(command, args, options || {});
}
const fullCommand = args ? `${command} ${args.join(" ")}` : command;
self.eventReporter.intercept("exec", fullCommand);
self.checkPolicy("exec", fullCommand).catch((error) => {
self.eventReporter.error("exec", fullCommand, error.message);
});
return original(command, args, options || {});
self.eventReporter.intercept("exec", fullCmd);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCmd);
} catch (error) {
debugLog(`cp.spawn DENIED command=${fullCmd}`);
const denied = original("false", [], { stdio: "pipe" });
process.nextTick(() => {
denied.emit("error", error);
});
return denied;
}
const wrapped = self.wrapWithSeatbelt(
command,
Array.from(args || []),
options,
policyResult
);
debugLog(`cp.spawn calling original command=${wrapped.command} args=${wrapped.args.join(" ")}`);
return original(wrapped.command, wrapped.args, wrapped.options || {});
};

@@ -718,45 +1163,37 @@ return interceptedSpawn;

const fullCommand = args ? `${command} ${args.join(" ")}` : command;
debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.spawnSync SKIP (re-entrancy) command=${fullCommand}`);
return original(command, args, options);
}
self._checking = true;
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
self.eventReporter.intercept("exec", fullCommand);
debugLog(`cp.spawnSync policy_check START command=${fullCommand}`);
const result = self.syncClient.request(
"policy_check",
{ operation: "exec", target: fullCommand }
);
debugLog(`cp.spawnSync policy_check DONE allowed=${result.allowed} command=${fullCommand}`);
if (!result.allowed) {
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(result.reason || "Policy denied"),
status: 1,
signal: null,
error: new PolicyDeniedError(result.reason || "Policy denied")
};
}
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
debugLog(`cp.spawnSync policy_check ERROR: ${error.message} command=${fullCommand}`);
if (!self.failOpen) {
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(error.message),
status: 1,
signal: null,
error
};
}
} finally {
self._checking = false;
debugLog(`cp.spawnSync DENIED command=${fullCommand}`);
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(
error instanceof PolicyDeniedError ? error.message || "Policy denied" : error.message
),
status: 1,
signal: null,
error
};
}
debugLog(`cp.spawnSync calling original command=${fullCommand}`);
return original(command, args, options);
const wrapped = self.wrapWithSeatbelt(
command,
Array.from(args || []),
options,
policyResult
);
debugLog(`cp.spawnSync calling original command=${wrapped.command}`);
return original(
wrapped.command,
wrapped.args,
wrapped.options
);
};

@@ -767,11 +1204,48 @@ }

const original = this.originalExecFile;
return function interceptedExecFile(file, ...args) {
if (self._checking) {
return original(file, ...args);
return function interceptedExecFile(file, ...rest) {
if (self._checking || self._executing) {
return original(file, ...rest);
}
self.eventReporter.intercept("exec", file);
self.checkPolicy("exec", file).catch((error) => {
self.eventReporter.error("exec", file, error.message);
});
return original(file, ...args);
let args = [];
let options;
let callback;
for (const arg of rest) {
if (typeof arg === "function") {
callback = arg;
} else if (Array.isArray(arg)) {
args = arg;
} else if (typeof arg === "object" && arg !== null) {
options = arg;
}
}
const fullCommand = args.length > 0 ? `${file} ${args.join(" ")}` : file;
debugLog(`cp.execFile ENTER command=${fullCommand}`);
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
if (callback) {
process.nextTick(() => callback(error, "", ""));
}
return original("false");
}
const wrapped = self.wrapWithSeatbelt(file, args, options, policyResult);
debugLog(`cp.execFile calling original command=${wrapped.command}`);
if (callback) {
return original(
wrapped.command,
wrapped.args,
wrapped.options,
callback
);
}
return original(
wrapped.command,
wrapped.args,
wrapped.options || {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
() => {
}
);
};

@@ -783,10 +1257,34 @@ }

const interceptedFork = function(modulePath, args, options) {
if (self._checking) {
if (self._checking || self._executing) {
return original(modulePath, args, options);
}
const pathStr = modulePath.toString();
self.eventReporter.intercept("exec", `fork:${pathStr}`);
self.checkPolicy("exec", `fork:${pathStr}`).catch((error) => {
self.eventReporter.error("exec", pathStr, error.message);
});
const fullCommand = `fork:${pathStr}`;
debugLog(`cp.fork ENTER command=${fullCommand}`);
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
debugLog(`cp.fork DENIED command=${fullCommand}`);
const denied = self.originalSpawn("false", [], { stdio: "pipe" });
process.nextTick(() => {
denied.emit("error", error);
});
return denied;
}
if (policyResult?.sandbox) {
const sandbox = policyResult.sandbox;
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
options = { ...options, env };
}
debugLog(`cp.fork calling original module=${pathStr}`);
return original(modulePath, args, options);

@@ -885,4 +1383,4 @@ };

const self = this;
safeOverride(module2, methodName, function intercepted(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, function intercepted(path2, ...args) {
const pathString = normalizePathArg(path2);
const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;

@@ -892,3 +1390,3 @@ debugLog(`fs.${methodName} ENTER (async) path=${pathString} _checking=${self._checking}`);

debugLog(`fs.${methodName} SKIP (re-entrancy, async) path=${pathString}`);
original.call(module2, path, ...args, callback);
original.call(module2, path2, ...args, callback);
return;

@@ -899,3 +1397,3 @@ }

debugLog(`fs.${methodName} policy OK (async) path=${pathString}`);
original.call(module2, path, ...args, callback);
original.call(module2, path2, ...args, callback);
}).catch((error) => {

@@ -915,8 +1413,8 @@ debugLog(`fs.${methodName} policy ERROR (async): ${error.message} path=${pathString}`);

const self = this;
safeOverride(module2, methodName, function interceptedSync(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, function interceptedSync(path2, ...args) {
const pathString = normalizePathArg(path2);
debugLog(`fs.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
if (self._checking) {
debugLog(`fs.${methodName} SKIP (re-entrancy) path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
}

@@ -950,3 +1448,3 @@ self._checking = true;

debugLog(`fs.${methodName} calling original path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
});

@@ -960,8 +1458,8 @@ }

const self = this;
safeOverride(module2, methodName, async function interceptedPromise(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, async function interceptedPromise(path2, ...args) {
const pathString = normalizePathArg(path2);
debugLog(`fsPromises.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
if (self._checking) {
debugLog(`fsPromises.${methodName} SKIP (re-entrancy) path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
}

@@ -971,3 +1469,3 @@ self.eventReporter.intercept(operation, pathString);

debugLog(`fsPromises.${methodName} policy OK path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
});

@@ -1116,7 +1614,7 @@ }

*/
async check(operation, target) {
async check(operation, target, context) {
try {
const result = await this.client.request(
"policy_check",
{ operation, target }
{ operation, target, context }
);

@@ -1165,3 +1663,7 @@ return result;

const prefix = event.type === "allow" ? "\u2713" : event.type === "deny" ? "\u2717" : "\u2022";
console[level](`[AgenShield] ${prefix} ${event.operation}: ${event.target}`);
let detail = `${prefix} ${event.operation}: ${event.target}`;
if (event.policyId) detail += ` [policy:${event.policyId}]`;
if (event.error) detail += ` [reason:${event.error}]`;
if (event.duration) detail += ` [${event.duration}ms]`;
console[level](`[AgenShield] ${detail}`);
}

@@ -1286,2 +1788,9 @@ if (this.queue.length >= 100) {

const config = createConfig(configOverrides);
if (config.logLevel === "debug") {
try {
const safeConfig = { ...config };
console.error("[AgenShield:config]", JSON.stringify(safeConfig, null, 2));
} catch {
}
}
client = new AsyncClient({

@@ -1307,3 +1816,4 @@ socketPath: config.socketPath,

failOpen: config.failOpen,
brokerHttpPort: config.httpPort
brokerHttpPort: config.httpPort,
config
});

@@ -1341,3 +1851,4 @@ installed.fetch.install();

failOpen: config.failOpen,
brokerHttpPort: config.httpPort
brokerHttpPort: config.httpPort,
config
});

@@ -1344,0 +1855,0 @@ installed.childProcess.install();

+677
-166

@@ -39,3 +39,8 @@ "use strict";

interceptExec: env["AGENSHIELD_INTERCEPT_EXEC"] !== "false",
timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "30000", 10),
timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "5000", 10),
contextType: env["AGENSHIELD_CONTEXT_TYPE"] || "agent",
contextSkillSlug: env["AGENSHIELD_SKILL_SLUG"],
contextAgentId: env["AGENSHIELD_AGENT_ID"],
enableSeatbelt: env["AGENSHIELD_SEATBELT"] !== "false" && process.platform === "darwin",
seatbeltProfileDir: env["AGENSHIELD_SEATBELT_DIR"] || "/tmp/agenshield-profiles",
...overrides

@@ -84,9 +89,30 @@ };

var _appendFileSync = fs.appendFileSync.bind(fs);
var _writeSync = fs.writeSync.bind(fs);
var LOG_PATH = "/var/log/agenshield/interceptor.log";
var FALLBACK_LOG_PATH = "/tmp/agenshield-interceptor.log";
var resolvedLogPath = null;
function getLogPath() {
if (resolvedLogPath !== null) return resolvedLogPath;
try {
_appendFileSync(LOG_PATH, "");
resolvedLogPath = LOG_PATH;
} catch {
resolvedLogPath = FALLBACK_LOG_PATH;
}
return resolvedLogPath;
}
function debugLog(msg) {
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
`;
try {
_appendFileSync(LOG_PATH, `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
`);
_appendFileSync(getLogPath(), line);
} catch {
}
if (process.env["AGENSHIELD_LOG_LEVEL"] === "debug") {
try {
_writeSync(2, `[AgenShield:debug] ${msg}
`);
} catch {
}
}
}

@@ -101,2 +127,3 @@

installed = false;
interceptorConfig;
brokerHttpPort;

@@ -109,2 +136,3 @@ constructor(options) {

this.brokerHttpPort = options.brokerHttpPort ?? 5201;
this.interceptorConfig = options.config;
}

@@ -135,5 +163,18 @@ /**

/**
* Build execution context from config
*/
getBasePolicyExecutionContext() {
const config = this.interceptorConfig;
if (!config) return void 0;
return {
callerType: config.contextType || "agent",
skillSlug: config.contextSkillSlug,
agentId: config.contextAgentId,
depth: 0
};
}
/**
* Check policy and handle the result
*/
async checkPolicy(operation, target) {
async checkPolicy(operation, target, context) {
const startTime = Date.now();

@@ -143,3 +184,3 @@ debugLog(`base.checkPolicy START op=${operation} target=${target}`);

this.eventReporter.intercept(operation, target);
const result = await this.policyEvaluator.check(operation, target);
const result = await this.policyEvaluator.check(operation, target, context);
debugLog(`base.checkPolicy evaluator result op=${operation} target=${target} allowed=${result.allowed} policyId=${result.policyId}`);

@@ -191,2 +232,15 @@ if (!result.allowed) {

}
/**
* Build execution context from config
*/
getPolicyExecutionContext() {
const config = this.interceptorConfig;
if (!config) return void 0;
return {
callerType: config.contextType || "agent",
skillSlug: config.contextSkillSlug,
agentId: config.contextAgentId,
depth: 0
};
}
install() {

@@ -222,44 +276,6 @@ if (this.installed) return;

debugLog(`fetch checkPolicy START url=${url}`);
await this.checkPolicy("http_request", url);
debugLog(`fetch checkPolicy DONE url=${url}`);
try {
const method = init?.method || "GET";
const headers = {};
if (init?.headers) {
if (init.headers instanceof Headers) {
init.headers.forEach((value, key) => {
headers[key] = value;
});
} else if (Array.isArray(init.headers)) {
for (const [key, value] of init.headers) {
headers[key] = value;
}
} else {
Object.assign(headers, init.headers);
}
}
let body;
if (init?.body) {
if (typeof init.body === "string") {
body = init.body;
} else if (init.body instanceof ArrayBuffer) {
body = Buffer.from(init.body).toString("base64");
} else {
body = String(init.body);
}
}
const result = await this.client.request("http_request", {
url,
method,
headers,
body
});
const responseHeaders = new Headers(result.headers);
return new Response(result.body, {
status: result.status,
statusText: result.statusText,
headers: responseHeaders
});
await this.checkPolicy("http_request", url, this.getPolicyExecutionContext());
debugLog(`fetch checkPolicy DONE url=${url}`);
} catch (error) {
debugLog(`fetch ERROR url=${url} error=${error.message}`);
if (error.name === "PolicyDeniedError") {

@@ -274,2 +290,3 @@ throw error;

}
return this.originalFetch(input, init);
}

@@ -419,2 +436,4 @@ };

var _unlinkSync = fs2.unlinkSync.bind(fs2);
var _readdirSync = fs2.readdirSync.bind(fs2);
var _statSync = fs2.statSync.bind(fs2);
var _spawnSync = import_node_child_process.spawnSync;

@@ -427,2 +446,4 @@ var _execSync = import_node_child_process.execSync;

timeout;
socketFailCount = 0;
socketSkipUntil = 0;
constructor(options) {

@@ -433,4 +454,26 @@ this.socketPath = options.socketPath;

this.timeout = options.timeout;
this.cleanupStaleTmpFiles();
}
/**
* Remove stale /tmp/agenshield-sync-*.json files from previous runs
*/
cleanupStaleTmpFiles() {
try {
const tmpDir = "/tmp";
const files = _readdirSync(tmpDir);
const cutoff = Date.now() - 5 * 60 * 1e3;
for (const f of files) {
if (f.startsWith("agenshield-sync-") && f.endsWith(".json")) {
const fp = `${tmpDir}/${f}`;
try {
const stat = _statSync(fp);
if (stat.mtimeMs < cutoff) _unlinkSync(fp);
} catch {
}
}
}
} catch {
}
}
/**
* Send a synchronous request to the broker

@@ -440,7 +483,20 @@ */

debugLog(`syncClient.request START method=${method}`);
const now = Date.now();
if (now < this.socketSkipUntil) {
debugLog(`syncClient.request SKIP socket (circuit open for ${this.socketSkipUntil - now}ms), using HTTP`);
const result = this.httpRequestSync(method, params);
debugLog(`syncClient.request http OK method=${method}`);
return result;
}
try {
const result = this.socketRequestSync(method, params);
this.socketFailCount = 0;
debugLog(`syncClient.request socket OK method=${method}`);
return result;
} catch (socketErr) {
this.socketFailCount++;
if (this.socketFailCount >= 2) {
this.socketSkipUntil = Date.now() + 6e4;
debugLog(`syncClient.request socket circuit OPEN (${this.socketFailCount} failures)`);
}
debugLog(`syncClient.request socket FAILED: ${socketErr.message}, trying HTTP`);

@@ -470,5 +526,14 @@ const result = this.httpRequestSync(method, params);

let done = false;
const socket = net.createConnection('${this.socketPath}');
let data = '';
const timer = setTimeout(() => {
if (done) return;
done = true;
socket.destroy();
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
process.exit(1);
}, ${this.timeout});
socket.on('connect', () => {

@@ -480,5 +545,8 @@ socket.write(${JSON.stringify(request)});

data += chunk.toString();
if (data.includes('\\n')) {
if (data.includes('\\n') && !done) {
done = true;
clearTimeout(timer);
socket.end();
fs.writeFileSync('${tmpFile}', data.split('\\n')[0]);
process.exit(0);
}

@@ -488,9 +556,8 @@ });

socket.on('error', (err) => {
if (done) return;
done = true;
clearTimeout(timer);
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: err.message }));
process.exit(1);
});
setTimeout(() => {
socket.destroy();
fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
}, ${this.timeout});
`;

@@ -572,2 +639,215 @@ try {

// libs/shield-interceptor/src/seatbelt/profile-manager.ts
var fs3 = __toESM(require("node:fs"), 1);
var crypto = __toESM(require("node:crypto"), 1);
var path = __toESM(require("node:path"), 1);
var _mkdirSync = fs3.mkdirSync.bind(fs3);
var _writeFileSync = fs3.writeFileSync.bind(fs3);
var _existsSync2 = fs3.existsSync.bind(fs3);
var _readFileSync2 = fs3.readFileSync.bind(fs3);
var _readdirSync2 = fs3.readdirSync.bind(fs3);
var _statSync2 = fs3.statSync.bind(fs3);
var _unlinkSync2 = fs3.unlinkSync.bind(fs3);
var _chmodSync = fs3.chmodSync.bind(fs3);
var ProfileManager = class {
profileDir;
ensuredDir = false;
constructor(profileDir) {
this.profileDir = profileDir;
}
/**
* Get or create a profile file on disk. Returns the absolute path.
* Uses content-hash naming so identical configs reuse the same file.
*/
getOrCreateProfile(content) {
this.ensureDir();
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
const profilePath = path.join(this.profileDir, `sb-${hash}.sb`);
if (!_existsSync2(profilePath)) {
debugLog(`profile-manager: writing new profile ${profilePath} (${content.length} bytes)`);
_writeFileSync(profilePath, content, { mode: 420 });
}
return profilePath;
}
/**
* Generate an SBPL profile from a SandboxConfig.
*/
generateProfile(sandbox) {
if (sandbox.profileContent) {
return sandbox.profileContent;
}
const lines = [
";; AgenShield dynamic seatbelt profile",
`;; Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
"(version 1)",
"(deny default)",
""
];
lines.push(
";; Filesystem: reads allowed, writes restricted",
"(allow file-read*)",
""
);
const writePaths = ["/tmp", "/private/tmp", "/var/folders"];
if (sandbox.allowedWritePaths.length > 0) {
writePaths.push(...sandbox.allowedWritePaths);
}
lines.push("(allow file-write*");
for (const p of writePaths) {
lines.push(` (subpath "${this.escapeSbpl(p)}")`);
}
lines.push(")");
lines.push("");
lines.push("(allow file-write*");
lines.push(' (literal "/dev/null")');
lines.push(' (literal "/dev/zero")');
lines.push(' (literal "/dev/random")');
lines.push(' (literal "/dev/urandom")');
lines.push(")");
lines.push("");
if (sandbox.deniedPaths.length > 0) {
lines.push(";; Denied paths");
for (const p of sandbox.deniedPaths) {
lines.push(`(deny file-read* file-write* (subpath "${this.escapeSbpl(p)}"))`);
}
lines.push("");
}
lines.push(";; Binary execution (system directories allowed as subpaths)");
lines.push("(allow process-exec");
lines.push(' (subpath "/bin")');
lines.push(' (subpath "/sbin")');
lines.push(' (subpath "/usr/bin")');
lines.push(' (subpath "/usr/sbin")');
lines.push(' (subpath "/usr/local/bin")');
lines.push(' (subpath "/opt/agenshield/bin")');
const coveredSubpaths = ["/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/", "/opt/agenshield/bin/"];
const home = process.env["HOME"];
if (home) {
lines.push(` (subpath "${this.escapeSbpl(home)}/bin")`);
lines.push(` (subpath "${this.escapeSbpl(home)}/homebrew")`);
coveredSubpaths.push(`${home}/bin/`, `${home}/homebrew/`);
}
const nvmDir = process.env["NVM_DIR"] || (home ? `${home}/.nvm` : null);
if (nvmDir) {
lines.push(` (subpath "${this.escapeSbpl(nvmDir)}")`);
coveredSubpaths.push(`${nvmDir}/`);
}
const brewPrefix = process.env["HOMEBREW_PREFIX"];
if (brewPrefix && (!home || !brewPrefix.startsWith(home))) {
lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/bin")`);
lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/lib")`);
coveredSubpaths.push(`${brewPrefix}/bin/`, `${brewPrefix}/lib/`);
}
const uniqueBinaries = [...new Set(sandbox.allowedBinaries)];
for (const bin of uniqueBinaries) {
if (coveredSubpaths.some((dir) => bin === dir || bin.startsWith(dir))) continue;
if (bin.endsWith("/")) {
lines.push(` (subpath "${this.escapeSbpl(bin)}")`);
} else {
lines.push(` (literal "${this.escapeSbpl(bin)}")`);
}
}
lines.push(")");
lines.push("");
const uniqueDenied = [...new Set(sandbox.deniedBinaries)];
if (uniqueDenied.length > 0) {
lines.push(";; Denied binaries");
for (const bin of uniqueDenied) {
lines.push(`(deny process-exec (literal "${this.escapeSbpl(bin)}"))`);
}
lines.push("");
}
lines.push(";; Network");
if (sandbox.networkAllowed) {
if (sandbox.allowedHosts.length > 0 || sandbox.allowedPorts.length > 0) {
lines.push(";; Allow specific network targets");
for (const host of sandbox.allowedHosts) {
lines.push(`(allow network-outbound (remote tcp "${this.escapeSbpl(host)}:*"))`);
}
for (const port of sandbox.allowedPorts) {
lines.push(`(allow network-outbound (remote tcp "*:${port}"))`);
}
const isLocalhostOnly = sandbox.allowedHosts.length > 0 && sandbox.allowedHosts.every((h) => h === "localhost" || h === "127.0.0.1");
if (!isLocalhostOnly) {
lines.push('(allow network-outbound (remote udp "*:53") (remote tcp "*:53"))');
}
} else {
lines.push("(allow network*)");
}
} else {
lines.push("(deny network*)");
}
lines.push("");
lines.push(
";; Broker / local unix sockets",
"(allow network-outbound (remote unix))",
"(allow network-inbound (local unix))",
"(allow file-read* file-write*",
' (subpath "/var/run/agenshield")',
' (subpath "/private/var/run/agenshield"))',
""
);
lines.push(
";; Process management",
"(allow process-fork)",
"(allow signal (target self))",
"(allow sysctl-read)",
""
);
lines.push(
";; Mach IPC",
"(allow mach-lookup)",
""
);
return lines.join("\n");
}
/**
* Remove stale profile files older than maxAgeMs.
*/
cleanup(maxAgeMs) {
if (!_existsSync2(this.profileDir)) return;
try {
const now = Date.now();
const entries = _readdirSync2(this.profileDir);
for (const entry of entries) {
if (!entry.endsWith(".sb")) continue;
const filePath = path.join(this.profileDir, entry);
try {
const stat = _statSync2(filePath);
if (now - stat.mtimeMs > maxAgeMs) {
_unlinkSync2(filePath);
debugLog(`profile-manager: cleaned up stale profile ${filePath}`);
}
} catch {
}
}
} catch {
}
}
/**
* Escape a string for safe inclusion in SBPL
*/
escapeSbpl(s) {
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
/**
* Ensure the profile directory exists
*/
ensureDir() {
if (this.ensuredDir) return;
if (!_existsSync2(this.profileDir)) {
_mkdirSync(this.profileDir, { recursive: true, mode: 1023 });
} else {
try {
const stat = _statSync2(this.profileDir);
if ((stat.mode & 511) !== 511) {
_chmodSync(this.profileDir, 1023);
}
} catch {
}
}
this.ensuredDir = true;
}
};
// libs/shield-interceptor/src/interceptors/child-process.ts

@@ -578,2 +858,5 @@ var childProcessModule = require("node:child_process");

_checking = false;
_executing = false;
// Guards exec→execFile re-entrancy
profileManager = null;
originalExec = null;

@@ -587,9 +870,14 @@ originalExecSync = null;

super(options);
const config = this.interceptorConfig;
this.syncClient = new SyncClient({
socketPath: "/var/run/agenshield/agenshield.sock",
httpHost: "localhost",
httpPort: 5201,
// Broker uses 5201
timeout: 3e4
socketPath: config?.socketPath || "/var/run/agenshield/agenshield.sock",
httpHost: config?.httpHost || "localhost",
httpPort: config?.httpPort || 5201,
timeout: config?.timeout || 3e4
});
if (config?.enableSeatbelt && process.platform === "darwin") {
this.profileManager = new ProfileManager(
config.seatbeltProfileDir || "/tmp/agenshield-profiles"
);
}
}

@@ -628,2 +916,146 @@ install() {

}
/**
* Build execution context from config for RPC calls
*/
getPolicyExecutionContext() {
const config = this.interceptorConfig;
return {
callerType: config?.contextType || "agent",
skillSlug: config?.contextSkillSlug,
agentId: config?.contextAgentId,
depth: 0
};
}
/**
* Synchronous policy check via SyncClient.
* Returns the full policy result (with sandbox config) or null if broker
* is unavailable and failOpen is true.
*/
syncPolicyCheck(fullCommand) {
this._checking = true;
try {
debugLog(`cp.syncPolicyCheck START command=${fullCommand}`);
const context = this.getPolicyExecutionContext();
const result = this.syncClient.request(
"policy_check",
{ operation: "exec", target: fullCommand, context }
);
debugLog(`cp.syncPolicyCheck DONE allowed=${result.allowed} command=${fullCommand}`);
if (!result.allowed) {
throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
operation: "exec",
target: fullCommand,
policyId: result.policyId
});
}
return result;
} catch (error) {
if (error instanceof PolicyDeniedError) {
throw error;
}
debugLog(`cp.syncPolicyCheck ERROR: ${error.message} command=${fullCommand}`);
if (!this.failOpen) {
throw error;
}
return null;
} finally {
this._checking = false;
}
}
/**
* Create a restrictive default sandbox config for fail-open scenarios.
* No network, minimal fs — better than running completely unsandboxed.
*/
getFailOpenSandbox() {
return {
enabled: true,
allowedReadPaths: [],
allowedWritePaths: [],
deniedPaths: [],
networkAllowed: false,
allowedHosts: [],
allowedPorts: [],
allowedBinaries: [],
deniedBinaries: [],
envInjection: {},
envDeny: []
};
}
/**
* Resolve the sandbox config to use: from policy result, fail-open default, or null.
*/
resolveSandbox(policyResult) {
if (policyResult?.sandbox?.enabled) {
return policyResult.sandbox;
}
if (policyResult === null && this.profileManager) {
return this.getFailOpenSandbox();
}
return null;
}
/**
* Wrap a command with sandbox-exec if seatbelt is enabled and sandbox config is present.
* Returns modified { command, args, options } for spawn-style calls.
*/
wrapWithSeatbelt(command, args, options, policyResult) {
const sandbox = this.resolveSandbox(policyResult);
if (!this.profileManager || !sandbox || process.platform !== "darwin") {
return { command, args, options };
}
if (command === "/opt/agenshield/bin/node-bin" || command.endsWith("/node-bin")) {
debugLog(`cp.wrapWithSeatbelt: SKIP node-bin (already intercepted) command=${command}`);
return { command, args, options };
}
if (command === "/usr/bin/sandbox-exec" || command.endsWith("/sandbox-exec")) {
debugLog(`cp.wrapWithSeatbelt: SKIP already sandbox-exec command=${command}`);
return { command, args, options };
}
debugLog(`cp.wrapWithSeatbelt: wrapping command=${command}`);
const profileContent = this.profileManager.generateProfile(sandbox);
const profilePath = this.profileManager.getOrCreateProfile(profileContent);
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
return {
command: "/usr/bin/sandbox-exec",
args: ["-f", profilePath, command, ...args],
options: { ...options, env }
};
}
/**
* Wrap a shell command string with sandbox-exec.
* For exec/execSync which take a full command string.
*/
wrapCommandStringWithSeatbelt(command, options, policyResult) {
const sandbox = this.resolveSandbox(policyResult);
if (!this.profileManager || !sandbox || process.platform !== "darwin") {
return { command, options };
}
if (command.startsWith("/usr/bin/sandbox-exec ") || command.startsWith("sandbox-exec ")) {
debugLog(`cp.wrapCommandStringWithSeatbelt: SKIP already sandbox-exec command=${command}`);
return { command, options };
}
debugLog(`cp.wrapCommandStringWithSeatbelt: wrapping command=${command}`);
const profileContent = this.profileManager.generateProfile(sandbox);
const profilePath = this.profileManager.getOrCreateProfile(profileContent);
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
return {
command: `/usr/bin/sandbox-exec -f ${profilePath} ${command}`,
options: { ...options, env }
};
}
createInterceptedExec() {

@@ -634,4 +1066,5 @@ const self = this;

const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
debugLog(`cp.exec ENTER command=${command} _checking=${self._checking}`);
if (self._checking) {
const options = args[0];
debugLog(`cp.exec ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.exec SKIP (re-entrancy) command=${command}`);

@@ -641,10 +1074,22 @@ return original(command, ...args, callback);

self.eventReporter.intercept("exec", command);
self.checkPolicy("exec", command).then(() => {
original(command, ...args, callback);
}).catch((error) => {
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(command);
} catch (error) {
if (callback) {
callback(error, "", "");
process.nextTick(() => callback(error, "", ""));
}
});
return original('echo ""');
return original('echo ""');
}
const wrapped = self.wrapCommandStringWithSeatbelt(command, options, policyResult);
debugLog(`cp.exec calling original command=${wrapped.command}`);
self._executing = true;
try {
if (wrapped.options) {
return original(wrapped.command, wrapped.options, callback);
}
return original(wrapped.command, callback);
} finally {
self._executing = false;
}
};

@@ -656,35 +1101,21 @@ }

const interceptedExecSync = function(command, options) {
debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.execSync SKIP (re-entrancy) command=${command}`);
return original(command, options);
}
self._checking = true;
self.eventReporter.intercept("exec", command);
const policyResult = self.syncPolicyCheck(command);
const wrapped = self.wrapCommandStringWithSeatbelt(
command,
options,
policyResult
);
debugLog(`cp.execSync calling original command=${wrapped.command}`);
self._executing = true;
try {
self.eventReporter.intercept("exec", command);
debugLog(`cp.execSync policy_check START command=${command}`);
const result = self.syncClient.request(
"policy_check",
{ operation: "exec", target: command }
);
debugLog(`cp.execSync policy_check DONE allowed=${result.allowed} command=${command}`);
if (!result.allowed) {
throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
operation: "exec",
target: command
});
}
} catch (error) {
debugLog(`cp.execSync policy_check ERROR: ${error.message} command=${command}`);
if (error instanceof PolicyDeniedError) {
throw error;
}
if (!self.failOpen) {
throw error;
}
return original(wrapped.command, wrapped.options);
} finally {
self._checking = false;
self._executing = false;
}
debugLog(`cp.execSync calling original command=${command}`);
return original(command, options);
};

@@ -698,13 +1129,27 @@ return interceptedExecSync;

const fullCmd = args ? `${command} ${args.join(" ")}` : command;
debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.spawn SKIP (re-entrancy) command=${fullCmd}`);
return original(command, args, options || {});
}
const fullCommand = args ? `${command} ${args.join(" ")}` : command;
self.eventReporter.intercept("exec", fullCommand);
self.checkPolicy("exec", fullCommand).catch((error) => {
self.eventReporter.error("exec", fullCommand, error.message);
});
return original(command, args, options || {});
self.eventReporter.intercept("exec", fullCmd);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCmd);
} catch (error) {
debugLog(`cp.spawn DENIED command=${fullCmd}`);
const denied = original("false", [], { stdio: "pipe" });
process.nextTick(() => {
denied.emit("error", error);
});
return denied;
}
const wrapped = self.wrapWithSeatbelt(
command,
Array.from(args || []),
options,
policyResult
);
debugLog(`cp.spawn calling original command=${wrapped.command} args=${wrapped.args.join(" ")}`);
return original(wrapped.command, wrapped.args, wrapped.options || {});
};

@@ -718,45 +1163,37 @@ return interceptedSpawn;

const fullCommand = args ? `${command} ${args.join(" ")}` : command;
debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking}`);
if (self._checking) {
debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking} _executing=${self._executing}`);
if (self._checking || self._executing) {
debugLog(`cp.spawnSync SKIP (re-entrancy) command=${fullCommand}`);
return original(command, args, options);
}
self._checking = true;
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
self.eventReporter.intercept("exec", fullCommand);
debugLog(`cp.spawnSync policy_check START command=${fullCommand}`);
const result = self.syncClient.request(
"policy_check",
{ operation: "exec", target: fullCommand }
);
debugLog(`cp.spawnSync policy_check DONE allowed=${result.allowed} command=${fullCommand}`);
if (!result.allowed) {
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(result.reason || "Policy denied"),
status: 1,
signal: null,
error: new PolicyDeniedError(result.reason || "Policy denied")
};
}
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
debugLog(`cp.spawnSync policy_check ERROR: ${error.message} command=${fullCommand}`);
if (!self.failOpen) {
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(error.message),
status: 1,
signal: null,
error
};
}
} finally {
self._checking = false;
debugLog(`cp.spawnSync DENIED command=${fullCommand}`);
return {
pid: -1,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.from(
error instanceof PolicyDeniedError ? error.message || "Policy denied" : error.message
),
status: 1,
signal: null,
error
};
}
debugLog(`cp.spawnSync calling original command=${fullCommand}`);
return original(command, args, options);
const wrapped = self.wrapWithSeatbelt(
command,
Array.from(args || []),
options,
policyResult
);
debugLog(`cp.spawnSync calling original command=${wrapped.command}`);
return original(
wrapped.command,
wrapped.args,
wrapped.options
);
};

@@ -767,11 +1204,48 @@ }

const original = this.originalExecFile;
return function interceptedExecFile(file, ...args) {
if (self._checking) {
return original(file, ...args);
return function interceptedExecFile(file, ...rest) {
if (self._checking || self._executing) {
return original(file, ...rest);
}
self.eventReporter.intercept("exec", file);
self.checkPolicy("exec", file).catch((error) => {
self.eventReporter.error("exec", file, error.message);
});
return original(file, ...args);
let args = [];
let options;
let callback;
for (const arg of rest) {
if (typeof arg === "function") {
callback = arg;
} else if (Array.isArray(arg)) {
args = arg;
} else if (typeof arg === "object" && arg !== null) {
options = arg;
}
}
const fullCommand = args.length > 0 ? `${file} ${args.join(" ")}` : file;
debugLog(`cp.execFile ENTER command=${fullCommand}`);
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
if (callback) {
process.nextTick(() => callback(error, "", ""));
}
return original("false");
}
const wrapped = self.wrapWithSeatbelt(file, args, options, policyResult);
debugLog(`cp.execFile calling original command=${wrapped.command}`);
if (callback) {
return original(
wrapped.command,
wrapped.args,
wrapped.options,
callback
);
}
return original(
wrapped.command,
wrapped.args,
wrapped.options || {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
() => {
}
);
};

@@ -783,10 +1257,34 @@ }

const interceptedFork = function(modulePath, args, options) {
if (self._checking) {
if (self._checking || self._executing) {
return original(modulePath, args, options);
}
const pathStr = modulePath.toString();
self.eventReporter.intercept("exec", `fork:${pathStr}`);
self.checkPolicy("exec", `fork:${pathStr}`).catch((error) => {
self.eventReporter.error("exec", pathStr, error.message);
});
const fullCommand = `fork:${pathStr}`;
debugLog(`cp.fork ENTER command=${fullCommand}`);
self.eventReporter.intercept("exec", fullCommand);
let policyResult = null;
try {
policyResult = self.syncPolicyCheck(fullCommand);
} catch (error) {
debugLog(`cp.fork DENIED command=${fullCommand}`);
const denied = self.originalSpawn("false", [], { stdio: "pipe" });
process.nextTick(() => {
denied.emit("error", error);
});
return denied;
}
if (policyResult?.sandbox) {
const sandbox = policyResult.sandbox;
const env = { ...options?.env || process.env };
if (sandbox.envInjection) {
Object.assign(env, sandbox.envInjection);
}
if (sandbox.envDeny) {
for (const key of sandbox.envDeny) {
delete env[key];
}
}
options = { ...options, env };
}
debugLog(`cp.fork calling original module=${pathStr}`);
return original(modulePath, args, options);

@@ -885,4 +1383,4 @@ };

const self = this;
safeOverride(module2, methodName, function intercepted(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, function intercepted(path2, ...args) {
const pathString = normalizePathArg(path2);
const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;

@@ -892,3 +1390,3 @@ debugLog(`fs.${methodName} ENTER (async) path=${pathString} _checking=${self._checking}`);

debugLog(`fs.${methodName} SKIP (re-entrancy, async) path=${pathString}`);
original.call(module2, path, ...args, callback);
original.call(module2, path2, ...args, callback);
return;

@@ -899,3 +1397,3 @@ }

debugLog(`fs.${methodName} policy OK (async) path=${pathString}`);
original.call(module2, path, ...args, callback);
original.call(module2, path2, ...args, callback);
}).catch((error) => {

@@ -915,8 +1413,8 @@ debugLog(`fs.${methodName} policy ERROR (async): ${error.message} path=${pathString}`);

const self = this;
safeOverride(module2, methodName, function interceptedSync(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, function interceptedSync(path2, ...args) {
const pathString = normalizePathArg(path2);
debugLog(`fs.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
if (self._checking) {
debugLog(`fs.${methodName} SKIP (re-entrancy) path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
}

@@ -950,3 +1448,3 @@ self._checking = true;

debugLog(`fs.${methodName} calling original path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
});

@@ -960,8 +1458,8 @@ }

const self = this;
safeOverride(module2, methodName, async function interceptedPromise(path, ...args) {
const pathString = normalizePathArg(path);
safeOverride(module2, methodName, async function interceptedPromise(path2, ...args) {
const pathString = normalizePathArg(path2);
debugLog(`fsPromises.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
if (self._checking) {
debugLog(`fsPromises.${methodName} SKIP (re-entrancy) path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
}

@@ -971,3 +1469,3 @@ self.eventReporter.intercept(operation, pathString);

debugLog(`fsPromises.${methodName} policy OK path=${pathString}`);
return original.call(module2, path, ...args);
return original.call(module2, path2, ...args);
});

@@ -1116,7 +1614,7 @@ }

*/
async check(operation, target) {
async check(operation, target, context) {
try {
const result = await this.client.request(
"policy_check",
{ operation, target }
{ operation, target, context }
);

@@ -1165,3 +1663,7 @@ return result;

const prefix = event.type === "allow" ? "\u2713" : event.type === "deny" ? "\u2717" : "\u2022";
console[level](`[AgenShield] ${prefix} ${event.operation}: ${event.target}`);
let detail = `${prefix} ${event.operation}: ${event.target}`;
if (event.policyId) detail += ` [policy:${event.policyId}]`;
if (event.error) detail += ` [reason:${event.error}]`;
if (event.duration) detail += ` [${event.duration}ms]`;
console[level](`[AgenShield] ${detail}`);
}

@@ -1286,2 +1788,9 @@ if (this.queue.length >= 100) {

const config = createConfig(configOverrides);
if (config.logLevel === "debug") {
try {
const safeConfig = { ...config };
console.error("[AgenShield:config]", JSON.stringify(safeConfig, null, 2));
} catch {
}
}
client = new AsyncClient({

@@ -1307,3 +1816,4 @@ socketPath: config.socketPath,

failOpen: config.failOpen,
brokerHttpPort: config.httpPort
brokerHttpPort: config.httpPort,
config
});

@@ -1341,3 +1851,4 @@ installed.fetch.install();

failOpen: config.failOpen,
brokerHttpPort: config.httpPort
brokerHttpPort: config.httpPort,
config
});

@@ -1344,0 +1855,0 @@ installed.childProcess.install();