This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git


The following commit(s) were added to refs/heads/master by this push:
     new 9e004ad  fix(permissions): stop claiming enforcement for an agent that 
never calls the gateway
9e004ad is described below

commit 9e004ad5d44ff79d79f15471e3298d713828f18a
Author: Yang Luo <[email protected]>
AuthorDate: Fri Sep 4 20:34:12 2026 +0800

    fix(permissions): stop claiming enforcement for an agent that never calls 
the gateway
---
 web/src/components/PermissionCard.tsx | 47 ++++++++++++++++++++++++++++-------
 web/src/lib/agents.ts                 | 25 +++++++++++++++++++
 web/src/locales/en/data.json          |  4 +++
 web/src/locales/zh/data.json          |  4 +++
 web/src/pages/PermissionsPage.tsx     |  6 ++++-
 5 files changed, 76 insertions(+), 10 deletions(-)

diff --git a/web/src/components/PermissionCard.tsx 
b/web/src/components/PermissionCard.tsx
index 4de6839..971a900 100644
--- a/web/src/components/PermissionCard.tsx
+++ b/web/src/components/PermissionCard.tsx
@@ -38,7 +38,7 @@ import {Card, CardContent, CardHeader, CardTitle} from 
"@/components/ui/card";
 import {Switch} from "@/components/ui/switch";
 import {TagsInput} from "@/components/ui/tags-input";
 import {Textarea} from "@/components/ui/textarea";
-import {directMode} from "@/lib/agents";
+import {agentRoutedHere, directMode, gatewayMode, routedAgentId} from 
"@/lib/agents";
 import {providerIdOf} from "@/lib/providers";
 import {cn} from "@/lib/utils";
 import type {
@@ -285,6 +285,32 @@ function AdvancedView({
   );
 }
 
+/** Why the rules below are reaching nothing. An agent whose own configuration
+ *  does not name its gateway endpoint never relays a request through Gateway,
+ *  so its switches decide nothing until it is bound again. */
+function RoutingWarning({agent}: {agent: Agent}) {
+  if (agentRoutedHere(agent)) {
+    return null;
+  }
+
+  const routed = routedAgentId(agent);
+  return (
+    <p className="text-sm text-warning">
+      {routed !== "" ? (
+        <>
+          {i18next.t("agent:Shared config permission hint")} 
<code>{routed}</code>
+        </>
+      ) : agent.providerConfig?.supported === false ? (
+        i18next.t("agent:Unknown routing permission hint")
+      ) : (agent.mode || gatewayMode) === directMode ? (
+        i18next.t("agent:Direct mode permission hint")
+      ) : (
+        i18next.t("agent:Unrouted permission hint")
+      )}
+    </p>
+  );
+}
+
 /**
  * What one agent is allowed to ask Gateway for: the tools it may be offered, 
the
  * models it may name and the providers its requests may reach. The switches 
are
@@ -373,6 +399,8 @@ export function PermissionCard({
             {i18next.t("agent:Permissions")}
             {!permission.enabled ? (
               <Badge variant="muted">{i18next.t("agent:Unrestricted")}</Badge>
+            ) : !agentRoutedHere(agent) ? (
+              <Badge variant="warning">{i18next.t("agent:Not 
enforced")}</Badge>
             ) : blocked.length === 0 ? (
               <Badge variant="muted">{i18next.t("agent:Enforced")}</Badge>
             ) : (
@@ -397,17 +425,18 @@ export function PermissionCard({
           />
           <span>
             {i18next.t("agent:Enforce permissions")}
-            <span className="block text-muted-foreground">
-              {i18next.t(permission.enabled ? "agent:Enforced hint" : 
"agent:Unrestricted hint")}
-            </span>
+            {/* The claim that every relayed request is held to the rules is 
only
+                made where it is true; where it is not, the warning below says
+                why instead. */}
+            {!permission.enabled || agentRoutedHere(agent) ? (
+              <span className="block text-muted-foreground">
+                {i18next.t(permission.enabled ? "agent:Enforced hint" : 
"agent:Unrestricted hint")}
+              </span>
+            ) : null}
           </span>
         </label>
 
