Skip to content

Commit 616b8a3

Browse files
committed
fix: match sandbox tool allowlist by project name
1 parent d096e33 commit 616b8a3

2 files changed

Lines changed: 97 additions & 8 deletions

File tree

services/sandbox/install_tool_shims.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,20 @@ def _clear_published_tools(tool_dir: Path) -> None:
140140
_remove_path(child)
141141

142142

143+
def _tool_project_name(package_dir: Path) -> str:
144+
pyproject = package_dir / "pyproject.toml"
145+
try:
146+
data = tomllib.loads(pyproject.read_text())
147+
except (OSError, tomllib.TOMLDecodeError) as exc:
148+
print(f"warning: failed to read {pyproject}: {exc}", file=sys.stderr)
149+
return ""
150+
project = data.get("project") or {}
151+
if not isinstance(project, dict):
152+
print(f"warning: invalid [project] table in {pyproject}", file=sys.stderr)
153+
return ""
154+
return str(project.get("name") or "")
155+
156+
143157
def _copy_published_tools(tool_dir: Path, published: Path) -> None:
144158
if not published.is_dir():
145159
raise RuntimeError(f"refreshed tools subdir does not exist: {published}")
@@ -148,16 +162,23 @@ def _copy_published_tools(tool_dir: Path, published: Path) -> None:
148162
blocklist = _tool_blocklist()
149163
existing = {package_dir.name: package_dir for package_dir in _tool_package_dirs(tool_dir)}
150164
for package_dir in _tool_package_dirs(published):
151-
tool_name = package_dir.name
152-
if allowlist is not None and tool_name not in allowlist:
165+
package_dir_name = package_dir.name
166+
project_name = ""
167+
if allowlist is not None or blocklist:
168+
project_name = _tool_project_name(package_dir)
169+
if (
170+
allowlist is not None
171+
and package_dir_name not in allowlist
172+
and project_name not in allowlist
173+
):
153174
# Not in TOOL_ALLOWLIST -> don't install; keeps the agent's catalog
154175
# to configured tools (no phantom, credential-less tools).
155176
continue
156-
if tool_name in blocklist:
177+
if package_dir_name in blocklist or project_name in blocklist:
157178
continue
158-
if tool_name in existing:
179+
if package_dir_name in existing:
159180
print(
160-
f"skipping duplicate tool {tool_name}: {package_dir} conflicts with {existing[tool_name]}",
181+
f"skipping duplicate tool {package_dir_name}: {package_dir} conflicts with {existing[package_dir_name]}",
161182
file=sys.stderr,
162183
)
163184
continue
@@ -167,7 +188,7 @@ def _copy_published_tools(tool_dir: Path, published: Path) -> None:
167188
_remove_path(target)
168189
target.parent.mkdir(parents=True, exist_ok=True)
169190
shutil.copytree(package_dir, target, symlinks=True)
170-
existing[tool_name] = target
191+
existing[package_dir_name] = target
171192

172193

173194
def _tool_package_dirs(published: Path) -> list[Path]:

services/sandbox/test_install_tool_shims.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
from __future__ import annotations
22

33
import contextlib
4+
import importlib.util
45
import io
56
import json
67
import os
8+
from pathlib import Path
79
import subprocess
810
import tempfile
911
import unittest
10-
from pathlib import Path
1112
from unittest import mock
1213

13-
import install_tool_shims
14+
INSTALL_TOOL_SHIMS_PATH = Path(__file__).resolve().parent / "install_tool_shims.py"
15+
INSTALL_TOOL_SHIMS_SPEC = importlib.util.spec_from_file_location(
16+
"install_tool_shims", INSTALL_TOOL_SHIMS_PATH
17+
)
18+
if INSTALL_TOOL_SHIMS_SPEC is None or INSTALL_TOOL_SHIMS_SPEC.loader is None:
19+
raise RuntimeError(f"failed to load {INSTALL_TOOL_SHIMS_PATH}")
20+
install_tool_shims = importlib.util.module_from_spec(INSTALL_TOOL_SHIMS_SPEC)
21+
INSTALL_TOOL_SHIMS_SPEC.loader.exec_module(install_tool_shims)
1422

1523

1624
class CopyPublishedToolsTest(unittest.TestCase):
@@ -66,6 +74,66 @@ def test_tool_allowlist_restricts_installed_tools(self) -> None:
6674
self.assertTrue((target / "research" / "websearch" / "pyproject.toml").exists())
6775
self.assertFalse((target / "productivity" / "linear").exists())
6876

77+
def test_tool_allowlist_matches_project_name_when_copying_published_tools(self) -> None:
78+
with tempfile.TemporaryDirectory() as tmp:
79+
root = Path(tmp)
80+
published = root / "published"
81+
target = root / "target"
82+
83+
composio = published / "productivity" / "composio"
84+
composio.mkdir(parents=True)
85+
(composio / "pyproject.toml").write_text(
86+
'[project]\nname = "centaur-composio-tool"\n'
87+
)
88+
linear = published / "productivity" / "linear"
89+
linear.mkdir(parents=True)
90+
(linear / "pyproject.toml").write_text('[project]\nname = "linear"\n')
91+
92+
with mock.patch.dict("os.environ", {"TOOL_ALLOWLIST": "centaur-composio-tool"}):
93+
install_tool_shims._copy_published_tools(target, published)
94+
95+
self.assertTrue((target / "productivity" / "composio" / "pyproject.toml").exists())
96+
self.assertFalse((target / "productivity" / "linear").exists())
97+
98+
def test_tool_blocklist_matches_project_name_when_copying_published_tools(self) -> None:
99+
with tempfile.TemporaryDirectory() as tmp:
100+
root = Path(tmp)
101+
published = root / "published"
102+
target = root / "target"
103+
104+
composio = published / "productivity" / "composio"
105+
composio.mkdir(parents=True)
106+
(composio / "pyproject.toml").write_text(
107+
'[project]\nname = "centaur-composio-tool"\n'
108+
)
109+
linear = published / "productivity" / "linear"
110+
linear.mkdir(parents=True)
111+
(linear / "pyproject.toml").write_text('[project]\nname = "linear"\n')
112+
113+
with mock.patch.dict("os.environ", {"TOOL_BLOCKLIST": "centaur-composio-tool"}):
114+
install_tool_shims._copy_published_tools(target, published)
115+
116+
self.assertFalse((target / "productivity" / "composio").exists())
117+
self.assertTrue((target / "productivity" / "linear" / "pyproject.toml").exists())
118+
119+
def test_malformed_project_table_falls_back_to_directory_matching(self) -> None:
120+
with tempfile.TemporaryDirectory() as tmp:
121+
root = Path(tmp)
122+
published = root / "published"
123+
target = root / "target"
124+
125+
broken = published / "research" / "broken"
126+
broken.mkdir(parents=True)
127+
(broken / "pyproject.toml").write_text('project = "not-a-table"\n')
128+
129+
stderr = io.StringIO()
130+
with mock.patch.dict("os.environ", {"TOOL_ALLOWLIST": "broken"}):
131+
with contextlib.redirect_stderr(stderr):
132+
install_tool_shims._copy_published_tools(target, published)
133+
134+
self.assertTrue((target / "research" / "broken" / "pyproject.toml").exists())
135+
self.assertIn("warning: invalid [project] table", stderr.getvalue())
136+
69137
def test_unset_allowlist_installs_all_tools(self) -> None:
70138
with tempfile.TemporaryDirectory() as tmp:
71139
root = Path(tmp)

0 commit comments

Comments
 (0)