@solongate/proxy 0.42.2 → 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 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/guard.mjs CHANGED
@@ -366,6 +366,66 @@ function evaluate(policy, args) {
366
366
  return null;
367
367
  }
368
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
+
369
429
  // ── Main ──
370
430
  let input = '';
371
431
  process.stdin.on('data', c => input += c);
@@ -394,6 +454,7 @@ process.stdin.on('end', async () => {
394
454
  session_id: raw.session_id || raw.sessionId || raw.conversation_id || '',
395
455
  };
396
456
  const args = data.tool_input;
457
+ const toolName = data.tool_name || '';
397
458
 
398
459
  // ── Self-protection: block access to hook files and settings ──
399
460
  // Hardcoded, no bypass possible — runs before policy/PI config
@@ -997,7 +1058,7 @@ process.stdin.on('end', async () => {
997
1058
  }
998
1059
 
999
1060
  // ── Per-tool config: check if PI scanning is disabled for this tool ──
1000
- const toolName = data.tool_name || '';
1061
+ // toolName already defined above (before self-protection)
1001
1062
  if (piCfg.piToolConfig && typeof piCfg.piToolConfig === 'object') {
1002
1063
  if (piCfg.piToolConfig[toolName] === false) {
1003
1064
  // PI scanning explicitly disabled for this tool — skip detection
@@ -1141,8 +1202,91 @@ process.stdin.on('end', async () => {
1141
1202
  allowTool(); // No policy = allow all
1142
1203
  }
1143
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
+
1144
1282
  let reason = evaluate(policy, args);
1145
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
+
1146
1290
  // ── AI Judge: semantic intent analysis (runs when policy ALLOWs) ──
1147
1291
  if (!reason) {
1148
1292
  let GROQ_KEY = process.env.GROQ_API_KEY || dotenv.GROQ_API_KEY || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.42.2",
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": {