From d4b7209ed29cd58a707f4be2f9778c19031776bc Mon Sep 17 00:00:00 2001 From: rasdani <73563550+rasdani@users.noreply.github.com> Date: Fri, 17 Apr 2026 04:05:54 +0530 Subject: [PATCH] Add RLM_ENABLED_TOOLS allowlist with default 'edit' RLM_ENABLED_TOOLS is a comma-separated allowlist of skill names. It filters both the system-prompt "Installed skills" listing (tools.get_installed_skills) and the kernel_shim sys.modules proxies, so that a disabled skill is neither advertised to the agent nor importable from the ipython kernel. The CLI itself stays on PATH. RLM_ENABLED_TOOLS=* opts out of filtering entirely; "" disables every skill; unset defaults to 'edit'. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/rlm/kernel_shim.py | 20 +++++++++++++++++--- src/rlm/tools.py | 20 +++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/rlm/kernel_shim.py b/src/rlm/kernel_shim.py index 9dd4fff..ebf9e51 100644 --- a/src/rlm/kernel_shim.py +++ b/src/rlm/kernel_shim.py @@ -85,12 +85,21 @@ def _make_proxy(name: str, parameters: dict) -> types.ModuleType: return mod +def _parse_enabled_tools() -> frozenset[str] | None: + """Mirror of rlm.tools._parse_enabled_tools (duplicated to keep this + module independent of rlm.__init__, which pulls in openai).""" + raw = os.environ.get("RLM_ENABLED_TOOLS", "edit").strip() + if raw == "*": + return None + return frozenset(name.strip() for name in raw.split(",") if name.strip()) + + def install_shims(skills_dir: str) -> list[str]: """Register proxy modules for all skills found in *skills_dir*. - Always installs shims regardless of whether a same-named module is - already importable — this guarantees the kernel uses the rlm - checkout's version of each skill, not an unrelated package. + Respects ``RLM_ENABLED_TOOLS`` so that disabled skills are neither + importable nor listed in the system prompt. The CLI itself remains + on PATH — we only gate the Python shim and the prompt exposure. Returns the list of skill names that were shimmed. """ @@ -98,6 +107,8 @@ def install_shims(skills_dir: str) -> list[str]: if not skills_path.is_dir(): return [] + enabled = _parse_enabled_tools() + shimmed = [] for skill_dir in sorted(skills_path.iterdir()): if not (skill_dir / "pyproject.toml").is_file(): @@ -113,6 +124,9 @@ def install_shims(skills_dir: str) -> list[str]: else: continue + if enabled is not None and name not in enabled: + continue + # Skip if the CLI isn't on PATH if not shutil.which(name): continue diff --git a/src/rlm/tools.py b/src/rlm/tools.py index 98f35f9..157c3d7 100644 --- a/src/rlm/tools.py +++ b/src/rlm/tools.py @@ -39,14 +39,32 @@ def _normalize_skill_name(name: str) -> str: return name.replace("-", "_") +DEFAULT_ENABLED_TOOLS = "edit" + + +def _parse_enabled_tools() -> frozenset[str] | None: + """Parse RLM_ENABLED_TOOLS into an allowlist of skill names. + + Returns None when the env var is ``*`` (allow every discovered skill). + Empty string disables every skill. Unset defaults to ``edit``. + """ + raw = os.environ.get("RLM_ENABLED_TOOLS", DEFAULT_ENABLED_TOOLS).strip() + if raw == "*": + return None + return frozenset(name.strip() for name in raw.split(",") if name.strip()) + + def get_installed_skills() -> list[str]: - """Return installed skill names discovered from distribution metadata.""" + """Return installed skill names, filtered by RLM_ENABLED_TOOLS.""" skills: set[str] = set() prefix = "rlm-skill-" for dist in metadata.distributions(): name = dist.metadata.get("Name", "") if name.startswith(prefix): skills.add(_normalize_skill_name(name[len(prefix) :])) + enabled = _parse_enabled_tools() + if enabled is not None: + skills &= enabled return sorted(skills)