@solongate/proxy 0.42.1 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +213 -0
- package/dist/lib.js +213 -0
- package/hooks/audit.mjs +9 -0
- package/hooks/guard.mjs +157 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2745,12 +2745,56 @@ var PolicyRuleSchema = z.object({
|
|
|
2745
2745
|
createdAt: z.string().datetime(),
|
|
2746
2746
|
updatedAt: z.string().datetime()
|
|
2747
2747
|
});
|
|
2748
|
+
var GroupPolicyRuleSchema = z.object({
|
|
2749
|
+
toolPattern: z.string().min(1),
|
|
2750
|
+
permission: z.enum(["READ", "WRITE", "EXECUTE", "NETWORK"]).optional(),
|
|
2751
|
+
effect: z.enum(["ALLOW", "DENY"]),
|
|
2752
|
+
pathConstraints: z.object({
|
|
2753
|
+
allowed: z.array(z.string()).optional(),
|
|
2754
|
+
denied: z.array(z.string()).optional(),
|
|
2755
|
+
rootDirectory: z.string().optional(),
|
|
2756
|
+
allowSymlinks: z.boolean().optional()
|
|
2757
|
+
}).optional(),
|
|
2758
|
+
commandConstraints: z.object({
|
|
2759
|
+
allowed: z.array(z.string()).optional(),
|
|
2760
|
+
denied: z.array(z.string()).optional()
|
|
2761
|
+
}).optional(),
|
|
2762
|
+
urlConstraints: z.object({
|
|
2763
|
+
allowed: z.array(z.string()).optional(),
|
|
2764
|
+
denied: z.array(z.string()).optional()
|
|
2765
|
+
}).optional()
|
|
2766
|
+
});
|
|
2767
|
+
var AgentRelationshipConfigSchema = z.object({
|
|
2768
|
+
source: z.string().min(1),
|
|
2769
|
+
target: z.string().min(1),
|
|
2770
|
+
type: z.enum(["peer", "delegation", "supervisor"]),
|
|
2771
|
+
trustLevel: z.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]).optional(),
|
|
2772
|
+
allowedTools: z.array(z.string()).optional(),
|
|
2773
|
+
deniedTools: z.array(z.string()).optional(),
|
|
2774
|
+
allowedPermissions: z.array(z.string()).optional(),
|
|
2775
|
+
maxDelegationDepth: z.number().int().min(0).max(10).optional()
|
|
2776
|
+
});
|
|
2777
|
+
var DelegationConfigSchema = z.object({
|
|
2778
|
+
chain: z.array(z.string()).min(2),
|
|
2779
|
+
effectiveTools: z.array(z.string()).optional(),
|
|
2780
|
+
effectivePermissions: z.array(z.string()).optional()
|
|
2781
|
+
});
|
|
2782
|
+
var AgentTrustMapSchema = z.object({
|
|
2783
|
+
groups: z.record(z.object({
|
|
2784
|
+
description: z.string().optional(),
|
|
2785
|
+
members: z.array(z.string()),
|
|
2786
|
+
rules: z.array(GroupPolicyRuleSchema)
|
|
2787
|
+
})).optional(),
|
|
2788
|
+
relationships: z.array(AgentRelationshipConfigSchema).optional(),
|
|
2789
|
+
delegations: z.array(DelegationConfigSchema).optional()
|
|
2790
|
+
});
|
|
2748
2791
|
var PolicySetSchema = z.object({
|
|
2749
2792
|
id: z.string().min(1).max(256),
|
|
2750
2793
|
name: z.string().min(1).max(256),
|
|
2751
2794
|
description: z.string().max(2048),
|
|
2752
2795
|
version: z.number().int().min(0),
|
|
2753
2796
|
rules: z.array(PolicyRuleSchema),
|
|
2797
|
+
agentTrustMap: AgentTrustMapSchema.optional(),
|
|
2754
2798
|
createdAt: z.string().datetime(),
|
|
2755
2799
|
updatedAt: z.string().datetime()
|
|
2756
2800
|
});
|
|
@@ -5409,6 +5453,132 @@ var SolonGate = class {
|
|
|
5409
5453
|
}
|
|
5410
5454
|
};
|
|
5411
5455
|
|
|
5456
|
+
// src/agent-trust.ts
|
|
5457
|
+
function matchGlob(value, pattern) {
|
|
5458
|
+
if (pattern === "*") return true;
|
|
5459
|
+
if (!pattern.includes("*")) return value === pattern;
|
|
5460
|
+
const regex = new RegExp("^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$");
|
|
5461
|
+
return regex.test(value);
|
|
5462
|
+
}
|
|
5463
|
+
var AgentTrustEvaluator = class {
|
|
5464
|
+
localConfig = null;
|
|
5465
|
+
cloudRulesCache = /* @__PURE__ */ new Map();
|
|
5466
|
+
lastFetchTime = /* @__PURE__ */ new Map();
|
|
5467
|
+
fetchIntervalMs = 6e4;
|
|
5468
|
+
apiKey;
|
|
5469
|
+
apiUrl;
|
|
5470
|
+
constructor(apiKey, apiUrl) {
|
|
5471
|
+
this.apiKey = apiKey;
|
|
5472
|
+
this.apiUrl = apiUrl;
|
|
5473
|
+
}
|
|
5474
|
+
/** Load local trust map from policy.json's agentTrustMap section */
|
|
5475
|
+
loadLocal(config) {
|
|
5476
|
+
this.localConfig = config ?? null;
|
|
5477
|
+
}
|
|
5478
|
+
/** Fetch cloud rules for a specific agent */
|
|
5479
|
+
async fetchCloudRules(agentId) {
|
|
5480
|
+
if (!this.apiKey || !this.apiUrl) return;
|
|
5481
|
+
const lastFetch = this.lastFetchTime.get(agentId) ?? 0;
|
|
5482
|
+
if (Date.now() - lastFetch < this.fetchIntervalMs) return;
|
|
5483
|
+
try {
|
|
5484
|
+
const res = await fetch(`${this.apiUrl}/api/v1/trust-map/rules?agentId=${encodeURIComponent(agentId)}`, {
|
|
5485
|
+
headers: {
|
|
5486
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
5487
|
+
"Content-Type": "application/json"
|
|
5488
|
+
},
|
|
5489
|
+
signal: AbortSignal.timeout(5e3)
|
|
5490
|
+
});
|
|
5491
|
+
if (res.ok) {
|
|
5492
|
+
const data = await res.json();
|
|
5493
|
+
this.cloudRulesCache.set(agentId, data);
|
|
5494
|
+
this.lastFetchTime.set(agentId, Date.now());
|
|
5495
|
+
}
|
|
5496
|
+
} catch {
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
/**
|
|
5500
|
+
* Evaluate whether agentId is allowed to call toolName with the given permission.
|
|
5501
|
+
* DENY wins: if either local or cloud rules deny, the tool call is blocked.
|
|
5502
|
+
*/
|
|
5503
|
+
evaluate(agentId, toolName, permission, subAgentId) {
|
|
5504
|
+
const effectiveId = subAgentId || agentId;
|
|
5505
|
+
const localDecision = this.evaluateLocal(effectiveId, toolName, permission);
|
|
5506
|
+
if (!localDecision.allowed) return localDecision;
|
|
5507
|
+
const cloudDecision = this.evaluateCloud(effectiveId, toolName, permission);
|
|
5508
|
+
if (!cloudDecision.allowed) return cloudDecision;
|
|
5509
|
+
return { allowed: true, reason: "Passed agent trust checks" };
|
|
5510
|
+
}
|
|
5511
|
+
evaluateLocal(agentId, toolName, permission) {
|
|
5512
|
+
if (!this.localConfig) return { allowed: true, reason: "No local agent trust map" };
|
|
5513
|
+
for (const [groupName, group] of Object.entries(this.localConfig.groups ?? {})) {
|
|
5514
|
+
if (group.members.includes(agentId)) {
|
|
5515
|
+
for (const rule of group.rules) {
|
|
5516
|
+
if (matchGlob(toolName, rule.toolPattern)) {
|
|
5517
|
+
if (rule.permission && rule.permission !== permission) continue;
|
|
5518
|
+
if (rule.effect === "DENY") {
|
|
5519
|
+
return { allowed: false, reason: `Blocked by group "${groupName}": ${rule.toolPattern}` };
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
for (const rel of this.localConfig.relationships ?? []) {
|
|
5526
|
+
if (rel.target === agentId) {
|
|
5527
|
+
if (rel.deniedTools?.some((p) => matchGlob(toolName, p))) {
|
|
5528
|
+
return { allowed: false, reason: `Blocked by relationship: ${rel.source} \u2192 ${rel.target}` };
|
|
5529
|
+
}
|
|
5530
|
+
if (rel.allowedTools && rel.allowedTools.length > 0) {
|
|
5531
|
+
if (!rel.allowedTools.some((p) => matchGlob(toolName, p))) {
|
|
5532
|
+
return { allowed: false, reason: `Tool not in allowed list: ${rel.source} \u2192 ${rel.target}` };
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
for (const del of this.localConfig.delegations ?? []) {
|
|
5538
|
+
if (del.chain.includes(agentId)) {
|
|
5539
|
+
if (del.effectiveTools && del.effectiveTools.length > 0) {
|
|
5540
|
+
if (!del.effectiveTools.some((p) => matchGlob(toolName, p))) {
|
|
5541
|
+
return { allowed: false, reason: `Tool not in delegation chain effective tools` };
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
return { allowed: true, reason: "Passed local checks" };
|
|
5547
|
+
}
|
|
5548
|
+
evaluateCloud(agentId, toolName, permission) {
|
|
5549
|
+
const rules = this.cloudRulesCache.get(agentId);
|
|
5550
|
+
if (!rules) return { allowed: true, reason: "No cloud rules cached" };
|
|
5551
|
+
for (const group of rules.groups) {
|
|
5552
|
+
for (const rule of group.policyRules) {
|
|
5553
|
+
if (matchGlob(toolName, rule.toolPattern)) {
|
|
5554
|
+
if (rule.permission && rule.permission !== permission) continue;
|
|
5555
|
+
if (rule.effect === "DENY") {
|
|
5556
|
+
return { allowed: false, reason: `Blocked by cloud group "${group.name}": ${rule.toolPattern}` };
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
for (const rel of rules.relationships) {
|
|
5562
|
+
if (rel.deniedTools.some((p) => matchGlob(toolName, p))) {
|
|
5563
|
+
return { allowed: false, reason: `Blocked by cloud relationship: ${rel.sourceAgentId} \u2192 ${agentId}` };
|
|
5564
|
+
}
|
|
5565
|
+
if (rel.allowedTools.length > 0) {
|
|
5566
|
+
if (!rel.allowedTools.some((p) => matchGlob(toolName, p))) {
|
|
5567
|
+
return { allowed: false, reason: `Tool not in cloud allowed list: ${rel.sourceAgentId} \u2192 ${agentId}` };
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5571
|
+
for (const del of rules.delegations) {
|
|
5572
|
+
if (del.chain.includes(agentId) && del.effectiveTools.length > 0) {
|
|
5573
|
+
if (!del.effectiveTools.some((p) => matchGlob(toolName, p))) {
|
|
5574
|
+
return { allowed: false, reason: `Tool not in cloud delegation chain` };
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
return { allowed: true, reason: "Passed cloud checks" };
|
|
5579
|
+
}
|
|
5580
|
+
};
|
|
5581
|
+
|
|
5412
5582
|
// src/proxy.ts
|
|
5413
5583
|
init_config();
|
|
5414
5584
|
|
|
@@ -5940,6 +6110,8 @@ var SolonGateProxy = class {
|
|
|
5940
6110
|
httpAgentInfo = /* @__PURE__ */ new Map();
|
|
5941
6111
|
/** Per-request sub-agent info from HTTP headers (transient, overwritten per request) */
|
|
5942
6112
|
httpSubAgent = null;
|
|
6113
|
+
/** Agent trust map evaluator for group/relationship/delegation rules */
|
|
6114
|
+
agentTrustEvaluator = null;
|
|
5943
6115
|
constructor(config) {
|
|
5944
6116
|
this.config = config;
|
|
5945
6117
|
this.guardConfig = config.advancedDetection ? { ...DEFAULT_INPUT_GUARD_CONFIG, advancedDetection: config.advancedDetection } : DEFAULT_INPUT_GUARD_CONFIG;
|
|
@@ -5962,6 +6134,13 @@ var SolonGateProxy = class {
|
|
|
5962
6134
|
for (const w of warnings) {
|
|
5963
6135
|
log2("WARNING:", w);
|
|
5964
6136
|
}
|
|
6137
|
+
if (config.policy.agentTrustMap || config.apiKey && !config.apiKey.startsWith("sg_test_")) {
|
|
6138
|
+
this.agentTrustEvaluator = new AgentTrustEvaluator(config.apiKey, config.apiUrl);
|
|
6139
|
+
if (config.policy.agentTrustMap) {
|
|
6140
|
+
this.agentTrustEvaluator.loadLocal(config.policy.agentTrustMap);
|
|
6141
|
+
log2("Agent trust map loaded from policy.json");
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
5965
6144
|
}
|
|
5966
6145
|
/** Normalize well-known MCP client names to display-friendly agent identities */
|
|
5967
6146
|
normalizeAgentName(raw) {
|
|
@@ -6193,6 +6372,10 @@ var SolonGateProxy = class {
|
|
|
6193
6372
|
} else if (this.config.agentName) {
|
|
6194
6373
|
log2(`Agent identity from --agent-name flag: ${this.agentName} (clientInfo: ${clientVersion?.name || "none"})`);
|
|
6195
6374
|
}
|
|
6375
|
+
if (this.agentTrustEvaluator && this.agentId) {
|
|
6376
|
+
this.agentTrustEvaluator.fetchCloudRules(this.agentId).catch(() => {
|
|
6377
|
+
});
|
|
6378
|
+
}
|
|
6196
6379
|
}
|
|
6197
6380
|
};
|
|
6198
6381
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -6212,6 +6395,36 @@ var SolonGateProxy = class {
|
|
|
6212
6395
|
};
|
|
6213
6396
|
}
|
|
6214
6397
|
log2(`Tool call: ${name}`);
|
|
6398
|
+
if (this.agentTrustEvaluator) {
|
|
6399
|
+
const trustDecision = this.agentTrustEvaluator.evaluate(
|
|
6400
|
+
this.agentId ?? "unknown",
|
|
6401
|
+
name,
|
|
6402
|
+
guessPermission(name),
|
|
6403
|
+
subAgent?.subAgentId
|
|
6404
|
+
);
|
|
6405
|
+
if (!trustDecision.allowed) {
|
|
6406
|
+
log2(`DENY: ${name} \u2014 ${trustDecision.reason}`);
|
|
6407
|
+
const apiUrl = this.config.apiUrl || "https://api.solongate.com";
|
|
6408
|
+
if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
|
|
6409
|
+
sendAuditLog(this.config.apiKey, apiUrl, {
|
|
6410
|
+
tool: name,
|
|
6411
|
+
arguments: args ?? {},
|
|
6412
|
+
decision: "DENY",
|
|
6413
|
+
reason: trustDecision.reason,
|
|
6414
|
+
permission: guessPermission(name),
|
|
6415
|
+
evaluationTimeMs: 0,
|
|
6416
|
+
agent_id: this.agentId ?? void 0,
|
|
6417
|
+
agent_name: this.agentName ?? void 0,
|
|
6418
|
+
sub_agent_id: subAgent?.subAgentId,
|
|
6419
|
+
sub_agent_name: subAgent?.subAgentName
|
|
6420
|
+
});
|
|
6421
|
+
}
|
|
6422
|
+
return {
|
|
6423
|
+
content: [{ type: "text", text: `Blocked by agent trust policy: ${trustDecision.reason}` }],
|
|
6424
|
+
isError: true
|
|
6425
|
+
};
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6215
6428
|
let piResult;
|
|
6216
6429
|
if (args && typeof args === "object") {
|
|
6217
6430
|
const argsCheck = this.config.advancedDetection ? await sanitizeInputAsync("tool.arguments", args, this.guardConfig) : sanitizeInput("tool.arguments", args);
|
package/dist/lib.js
CHANGED
|
@@ -692,12 +692,56 @@ var PolicyRuleSchema = z.object({
|
|
|
692
692
|
createdAt: z.string().datetime(),
|
|
693
693
|
updatedAt: z.string().datetime()
|
|
694
694
|
});
|
|
695
|
+
var GroupPolicyRuleSchema = z.object({
|
|
696
|
+
toolPattern: z.string().min(1),
|
|
697
|
+
permission: z.enum(["READ", "WRITE", "EXECUTE", "NETWORK"]).optional(),
|
|
698
|
+
effect: z.enum(["ALLOW", "DENY"]),
|
|
699
|
+
pathConstraints: z.object({
|
|
700
|
+
allowed: z.array(z.string()).optional(),
|
|
701
|
+
denied: z.array(z.string()).optional(),
|
|
702
|
+
rootDirectory: z.string().optional(),
|
|
703
|
+
allowSymlinks: z.boolean().optional()
|
|
704
|
+
}).optional(),
|
|
705
|
+
commandConstraints: z.object({
|
|
706
|
+
allowed: z.array(z.string()).optional(),
|
|
707
|
+
denied: z.array(z.string()).optional()
|
|
708
|
+
}).optional(),
|
|
709
|
+
urlConstraints: z.object({
|
|
710
|
+
allowed: z.array(z.string()).optional(),
|
|
711
|
+
denied: z.array(z.string()).optional()
|
|
712
|
+
}).optional()
|
|
713
|
+
});
|
|
714
|
+
var AgentRelationshipConfigSchema = z.object({
|
|
715
|
+
source: z.string().min(1),
|
|
716
|
+
target: z.string().min(1),
|
|
717
|
+
type: z.enum(["peer", "delegation", "supervisor"]),
|
|
718
|
+
trustLevel: z.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]).optional(),
|
|
719
|
+
allowedTools: z.array(z.string()).optional(),
|
|
720
|
+
deniedTools: z.array(z.string()).optional(),
|
|
721
|
+
allowedPermissions: z.array(z.string()).optional(),
|
|
722
|
+
maxDelegationDepth: z.number().int().min(0).max(10).optional()
|
|
723
|
+
});
|
|
724
|
+
var DelegationConfigSchema = z.object({
|
|
725
|
+
chain: z.array(z.string()).min(2),
|
|
726
|
+
effectiveTools: z.array(z.string()).optional(),
|
|
727
|
+
effectivePermissions: z.array(z.string()).optional()
|
|
728
|
+
});
|
|
729
|
+
var AgentTrustMapSchema = z.object({
|
|
730
|
+
groups: z.record(z.object({
|
|
731
|
+
description: z.string().optional(),
|
|
732
|
+
members: z.array(z.string()),
|
|
733
|
+
rules: z.array(GroupPolicyRuleSchema)
|
|
734
|
+
})).optional(),
|
|
735
|
+
relationships: z.array(AgentRelationshipConfigSchema).optional(),
|
|
736
|
+
delegations: z.array(DelegationConfigSchema).optional()
|
|
737
|
+
});
|
|
695
738
|
var PolicySetSchema = z.object({
|
|
696
739
|
id: z.string().min(1).max(256),
|
|
697
740
|
name: z.string().min(1).max(256),
|
|
698
741
|
description: z.string().max(2048),
|
|
699
742
|
version: z.number().int().min(0),
|
|
700
743
|
rules: z.array(PolicyRuleSchema),
|
|
744
|
+
agentTrustMap: AgentTrustMapSchema.optional(),
|
|
701
745
|
createdAt: z.string().datetime(),
|
|
702
746
|
updatedAt: z.string().datetime()
|
|
703
747
|
});
|
|
@@ -3629,6 +3673,132 @@ import { createServer as createHttpServer } from "http";
|
|
|
3629
3673
|
import { resolve as resolve2, join } from "path";
|
|
3630
3674
|
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
3631
3675
|
|
|
3676
|
+
// src/agent-trust.ts
|
|
3677
|
+
function matchGlob(value, pattern) {
|
|
3678
|
+
if (pattern === "*") return true;
|
|
3679
|
+
if (!pattern.includes("*")) return value === pattern;
|
|
3680
|
+
const regex = new RegExp("^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$");
|
|
3681
|
+
return regex.test(value);
|
|
3682
|
+
}
|
|
3683
|
+
var AgentTrustEvaluator = class {
|
|
3684
|
+
localConfig = null;
|
|
3685
|
+
cloudRulesCache = /* @__PURE__ */ new Map();
|
|
3686
|
+
lastFetchTime = /* @__PURE__ */ new Map();
|
|
3687
|
+
fetchIntervalMs = 6e4;
|
|
3688
|
+
apiKey;
|
|
3689
|
+
apiUrl;
|
|
3690
|
+
constructor(apiKey, apiUrl) {
|
|
3691
|
+
this.apiKey = apiKey;
|
|
3692
|
+
this.apiUrl = apiUrl;
|
|
3693
|
+
}
|
|
3694
|
+
/** Load local trust map from policy.json's agentTrustMap section */
|
|
3695
|
+
loadLocal(config) {
|
|
3696
|
+
this.localConfig = config ?? null;
|
|
3697
|
+
}
|
|
3698
|
+
/** Fetch cloud rules for a specific agent */
|
|
3699
|
+
async fetchCloudRules(agentId) {
|
|
3700
|
+
if (!this.apiKey || !this.apiUrl) return;
|
|
3701
|
+
const lastFetch = this.lastFetchTime.get(agentId) ?? 0;
|
|
3702
|
+
if (Date.now() - lastFetch < this.fetchIntervalMs) return;
|
|
3703
|
+
try {
|
|
3704
|
+
const res = await fetch(`${this.apiUrl}/api/v1/trust-map/rules?agentId=${encodeURIComponent(agentId)}`, {
|
|
3705
|
+
headers: {
|
|
3706
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
3707
|
+
"Content-Type": "application/json"
|
|
3708
|
+
},
|
|
3709
|
+
signal: AbortSignal.timeout(5e3)
|
|
3710
|
+
});
|
|
3711
|
+
if (res.ok) {
|
|
3712
|
+
const data = await res.json();
|
|
3713
|
+
this.cloudRulesCache.set(agentId, data);
|
|
3714
|
+
this.lastFetchTime.set(agentId, Date.now());
|
|
3715
|
+
}
|
|
3716
|
+
} catch {
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* Evaluate whether agentId is allowed to call toolName with the given permission.
|
|
3721
|
+
* DENY wins: if either local or cloud rules deny, the tool call is blocked.
|
|
3722
|
+
*/
|
|
3723
|
+
evaluate(agentId, toolName, permission, subAgentId) {
|
|
3724
|
+
const effectiveId = subAgentId || agentId;
|
|
3725
|
+
const localDecision = this.evaluateLocal(effectiveId, toolName, permission);
|
|
3726
|
+
if (!localDecision.allowed) return localDecision;
|
|
3727
|
+
const cloudDecision = this.evaluateCloud(effectiveId, toolName, permission);
|
|
3728
|
+
if (!cloudDecision.allowed) return cloudDecision;
|
|
3729
|
+
return { allowed: true, reason: "Passed agent trust checks" };
|
|
3730
|
+
}
|
|
3731
|
+
evaluateLocal(agentId, toolName, permission) {
|
|
3732
|
+
if (!this.localConfig) return { allowed: true, reason: "No local agent trust map" };
|
|
3733
|
+
for (const [groupName, group] of Object.entries(this.localConfig.groups ?? {})) {
|
|
3734
|
+
if (group.members.includes(agentId)) {
|
|
3735
|
+
for (const rule of group.rules) {
|
|
3736
|
+
if (matchGlob(toolName, rule.toolPattern)) {
|
|
3737
|
+
if (rule.permission && rule.permission !== permission) continue;
|
|
3738
|
+
if (rule.effect === "DENY") {
|
|
3739
|
+
return { allowed: false, reason: `Blocked by group "${groupName}": ${rule.toolPattern}` };
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
for (const rel of this.localConfig.relationships ?? []) {
|
|
3746
|
+
if (rel.target === agentId) {
|
|
3747
|
+
if (rel.deniedTools?.some((p) => matchGlob(toolName, p))) {
|
|
3748
|
+
return { allowed: false, reason: `Blocked by relationship: ${rel.source} \u2192 ${rel.target}` };
|
|
3749
|
+
}
|
|
3750
|
+
if (rel.allowedTools && rel.allowedTools.length > 0) {
|
|
3751
|
+
if (!rel.allowedTools.some((p) => matchGlob(toolName, p))) {
|
|
3752
|
+
return { allowed: false, reason: `Tool not in allowed list: ${rel.source} \u2192 ${rel.target}` };
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
for (const del of this.localConfig.delegations ?? []) {
|
|
3758
|
+
if (del.chain.includes(agentId)) {
|
|
3759
|
+
if (del.effectiveTools && del.effectiveTools.length > 0) {
|
|
3760
|
+
if (!del.effectiveTools.some((p) => matchGlob(toolName, p))) {
|
|
3761
|
+
return { allowed: false, reason: `Tool not in delegation chain effective tools` };
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
return { allowed: true, reason: "Passed local checks" };
|
|
3767
|
+
}
|
|
3768
|
+
evaluateCloud(agentId, toolName, permission) {
|
|
3769
|
+
const rules = this.cloudRulesCache.get(agentId);
|
|
3770
|
+
if (!rules) return { allowed: true, reason: "No cloud rules cached" };
|
|
3771
|
+
for (const group of rules.groups) {
|
|
3772
|
+
for (const rule of group.policyRules) {
|
|
3773
|
+
if (matchGlob(toolName, rule.toolPattern)) {
|
|
3774
|
+
if (rule.permission && rule.permission !== permission) continue;
|
|
3775
|
+
if (rule.effect === "DENY") {
|
|
3776
|
+
return { allowed: false, reason: `Blocked by cloud group "${group.name}": ${rule.toolPattern}` };
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
for (const rel of rules.relationships) {
|
|
3782
|
+
if (rel.deniedTools.some((p) => matchGlob(toolName, p))) {
|
|
3783
|
+
return { allowed: false, reason: `Blocked by cloud relationship: ${rel.sourceAgentId} \u2192 ${agentId}` };
|
|
3784
|
+
}
|
|
3785
|
+
if (rel.allowedTools.length > 0) {
|
|
3786
|
+
if (!rel.allowedTools.some((p) => matchGlob(toolName, p))) {
|
|
3787
|
+
return { allowed: false, reason: `Tool not in cloud allowed list: ${rel.sourceAgentId} \u2192 ${agentId}` };
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
for (const del of rules.delegations) {
|
|
3792
|
+
if (del.chain.includes(agentId) && del.effectiveTools.length > 0) {
|
|
3793
|
+
if (!del.effectiveTools.some((p) => matchGlob(toolName, p))) {
|
|
3794
|
+
return { allowed: false, reason: `Tool not in cloud delegation chain` };
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
return { allowed: true, reason: "Passed cloud checks" };
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
3801
|
+
|
|
3632
3802
|
// src/config.ts
|
|
3633
3803
|
import { readFileSync, existsSync } from "fs";
|
|
3634
3804
|
import { appendFile } from "fs/promises";
|
|
@@ -4266,6 +4436,8 @@ var SolonGateProxy = class {
|
|
|
4266
4436
|
httpAgentInfo = /* @__PURE__ */ new Map();
|
|
4267
4437
|
/** Per-request sub-agent info from HTTP headers (transient, overwritten per request) */
|
|
4268
4438
|
httpSubAgent = null;
|
|
4439
|
+
/** Agent trust map evaluator for group/relationship/delegation rules */
|
|
4440
|
+
agentTrustEvaluator = null;
|
|
4269
4441
|
constructor(config) {
|
|
4270
4442
|
this.config = config;
|
|
4271
4443
|
this.guardConfig = config.advancedDetection ? { ...DEFAULT_INPUT_GUARD_CONFIG, advancedDetection: config.advancedDetection } : DEFAULT_INPUT_GUARD_CONFIG;
|
|
@@ -4288,6 +4460,13 @@ var SolonGateProxy = class {
|
|
|
4288
4460
|
for (const w of warnings) {
|
|
4289
4461
|
log2("WARNING:", w);
|
|
4290
4462
|
}
|
|
4463
|
+
if (config.policy.agentTrustMap || config.apiKey && !config.apiKey.startsWith("sg_test_")) {
|
|
4464
|
+
this.agentTrustEvaluator = new AgentTrustEvaluator(config.apiKey, config.apiUrl);
|
|
4465
|
+
if (config.policy.agentTrustMap) {
|
|
4466
|
+
this.agentTrustEvaluator.loadLocal(config.policy.agentTrustMap);
|
|
4467
|
+
log2("Agent trust map loaded from policy.json");
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4291
4470
|
}
|
|
4292
4471
|
/** Normalize well-known MCP client names to display-friendly agent identities */
|
|
4293
4472
|
normalizeAgentName(raw) {
|
|
@@ -4519,6 +4698,10 @@ var SolonGateProxy = class {
|
|
|
4519
4698
|
} else if (this.config.agentName) {
|
|
4520
4699
|
log2(`Agent identity from --agent-name flag: ${this.agentName} (clientInfo: ${clientVersion?.name || "none"})`);
|
|
4521
4700
|
}
|
|
4701
|
+
if (this.agentTrustEvaluator && this.agentId) {
|
|
4702
|
+
this.agentTrustEvaluator.fetchCloudRules(this.agentId).catch(() => {
|
|
4703
|
+
});
|
|
4704
|
+
}
|
|
4522
4705
|
}
|
|
4523
4706
|
};
|
|
4524
4707
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -4538,6 +4721,36 @@ var SolonGateProxy = class {
|
|
|
4538
4721
|
};
|
|
4539
4722
|
}
|
|
4540
4723
|
log2(`Tool call: ${name}`);
|
|
4724
|
+
if (this.agentTrustEvaluator) {
|
|
4725
|
+
const trustDecision = this.agentTrustEvaluator.evaluate(
|
|
4726
|
+
this.agentId ?? "unknown",
|
|
4727
|
+
name,
|
|
4728
|
+
guessPermission(name),
|
|
4729
|
+
subAgent?.subAgentId
|
|
4730
|
+
);
|
|
4731
|
+
if (!trustDecision.allowed) {
|
|
4732
|
+
log2(`DENY: ${name} \u2014 ${trustDecision.reason}`);
|
|
4733
|
+
const apiUrl = this.config.apiUrl || "https://api.solongate.com";
|
|
4734
|
+
if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
|
|
4735
|
+
sendAuditLog(this.config.apiKey, apiUrl, {
|
|
4736
|
+
tool: name,
|
|
4737
|
+
arguments: args ?? {},
|
|
4738
|
+
decision: "DENY",
|
|
4739
|
+
reason: trustDecision.reason,
|
|
4740
|
+
permission: guessPermission(name),
|
|
4741
|
+
evaluationTimeMs: 0,
|
|
4742
|
+
agent_id: this.agentId ?? void 0,
|
|
4743
|
+
agent_name: this.agentName ?? void 0,
|
|
4744
|
+
sub_agent_id: subAgent?.subAgentId,
|
|
4745
|
+
sub_agent_name: subAgent?.subAgentName
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
return {
|
|
4749
|
+
content: [{ type: "text", text: `Blocked by agent trust policy: ${trustDecision.reason}` }],
|
|
4750
|
+
isError: true
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4541
4754
|
let piResult;
|
|
4542
4755
|
if (args && typeof args === "object") {
|
|
4543
4756
|
const argsCheck = this.config.advancedDetection ? await sanitizeInputAsync("tool.arguments", args, this.guardConfig) : sanitizeInput("tool.arguments", args);
|
package/hooks/audit.mjs
CHANGED
|
@@ -21,6 +21,14 @@ function loadEnvKey(dir) {
|
|
|
21
21
|
} catch { return {}; }
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function guessPermission(toolName) {
|
|
25
|
+
const name = (toolName || '').toLowerCase();
|
|
26
|
+
if (name.includes('exec') || name.includes('shell') || name.includes('run') || name.includes('eval') || name === 'bash') return 'EXECUTE';
|
|
27
|
+
if (name.includes('fetch') || name.includes('http') || name.includes('request') || name.includes('curl') || name.includes('network') || name.includes('download') || name.includes('upload') || name === 'websearch') return 'NETWORK';
|
|
28
|
+
if (name.includes('write') || name.includes('create') || name.includes('delete') || name.includes('update') || name.includes('set') || name.includes('edit') || name.includes('remove') || name.includes('insert')) return 'WRITE';
|
|
29
|
+
return 'READ';
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
const dotenv = loadEnvKey(process.cwd());
|
|
25
33
|
const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
|
|
26
34
|
const API_URL = process.env.SOLONGATE_API_URL || dotenv.SOLONGATE_API_URL || 'https://api.solongate.com';
|
|
@@ -102,6 +110,7 @@ process.stdin.on('end', async () => {
|
|
|
102
110
|
arguments: argsSummary,
|
|
103
111
|
decision: hasError ? 'DENY' : 'ALLOW',
|
|
104
112
|
reason: guardDenied ? 'blocked by policy guard' : hasError ? 'tool returned error' : 'allowed',
|
|
113
|
+
permission: guessPermission(toolName),
|
|
105
114
|
source: `${AGENT_ID}-hook`,
|
|
106
115
|
evaluationTimeMs: 0,
|
|
107
116
|
agent_id: AGENT_ID,
|
package/hooks/guard.mjs
CHANGED
|
@@ -35,6 +35,14 @@ function loadEnvKey(dir) {
|
|
|
35
35
|
} catch { return {}; }
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function guessPermission(toolName) {
|
|
39
|
+
const name = (toolName || '').toLowerCase();
|
|
40
|
+
if (name.includes('exec') || name.includes('shell') || name.includes('run') || name.includes('eval') || name === 'bash') return 'EXECUTE';
|
|
41
|
+
if (name.includes('fetch') || name.includes('http') || name.includes('request') || name.includes('curl') || name.includes('network') || name.includes('download') || name.includes('upload') || name === 'websearch') return 'NETWORK';
|
|
42
|
+
if (name.includes('write') || name.includes('create') || name.includes('delete') || name.includes('update') || name.includes('set') || name.includes('edit') || name.includes('remove') || name.includes('insert')) return 'WRITE';
|
|
43
|
+
return 'READ';
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
const hookCwdEarly = process.cwd();
|
|
39
47
|
const dotenv = loadEnvKey(hookCwdEarly);
|
|
40
48
|
const API_KEY = process.env.SOLONGATE_API_KEY || dotenv.SOLONGATE_API_KEY || '';
|
|
@@ -358,6 +366,66 @@ function evaluate(policy, args) {
|
|
|
358
366
|
return null;
|
|
359
367
|
}
|
|
360
368
|
|
|
369
|
+
// ── Agent Trust Map Evaluation ──
|
|
370
|
+
function matchTrustGlob(value, pattern) {
|
|
371
|
+
if (pattern === '*') return true;
|
|
372
|
+
if (!pattern.includes('*')) return value === pattern;
|
|
373
|
+
const regex = new RegExp('^' + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$');
|
|
374
|
+
return regex.test(value);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function evaluateAgentTrust(policy, agentId, toolName, permission) {
|
|
378
|
+
const trustMap = policy && policy.agentTrustMap;
|
|
379
|
+
if (!trustMap) return null;
|
|
380
|
+
|
|
381
|
+
// 1. Group rules
|
|
382
|
+
if (trustMap.groups) {
|
|
383
|
+
for (const [groupName, group] of Object.entries(trustMap.groups)) {
|
|
384
|
+
if (group.members && group.members.includes(agentId)) {
|
|
385
|
+
for (const rule of (group.rules || [])) {
|
|
386
|
+
if (matchTrustGlob(toolName, rule.toolPattern)) {
|
|
387
|
+
if (rule.permission && rule.permission !== permission) continue;
|
|
388
|
+
if (rule.effect === 'DENY') {
|
|
389
|
+
return 'Blocked by agent group "' + groupName + '": ' + rule.toolPattern;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 2. Relationship rules (where this agent is the target)
|
|
398
|
+
if (trustMap.relationships) {
|
|
399
|
+
for (const rel of trustMap.relationships) {
|
|
400
|
+
if (rel.target === agentId) {
|
|
401
|
+
if (rel.deniedTools && rel.deniedTools.some(p => matchTrustGlob(toolName, p))) {
|
|
402
|
+
return 'Blocked by relationship: ' + rel.source + ' → ' + rel.target;
|
|
403
|
+
}
|
|
404
|
+
if (rel.allowedTools && rel.allowedTools.length > 0) {
|
|
405
|
+
if (!rel.allowedTools.some(p => matchTrustGlob(toolName, p))) {
|
|
406
|
+
return 'Tool not in allowed list: ' + rel.source + ' → ' + rel.target;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 3. Delegation chains
|
|
414
|
+
if (trustMap.delegations) {
|
|
415
|
+
for (const del of trustMap.delegations) {
|
|
416
|
+
if (del.chain && del.chain.includes(agentId)) {
|
|
417
|
+
if (del.effectiveTools && del.effectiveTools.length > 0) {
|
|
418
|
+
if (!del.effectiveTools.some(p => matchTrustGlob(toolName, p))) {
|
|
419
|
+
return 'Tool not in delegation chain effective tools';
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
361
429
|
// ── Main ──
|
|
362
430
|
let input = '';
|
|
363
431
|
process.stdin.on('data', c => input += c);
|
|
@@ -386,6 +454,7 @@ process.stdin.on('end', async () => {
|
|
|
386
454
|
session_id: raw.session_id || raw.sessionId || raw.conversation_id || '',
|
|
387
455
|
};
|
|
388
456
|
const args = data.tool_input;
|
|
457
|
+
const toolName = data.tool_name || '';
|
|
389
458
|
|
|
390
459
|
// ── Self-protection: block access to hook files and settings ──
|
|
391
460
|
// Hardcoded, no bypass possible — runs before policy/PI config
|
|
@@ -407,6 +476,7 @@ process.stdin.on('end', async () => {
|
|
|
407
476
|
body: JSON.stringify({
|
|
408
477
|
tool: data.tool_name || '', arguments: args,
|
|
409
478
|
decision: 'DENY', reason,
|
|
479
|
+
permission: guessPermission(data.tool_name || ''),
|
|
410
480
|
source: `${AGENT_ID}-guard`,
|
|
411
481
|
agent_id: AGENT_ID, agent_name: AGENT_NAME,
|
|
412
482
|
}),
|
|
@@ -988,7 +1058,7 @@ process.stdin.on('end', async () => {
|
|
|
988
1058
|
}
|
|
989
1059
|
|
|
990
1060
|
// ── Per-tool config: check if PI scanning is disabled for this tool ──
|
|
991
|
-
|
|
1061
|
+
// toolName already defined above (before self-protection)
|
|
992
1062
|
if (piCfg.piToolConfig && typeof piCfg.piToolConfig === 'object') {
|
|
993
1063
|
if (piCfg.piToolConfig[toolName] === false) {
|
|
994
1064
|
// PI scanning explicitly disabled for this tool — skip detection
|
|
@@ -1054,6 +1124,7 @@ process.stdin.on('end', async () => {
|
|
|
1054
1124
|
arguments: args,
|
|
1055
1125
|
decision: isLogOnly ? 'ALLOW' : 'DENY',
|
|
1056
1126
|
reason: msg,
|
|
1127
|
+
permission: guessPermission(toolName),
|
|
1057
1128
|
source: `${AGENT_ID}-guard`,
|
|
1058
1129
|
agent_id: AGENT_ID, agent_name: AGENT_NAME,
|
|
1059
1130
|
pi_detected: true,
|
|
@@ -1115,6 +1186,7 @@ process.stdin.on('end', async () => {
|
|
|
1115
1186
|
arguments: args,
|
|
1116
1187
|
decision: 'ALLOW',
|
|
1117
1188
|
reason: 'Prompt injection detected but below threshold (trust: ' + (piResult.trustScore * 100).toFixed(0) + '%)',
|
|
1189
|
+
permission: guessPermission(toolName),
|
|
1118
1190
|
source: `${AGENT_ID}-guard`,
|
|
1119
1191
|
agent_id: AGENT_ID, agent_name: AGENT_NAME,
|
|
1120
1192
|
pi_detected: true,
|
|
@@ -1130,8 +1202,91 @@ process.stdin.on('end', async () => {
|
|
|
1130
1202
|
allowTool(); // No policy = allow all
|
|
1131
1203
|
}
|
|
1132
1204
|
|
|
1205
|
+
// ── Fetch cloud trust-map rules and merge into policy.agentTrustMap ──
|
|
1206
|
+
if (API_KEY && API_KEY.startsWith('sg_live_') && AGENT_ID) {
|
|
1207
|
+
const tmCacheFile = join(resolve('.solongate'), '.trust-rules-cache.json');
|
|
1208
|
+
let tmCached = null;
|
|
1209
|
+
try {
|
|
1210
|
+
if (existsSync(tmCacheFile)) {
|
|
1211
|
+
const cached = JSON.parse(readFileSync(tmCacheFile, 'utf-8'));
|
|
1212
|
+
if (cached._ts && Date.now() - cached._ts < 60000 && cached._agentId === AGENT_ID) {
|
|
1213
|
+
tmCached = cached;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
} catch {}
|
|
1217
|
+
if (!tmCached) {
|
|
1218
|
+
try {
|
|
1219
|
+
const tmRes = await fetch(API_URL + '/api/v1/trust-map/rules?agentId=' + encodeURIComponent(AGENT_ID), {
|
|
1220
|
+
headers: { 'Authorization': 'Bearer ' + API_KEY },
|
|
1221
|
+
signal: AbortSignal.timeout(5000),
|
|
1222
|
+
});
|
|
1223
|
+
if (tmRes.ok) {
|
|
1224
|
+
tmCached = await tmRes.json();
|
|
1225
|
+
tmCached._ts = Date.now();
|
|
1226
|
+
tmCached._agentId = AGENT_ID;
|
|
1227
|
+
try { writeFileSync(tmCacheFile, JSON.stringify(tmCached)); } catch {}
|
|
1228
|
+
}
|
|
1229
|
+
} catch {}
|
|
1230
|
+
}
|
|
1231
|
+
if (tmCached) {
|
|
1232
|
+
if (!policy) policy = {};
|
|
1233
|
+
if (!policy.agentTrustMap) policy.agentTrustMap = {};
|
|
1234
|
+
const tm = policy.agentTrustMap;
|
|
1235
|
+
|
|
1236
|
+
// Merge cloud groups into local groups
|
|
1237
|
+
if (tmCached.groups && tmCached.groups.length > 0) {
|
|
1238
|
+
if (!tm.groups) tm.groups = {};
|
|
1239
|
+
for (const g of tmCached.groups) {
|
|
1240
|
+
const key = 'cloud_' + g.id;
|
|
1241
|
+
if (!tm.groups[key]) {
|
|
1242
|
+
tm.groups[key] = {
|
|
1243
|
+
members: [AGENT_ID],
|
|
1244
|
+
rules: (g.policyRules || []).map(r => ({
|
|
1245
|
+
toolPattern: r.toolPattern || '*',
|
|
1246
|
+
permission: r.permission,
|
|
1247
|
+
effect: r.effect || 'DENY',
|
|
1248
|
+
})),
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Merge cloud relationships into local relationships
|
|
1255
|
+
if (tmCached.relationships && tmCached.relationships.length > 0) {
|
|
1256
|
+
if (!tm.relationships) tm.relationships = [];
|
|
1257
|
+
for (const r of tmCached.relationships) {
|
|
1258
|
+
tm.relationships.push({
|
|
1259
|
+
source: r.sourceAgentId,
|
|
1260
|
+
target: AGENT_ID,
|
|
1261
|
+
type: r.relationshipType || 'peer',
|
|
1262
|
+
allowedTools: r.allowedTools || [],
|
|
1263
|
+
deniedTools: r.deniedTools || [],
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Merge cloud delegations into local delegations
|
|
1269
|
+
if (tmCached.delegations && tmCached.delegations.length > 0) {
|
|
1270
|
+
if (!tm.delegations) tm.delegations = [];
|
|
1271
|
+
for (const d of tmCached.delegations) {
|
|
1272
|
+
tm.delegations.push({
|
|
1273
|
+
chain: d.chain || [],
|
|
1274
|
+
effectiveTools: d.effectiveTools || [],
|
|
1275
|
+
effectivePermissions: d.effectivePermissions || [],
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1133
1282
|
let reason = evaluate(policy, args);
|
|
1134
1283
|
|
|
1284
|
+
// ── Agent Trust Map evaluation ──
|
|
1285
|
+
if (!reason) {
|
|
1286
|
+
const trustReason = evaluateAgentTrust(policy, AGENT_ID, toolName, guessPermission(toolName));
|
|
1287
|
+
if (trustReason) reason = trustReason;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1135
1290
|
// ── AI Judge: semantic intent analysis (runs when policy ALLOWs) ──
|
|
1136
1291
|
if (!reason) {
|
|
1137
1292
|
let GROQ_KEY = process.env.GROQ_API_KEY || dotenv.GROQ_API_KEY || '';
|
|
@@ -1302,6 +1457,7 @@ Respond with ONLY valid JSON: {"decision": "ALLOW" or "DENY", "reason": "brief e
|
|
|
1302
1457
|
const logEntry = {
|
|
1303
1458
|
tool: toolName, arguments: args,
|
|
1304
1459
|
decision: 'DENY', reason,
|
|
1460
|
+
permission: guessPermission(toolName),
|
|
1305
1461
|
source: `${AGENT_ID}-guard`,
|
|
1306
1462
|
agent_id: AGENT_ID, agent_name: AGENT_NAME,
|
|
1307
1463
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "AI tool security proxy — protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|