feat: enhance user authorization checks in GatewayRunner

- Updated the authorization logic to include a per-platform allow-all flag for improved flexibility.
- Revised the order of checks to prioritize platform-specific allow-all settings, followed by environment variable allowlists and DM pairing approvals.
- Added global allow-all configuration for broader access control.
- Improved handling of allowlists by stripping whitespace and ensuring valid entries are processed.
This commit is contained in:
teknium1 2026-02-22 16:32:08 -08:00
parent db23f51bc6
commit e1604b2b4a
3 changed files with 42 additions and 24 deletions

View file

@ -253,43 +253,54 @@ class GatewayRunner:
Check if a user is authorized to use the bot. Check if a user is authorized to use the bot.
Checks in order: Checks in order:
1. Environment variable allowlists (TELEGRAM_ALLOWED_USERS, etc.) 1. Per-platform allow-all flag (e.g., DISCORD_ALLOW_ALL_USERS=true)
2. DM pairing approved list 2. Environment variable allowlists (TELEGRAM_ALLOWED_USERS, etc.)
3. If no allowlists AND no pairing approvals exist, allow all (open access) 3. DM pairing approved list
4. Global allow-all (GATEWAY_ALLOW_ALL_USERS=true)
5. Default: deny
""" """
user_id = source.user_id user_id = source.user_id
if not user_id: if not user_id:
return False # Can't verify unknown users return False
# Check platform-specific allowlist first
platform_env_map = { platform_env_map = {
Platform.TELEGRAM: "TELEGRAM_ALLOWED_USERS", Platform.TELEGRAM: "TELEGRAM_ALLOWED_USERS",
Platform.DISCORD: "DISCORD_ALLOWED_USERS", Platform.DISCORD: "DISCORD_ALLOWED_USERS",
Platform.WHATSAPP: "WHATSAPP_ALLOWED_USERS", Platform.WHATSAPP: "WHATSAPP_ALLOWED_USERS",
Platform.SLACK: "SLACK_ALLOWED_USERS", Platform.SLACK: "SLACK_ALLOWED_USERS",
} }
platform_allow_all_map = {
platform_allowlist = os.getenv(platform_env_map.get(source.platform, "")) Platform.TELEGRAM: "TELEGRAM_ALLOW_ALL_USERS",
global_allowlist = os.getenv("GATEWAY_ALLOWED_USERS", "") Platform.DISCORD: "DISCORD_ALLOW_ALL_USERS",
Platform.WHATSAPP: "WHATSAPP_ALLOW_ALL_USERS",
Platform.SLACK: "SLACK_ALLOW_ALL_USERS",
}
# Per-platform allow-all flag (e.g., DISCORD_ALLOW_ALL_USERS=true)
platform_allow_all_var = platform_allow_all_map.get(source.platform, "")
if platform_allow_all_var and os.getenv(platform_allow_all_var, "").lower() in ("true", "1", "yes"):
return True
# Check pairing store (always checked, regardless of allowlists) # Check pairing store (always checked, regardless of allowlists)
platform_name = source.platform.value if source.platform else "" platform_name = source.platform.value if source.platform else ""
if self.pairing_store.is_approved(platform_name, user_id): if self.pairing_store.is_approved(platform_name, user_id):
return True return True
# If no allowlists configured: default-deny unless explicitly opted in. # Check platform-specific and global allowlists
# Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env for open access. platform_allowlist = os.getenv(platform_env_map.get(source.platform, ""), "").strip()
global_allowlist = os.getenv("GATEWAY_ALLOWED_USERS", "").strip()
if not platform_allowlist and not global_allowlist: if not platform_allowlist and not global_allowlist:
allow_all = os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in ("true", "1", "yes") # No allowlists configured -- check global allow-all flag
return allow_all return os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in ("true", "1", "yes")
# Check if user is in any allowlist # Check if user is in any allowlist
allowed_ids = set() allowed_ids = set()
if platform_allowlist: if platform_allowlist:
allowed_ids.update(uid.strip() for uid in platform_allowlist.split(",")) allowed_ids.update(uid.strip() for uid in platform_allowlist.split(",") if uid.strip())
if global_allowlist: if global_allowlist:
allowed_ids.update(uid.strip() for uid in global_allowlist.split(",")) allowed_ids.update(uid.strip() for uid in global_allowlist.split(",") if uid.strip())
return user_id in allowed_ids return user_id in allowed_ids
async def _handle_message(self, event: MessageEvent) -> Optional[str]: async def _handle_message(self, event: MessageEvent) -> Optional[str]:

