))}
From 83c034bd5bc855955a825ff4acd1ed11edab6c3d Mon Sep 17 00:00:00 2001
From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
Date: Fri, 19 Jun 2026 12:18:15 +0530
Subject: [PATCH 3/4] fix(dashboard): accept Slack allow-all wildcard in
allowed-users validation
The new SLACK_ALLOWED_USERS validation rejected '*', but the Slack gateway
honors '*' as an allow-all wildcard (gateway/platforms/slack.py DM auth,
slash-confirm, and approval-button paths). Accept '*' as a valid list entry
in both the API validator and the dashboard form so a value the runtime
honors is no longer blocked at setup.
---
hermes_cli/web_server.py | 4 +++-
tests/hermes_cli/test_web_server.py | 13 +++++++++++++
web/src/pages/ChannelsPage.tsx | 2 +-
3 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py
index b890f68649e..316bc154fa4 100644
--- a/hermes_cli/web_server.py
+++ b/hermes_cli/web_server.py
@@ -2342,10 +2342,12 @@ def _validate_messaging_env_value(platform_id: str, key: str, value: str) -> Non
)
if key == "SLACK_ALLOWED_USERS":
user_ids = [part.strip() for part in value.split(",")]
+ # "*" is the gateway's allow-all wildcard (see gateway/platforms/slack.py),
+ # so accept it as a valid entry alongside Slack member IDs (U.../W...).
invalid = [
user_id
for user_id in user_ids
- if not user_id or not re.fullmatch(r"[UW][A-Z0-9]{2,}", user_id)
+ if user_id != "*" and (not user_id or not re.fullmatch(r"[UW][A-Z0-9]{2,}", user_id))
]
if invalid:
raise HTTPException(
diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py
index d44c789b3e3..d7a4dbcbbf9 100644
--- a/tests/hermes_cli/test_web_server.py
+++ b/tests/hermes_cli/test_web_server.py
@@ -1687,6 +1687,19 @@ class TestWebServerEndpoints:
assert resp.status_code == 400
assert "member IDs" in resp.json()["detail"]
+ def test_update_messaging_platform_accepts_slack_allowed_users_wildcard(self):
+ # "*" is the gateway's allow-all wildcard (gateway/platforms/slack.py),
+ # so the dashboard must accept it rather than rejecting it as malformed.
+ from hermes_cli.config import load_env
+
+ resp = self.client.put(
+ "/api/messaging/platforms/slack",
+ json={"env": {"SLACK_ALLOWED_USERS": "*"}},
+ )
+
+ assert resp.status_code == 200
+ assert load_env()["SLACK_ALLOWED_USERS"] == "*"
+
def test_messaging_platform_test_reports_missing_required_setup(self):
resp = self.client.put("/api/messaging/platforms/discord", json={"enabled": True})
assert resp.status_code == 200
diff --git a/web/src/pages/ChannelsPage.tsx b/web/src/pages/ChannelsPage.tsx
index 84791738a25..db56beb1925 100644
--- a/web/src/pages/ChannelsPage.tsx
+++ b/web/src/pages/ChannelsPage.tsx
@@ -76,7 +76,7 @@ function validateMessagingEnvField(field: MessagingPlatformEnvVar, value: string
if (parts.some((part) => !part)) {
return "Slack member IDs must be comma-separated without empty entries.";
}
- const invalid = parts.find((part) => !SLACK_MEMBER_ID_RE.test(part));
+ const invalid = parts.find((part) => part !== "*" && !SLACK_MEMBER_ID_RE.test(part));
if (invalid) {
return `${invalid} does not look like a Slack member ID. Use IDs like U01ABC2DEF3.`;
}
From 1ab6f34791e28559911185b308d8bd1b0be5f393 Mon Sep 17 00:00:00 2001
From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
Date: Fri, 19 Jun 2026 12:22:30 +0530
Subject: [PATCH 4/4] refactor(dashboard): align Slack allowlist validation
with gateway parse
- Drop empty entries before validating SLACK_ALLOWED_USERS so a trailing or
interior comma (which the gateway silently tolerates in
gateway/platforms/slack.py) is no longer rejected at the dashboard.
- Hoist the member-ID regex to a module-level _SLACK_MEMBER_ID_RE constant
and note it stays in sync with the frontend SLACK_MEMBER_ID_RE.
- Add a regression test for the trailing-comma case.
---
hermes_cli/web_server.py | 14 ++++++++++----
tests/hermes_cli/test_web_server.py | 13 +++++++++++++
web/src/pages/ChannelsPage.tsx | 11 +++++++----
3 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py
index 316bc154fa4..b0d51e2481e 100644
--- a/hermes_cli/web_server.py
+++ b/hermes_cli/web_server.py
@@ -2325,6 +2325,11 @@ def _gateway_display_command(profile: Optional[str], verb: str) -> str:
return " ".join(["hermes", *_gateway_subcommand(profile, verb)])
+# Slack member IDs (users U..., Enterprise Grid W...). Kept in sync with the
+# frontend SLACK_MEMBER_ID_RE in web/src/pages/ChannelsPage.tsx.
+_SLACK_MEMBER_ID_RE = re.compile(r"[UW][A-Z0-9]{2,}")
+
+
def _validate_messaging_env_value(platform_id: str, key: str, value: str) -> None:
"""Reject platform credentials that are clearly in the wrong field."""
if platform_id != "slack" or not value:
@@ -2341,13 +2346,14 @@ def _validate_messaging_env_value(platform_id: str, key: str, value: str) -> Non
detail="Slack App Token must start with xapp-. Paste the app-level token from Basic Information > App-Level Tokens.",
)
if key == "SLACK_ALLOWED_USERS":
- user_ids = [part.strip() for part in value.split(",")]
- # "*" is the gateway's allow-all wildcard (see gateway/platforms/slack.py),
- # so accept it as a valid entry alongside Slack member IDs (U.../W...).
+ # Mirror the gateway's parse (gateway/platforms/slack.py): split on comma,
+ # strip, and drop empty entries so a trailing/interior comma isn't rejected
+ # here when the runtime would accept it. "*" is the allow-all wildcard.
+ user_ids = [part.strip() for part in value.split(",") if part.strip()]
invalid = [
user_id
for user_id in user_ids
- if user_id != "*" and (not user_id or not re.fullmatch(r"[UW][A-Z0-9]{2,}", user_id))
+ if user_id != "*" and not _SLACK_MEMBER_ID_RE.fullmatch(user_id)
]
if invalid:
raise HTTPException(
diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py
index d7a4dbcbbf9..7416ec0b87a 100644
--- a/tests/hermes_cli/test_web_server.py
+++ b/tests/hermes_cli/test_web_server.py
@@ -1700,6 +1700,19 @@ class TestWebServerEndpoints:
assert resp.status_code == 200
assert load_env()["SLACK_ALLOWED_USERS"] == "*"
+ def test_update_messaging_platform_accepts_slack_allowed_users_trailing_comma(self):
+ # The gateway drops empty entries (gateway/platforms/slack.py), so a
+ # trailing/interior comma must not be rejected by the dashboard.
+ from hermes_cli.config import load_env
+
+ resp = self.client.put(
+ "/api/messaging/platforms/slack",
+ json={"env": {"SLACK_ALLOWED_USERS": "U01ABC2DEF3,,W04XYZ5LMN6,"}},
+ )
+
+ assert resp.status_code == 200
+ assert load_env()["SLACK_ALLOWED_USERS"] == "U01ABC2DEF3,,W04XYZ5LMN6,"
+
def test_messaging_platform_test_reports_missing_required_setup(self):
resp = self.client.put("/api/messaging/platforms/discord", json={"enabled": True})
assert resp.status_code == 200
diff --git a/web/src/pages/ChannelsPage.tsx b/web/src/pages/ChannelsPage.tsx
index db56beb1925..7658c0cd61a 100644
--- a/web/src/pages/ChannelsPage.tsx
+++ b/web/src/pages/ChannelsPage.tsx
@@ -72,10 +72,13 @@ function validateMessagingEnvField(field: MessagingPlatformEnvVar, value: string
}
if (field.key === "SLACK_ALLOWED_USERS") {
- const parts = trimmed.split(",").map((part) => part.trim());
- if (parts.some((part) => !part)) {
- return "Slack member IDs must be comma-separated without empty entries.";
- }
+ // Mirror the gateway's parse (gateway/platforms/slack.py): drop empty
+ // entries so a trailing/interior comma isn't rejected here. "*" is the
+ // allow-all wildcard the gateway honors.
+ const parts = trimmed
+ .split(",")
+ .map((part) => part.trim())
+ .filter(Boolean);
const invalid = parts.find((part) => part !== "*" && !SLACK_MEMBER_ID_RE.test(part));
if (invalid) {
return `${invalid} does not look like a Slack member ID. Use IDs like U01ABC2DEF3.`;