-        {/* An agent that was written the provider's own URL never calls the
-            proxy, so there is nothing here to hold it to. */}
-        {permission.enabled && agent.mode === directMode ? (
-          <p className="text-sm text-warning">{i18next.t("agent:Direct mode 
permission hint")}</p>
-        ) : null}
+        {permission.enabled ? <RoutingWarning agent={agent} /> : null}
 
         <div className={cn("space-y-3", !permission.enabled && 
"pointer-events-none opacity-50")}>
           <div className="space-y-2">
diff --git a/web/src/lib/agents.ts b/web/src/lib/agents.ts
index 7d888a9..94c7f72 100644
--- a/web/src/lib/agents.ts
+++ b/web/src/lib/agents.ts
@@ -159,6 +159,31 @@ export function agentProtocol(agent: Agent) {
   return agent.providerConfig?.protocol ?? "";
 }
 
+/**
+ * The agent id the configuration on disk routes to, empty when it names
+ * anything other than a gateway agent endpoint. Two front ends can share one
+ * configuration file - the opencode CLI and its desktop app do - so the id
+ * written there is not always the agent being looked at.
+ */
+export function routedAgentId(agent: Agent) {
+  const marker = "/v1/agents/";
+  const current = agent.providerConfig?.current ?? "";
+  const index = current.indexOf(marker);
+  if (index === -1) {
+    return "";
+  }
+  return decodeURIComponent(current.slice(index + 
marker.length).split("/")[0]);
+}
+
+/**
+ * Whether what this agent calls is its own gateway endpoint. Only the requests
+ * that arrive there are held to its permissions, so an agent this is false for
+ * is not restricted by them at all, whatever its switches say.
+ */
+export function agentRoutedHere(agent: Agent) {
+  return routedAgentId(agent) === agent.agentId;
+}
+
 /**
  * Whether an agent can be pointed at a provider serving this wire format.
  * Through the gateway any provider will do: the proxy translates between the
diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json
index 6b55cd4..ab02c9d 100644
--- a/web/src/locales/en/data.json
+++ b/web/src/locales/en/data.json
@@ -185,6 +185,7 @@
     "No supported agents found": "No supported agents found",
     "No usage yet": "No requests have been relayed for this agent yet",
     "No versions to list": "No versions to list",
+    "Not enforced": "Not enforced",
     "Not installed": "Not installed",
     "Not installed hint": "Agents Gateway works with that were not found on 
this machine. Install one with a package manager this host already has, or open 
its vendor's page.",
     "Not patched": "Not monitored",
@@ -260,6 +261,7 @@
     "Set in the system environment file": "Set in the system environment file",
     "Set in the system environment variables": "Set in the system environment 
variables",
     "Set in the user environment variables": "Set in the user environment 
variables",
+    "Shared config permission hint": "This agent shares its configuration file 
with another agent, and that file routes through Gateway under the other id, so 
none of these rules are ever reached. Its requests are held to the rules of:",
     "Source": "Source",
     "Start": "Start",
     "Start a sub-agent": "Start a sub-agent",
@@ -295,9 +297,11 @@
     "Uninstall {agent}?": "Uninstall {agent}?",
     "Uninstalling": "Uninstalling",
     "Unknown": "Unknown",
+    "Unknown routing permission hint": "Gateway cannot read this agent's 
provider configuration, so it cannot tell whether its requests come through 
here. Only the requests that do are held to these rules.",
     "Unpatch": "Turn off monitoring",
     "Unrestricted": "Unrestricted",
     "Unrestricted hint": "This agent may ask for any model, reach any provider 
and use every tool.",
+    "Unrouted permission hint": "This agent's own configuration does not point 
at Gateway, so its requests never reach it and none of these rules apply. Bind 
it to a provider on the Agents page.",
     "Up to date": "Up to date",
     "Update manually": "Update manually",
     "Updates available": "Updates available",
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index 498fcef..390f3ba 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -185,6 +185,7 @@
     "No supported agents found": "没有发现本机安装的智能体",
     "No usage yet": "该 Agent 还没有经过网关的请求",
     "No versions to list": "没有可列出的版本",
+    "Not enforced": "未生效",
     "Not installed": "未安装",
     "Not installed hint": "本机未找到、但 Gateway 支持的代理。可直接用本机已有的包管理器安装,或打开厂商页面。",
     "Not patched": "未开启",
@@ -260,6 +261,7 @@
     "Set in the system environment file": "设置在系统环境变量文件中",
     "Set in the system environment variables": "设置在系统环境变量中",
     "Set in the user environment variables": "设置在用户环境变量中",
+    "Shared config permission hint": 
"该智能体与另一个智能体共用同一个配置文件,而文件里写的是那个智能体的网关地址,因此这里的规则永远不会被用到。它的请求按下面这个智能体的规则判定:",
     "Source": "来源",
     "Start": "启动",
     "Start a sub-agent": "启动子智能体",
@@ -295,9 +297,11 @@
     "Uninstall {agent}?": "卸载 {agent}?",
     "Uninstalling": "卸载中",
     "Unknown": "未知",
+    "Unknown routing permission hint": 
"网关读不到该智能体的供应商配置,无法确认它的请求是否经过网关。这里的规则只对经过网关的请求生效。",
     "Unpatch": "关闭监控",
     "Unrestricted": "不受限制",
     "Unrestricted hint": "该智能体可以使用任意模型、任意供应商和全部工具。",
+    "Unrouted permission hint": 
"该智能体自己的配置没有指向网关,请求不会经过网关,这里的规则不会生效。请先在「智能体」页面为它绑定一个供应商。",
     "Up to date": "已是最新",
     "Update manually": "手动更新",
     "Updates available": "可更新",
diff --git a/web/src/pages/PermissionsPage.tsx 
b/web/src/pages/PermissionsPage.tsx
index 447efdc..db386a5 100644
--- a/web/src/pages/PermissionsPage.tsx
+++ b/web/src/pages/PermissionsPage.tsx
@@ -28,7 +28,7 @@ import {PageContainer, PageHeader} from 
"@/components/shared/page-header";
 import {UnauthorizedResult} from "@/components/shared/misc";
 import {Badge} from "@/components/ui/badge";
 import {Button} from "@/components/ui/button";
-import {useAgents} from "@/lib/agents";
+import {agentRoutedHere, useAgents} from "@/lib/agents";
 import {cn} from "@/lib/utils";
 import type {Account, Agent, AgentPermission, Provider} from "@/types";
 
@@ -66,6 +66,10 @@ function AgentRow({
         <Badge variant="muted" className="shrink-0 font-normal">
           {i18next.t("agent:Unrestricted")}
         </Badge>
+      ) : !agentRoutedHere(agent) ? (
+        <Badge variant="warning" className="shrink-0 font-normal">
+          {i18next.t("agent:Not enforced")}
+        </Badge>
       ) : blocked === 0 ? (
         <Badge variant="muted" className="shrink-0 font-normal">
           {i18next.t("agent:Enforced")}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to