View file

@ -199,7 +199,7 @@ def _check_file_reqs():
READ_FILE_SCHEMA = { READ_FILE_SCHEMA = {
"name": "read_file", "name": "read_file",
"description": "Read a file with line numbers and pagination. Output format: 'LINE_NUM|CONTENT'. Suggests similar filenames if not found. Images (png/jpg/gif/webp) returned as base64. Use offset and limit for large files.", "description": "Read a file with line numbers and pagination. Use this instead of cat/head/tail in terminal. Output format: 'LINE_NUM|CONTENT'. Suggests similar filenames if not found. Images (png/jpg/gif/webp) returned as base64. Use offset and limit for large files.",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -213,7 +213,7 @@ READ_FILE_SCHEMA = {
WRITE_FILE_SCHEMA = { WRITE_FILE_SCHEMA = {
"name": "write_file", "name": "write_file",
"description": "Write content to a file, completely replacing existing content. Creates parent directories automatically. OVERWRITES the entire file — use 'patch' for targeted edits.", "description": "Write content to a file, completely replacing existing content. Use this instead of echo/cat heredoc in terminal. Creates parent directories automatically. OVERWRITES the entire file — use 'patch' for targeted edits.",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -226,7 +226,7 @@ WRITE_FILE_SCHEMA = {
PATCH_SCHEMA = { PATCH_SCHEMA = {
"name": "patch", "name": "patch",
"description": "Targeted find-and-replace edits in files. Uses fuzzy matching (9 strategies) so minor whitespace/indentation differences won't break it. Returns a unified diff. Auto-runs syntax checks after editing.\n\nReplace mode (default): find a unique string and replace it.\nPatch mode: apply V4A multi-file patches for bulk changes.", "description": "Targeted find-and-replace edits in files. Use this instead of sed/awk in terminal. Uses fuzzy matching (9 strategies) so minor whitespace/indentation differences won't break it. Returns a unified diff. Auto-runs syntax checks after editing.\n\nReplace mode (default): find a unique string and replace it.\nPatch mode: apply V4A multi-file patches for bulk changes.",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -243,7 +243,7 @@ PATCH_SCHEMA = {
SEARCH_FILES_SCHEMA = { SEARCH_FILES_SCHEMA = {
"name": "search_files", "name": "search_files",
"description": "Search file contents or find files by name. Ripgrep-backed, faster than grep/rg/find in the terminal.\n\nContent search (target='content'): Regex search inside files. Output modes: full matches with line numbers, file paths only, or match counts.\n\nFile search (target='files'): Find files by glob pattern (e.g., '*.py', '*config*'). Results sorted by modification time.", "description": "Search file contents or find files by name. Use this instead of grep/rg/find/ls in terminal. Ripgrep-backed, faster than shell equivalents.\n\nContent search (target='content'): Regex search inside files. Output modes: full matches with line numbers, file paths only, or match counts.\n\nFile search (target='files'): Find files by glob pattern (e.g., '*.py', '*config*'). Also use this instead of ls — results sorted by modification time.",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -348,7 +348,14 @@ from tools.environments.modal import ModalEnvironment as _ModalEnvironment
# Tool description for LLM # Tool description for LLM
TERMINAL_TOOL_DESCRIPTION = """Execute commands on a Linux environment. Filesystem persists between calls. TERMINAL_TOOL_DESCRIPTION = """Execute shell commands on a Linux environment. Filesystem persists between calls.
Do NOT use cat/head/tail to read files use read_file instead.
Do NOT use grep/rg/find to search use search_files instead.
Do NOT use ls to list directories use search_files(target='files') instead.
Do NOT use sed/awk to edit files use patch instead.
Do NOT use echo/cat heredoc to create files use write_file instead.
Reserve terminal for: builds, installs, git, processes, scripts, network, package managers, and anything that needs a shell.
Background processes: Set background=true to get a session_id, then use the 'process' tool to poll/wait/kill/write. Background processes: Set background=true to get a session_id, then use the 'process' tool to poll/wait/kill/write.
Working directory: Use 'workdir' for per-command cwd. Working directory: Use 'workdir' for per-command cwd.