-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathpackage_validation_tests.py
More file actions
251 lines (207 loc) · 7.88 KB
/
Copy pathpackage_validation_tests.py
File metadata and controls
251 lines (207 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any, Iterable
REPO_ROOT = Path(__file__).resolve().parent
EXPECTED_MCP_ENDPOINTS = {
"claude/.mcp.json": {
"name": "claude_app_mcp",
"url": "https://mcp.dropbox.com/claude_app_mcp",
},
"codex/.mcp.json": {
"name": "chatgpt_app_mcp",
"url": "https://mcp.dropbox.com/chatgpt_app_mcp",
},
"cursor/mcp.json": {
"name": "dropbox",
"url": "https://mcp.dropbox.com/cursor_app_mcp",
},
}
MANIFEST_PATHS = [
Path("claude/.claude-plugin/plugin.json"),
Path("codex/.codex-plugin/plugin.json"),
Path("cursor/.cursor-plugin/plugin.json"),
]
REQUIRED_MCP_HOST_FILES = [
Path("claude/README.md"),
Path("claude/LICENSE"),
Path("codex/README.md"),
Path("codex/LICENSE"),
Path("cursor/README.md"),
Path("cursor/LICENSE"),
]
PUBLIC_PACKAGE_PATHS = [
Path("README.md"),
Path("LICENSE"),
Path("claude"),
Path("codex"),
Path("cursor"),
Path("shared"),
]
REPO_ONLY_FILE_PATTERNS = [
re.compile(r"(^|/)BUILD(?:\.in)?$"),
re.compile(r"(^|/)mcp_host_skills_tests\.py$"),
re.compile(r"(^|/)package_validation_tests\.py$"),
re.compile(r"^temp/"),
re.compile(r"(^|/)__pycache__/"),
re.compile(r"(^|/)\.DS_Store$"),
]
DISALLOWED_PUBLIC_PATTERNS = [
re.compile(r"/Users/[^/\s]+/\.codex"),
re.compile(r"/Users/[^/\s]+/Developer"),
re.compile(r"/Users/[^/\s]+/src/server"),
re.compile(r"/home/[^/\s]+/src/server"),
re.compile(r"\b[A-Za-z0-9_-]+-internal\b", re.IGNORECASE),
re.compile(
r"\.(?:corp|dev|pp)\.[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b",
re.IGNORECASE,
),
re.compile(r"\blocalhost\b", re.IGNORECASE),
re.compile(r"\b127\.0\.0\.1\b"),
re.compile(r"\bngrok\b", re.IGNORECASE),
re.compile(r"\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}\b"),
re.compile(r"\bsk-[A-Za-z0-9]{20,}\b"),
re.compile(r"\bxox[abprs]-[A-Za-z0-9-]{20,}\b"),
re.compile(
r"\b(?:api[_-]?key|client[_-]?secret|access[_-]?token|"
r"refresh[_-]?token)\b\s*[:=]\s*[\"']?[A-Za-z0-9_./+=-]{8,}",
re.IGNORECASE,
),
re.compile(
r"\bauthorization\s*:\s*bearer\s+[A-Za-z0-9._~+/-]+=*",
re.IGNORECASE,
),
]
def _package_root() -> Path:
return REPO_ROOT
def _load_json(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
assert isinstance(value, dict), f"{path} must contain a JSON object."
return value
def _mcp_host_root_for_manifest(package_root: Path, manifest_path: Path) -> Path:
if manifest_path.parts[:2] == ("codex", ".codex-plugin"):
return package_root / "codex"
return package_root / manifest_path.parts[0]
def _resolve_relative_path(root: Path, raw_path: str) -> Path:
assert raw_path.startswith("./"), (
f"Manifest path must be ./-relative: {raw_path!r}"
)
resolved = (root / raw_path.removeprefix("./")).resolve()
root_resolved = root.resolve()
assert root_resolved == resolved or root_resolved in resolved.parents, (
f"Manifest path escapes MCP Host root: {raw_path!r}"
)
return resolved
def _iter_manifest_path_references(manifest: dict[str, Any]) -> Iterable[str]:
for key in ("skills", "mcpServers", "apps", "hooks", "logo"):
value = manifest.get(key)
if isinstance(value, str):
yield value
interface = manifest.get("interface")
if not isinstance(interface, dict):
return
for key in ("composerIcon", "logo"):
value = interface.get(key)
if isinstance(value, str):
yield value
screenshots = interface.get("screenshots")
if isinstance(screenshots, list):
for value in screenshots:
if isinstance(value, str):
yield value
def _iter_public_package_files(package_root: Path) -> Iterable[Path]:
for relative_package_path in PUBLIC_PACKAGE_PATHS:
package_path = package_root / relative_package_path
if package_path.is_file():
yield package_path
continue
for path in sorted(package_path.rglob("*")):
if path.is_file():
yield path
def test_mcp_host_json_files_are_valid() -> None:
package_root = _package_root()
for relative_path in [
*MANIFEST_PATHS,
*(Path(path) for path in EXPECTED_MCP_ENDPOINTS),
]:
path = package_root / relative_path
assert path.is_file(), f"Missing required JSON file: {relative_path}"
_load_json(path)
def test_mcp_host_required_files_exist() -> None:
package_root = _package_root()
for relative_path in REQUIRED_MCP_HOST_FILES:
assert (package_root / relative_path).is_file(), (
f"Missing required MCP Host file: {relative_path}"
)
def test_mcp_host_manifest_path_references_are_valid() -> None:
package_root = _package_root()
for manifest_path in MANIFEST_PATHS:
manifest = _load_json(package_root / manifest_path)
mcp_host_root = _mcp_host_root_for_manifest(package_root, manifest_path)
for raw_path in _iter_manifest_path_references(manifest):
resolved_path = _resolve_relative_path(mcp_host_root, raw_path)
assert resolved_path.exists(), (
f"{manifest_path} references a missing path: {raw_path}"
)
def test_mcp_configs_use_only_production_dropbox_endpoints() -> None:
package_root = _package_root()
for relative_path, expected in EXPECTED_MCP_ENDPOINTS.items():
config = _load_json(package_root / relative_path)
mcp_servers = config.get("mcpServers")
assert isinstance(mcp_servers, dict), (
f"{relative_path} must define mcpServers."
)
assert set(mcp_servers) == {expected["name"]}, (
f"{relative_path} must define only {expected['name']}."
)
server = mcp_servers[expected["name"]]
assert isinstance(server, dict), (
f"{relative_path} server config must be an object."
)
assert server.get("type") == "http", (
f"{relative_path} must use an HTTP MCP server."
)
assert server.get("url") == expected["url"], (
f"{relative_path} must point at the production endpoint."
)
def test_public_package_paths_exclude_repo_only_files() -> None:
package_root = _package_root()
repo_only_files = []
for path in _iter_public_package_files(package_root):
relative_path = path.relative_to(package_root).as_posix()
if any(
pattern.search(relative_path)
for pattern in REPO_ONLY_FILE_PATTERNS
):
repo_only_files.append(relative_path)
assert not repo_only_files, (
"Public plugin package paths must not include repo-only files:\n"
+ "\n".join(repo_only_files)
)
def test_public_package_files_do_not_contain_private_or_secret_material() -> None:
package_root = _package_root()
failures: list[str] = []
for path in _iter_public_package_files(package_root):
if path.suffix not in {".json", ".md", ".yaml", ".yml"}:
continue
contents = path.read_text(encoding="utf-8")
relative_path = path.relative_to(package_root).as_posix()
for pattern in DISALLOWED_PUBLIC_PATTERNS:
if pattern.search(contents):
failures.append(f"{relative_path} matches {pattern.pattern}")
assert not failures, (
"Public plugin package files must not contain private URLs, local paths, "
"or secret-looking values:\n" + "\n".join(failures)
)
def _run_tests() -> None:
tests = [
(name, test)
for name, test in sorted(globals().items())
if name.startswith("test_") and callable(test)
]
for _, test in tests:
test()
print(f"{Path(__file__).name}: {len(tests)} tests passed")
if __name__ == "__main__":
_run_tests()