codebase-memory-mcp 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-memory-mcp
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Fast code intelligence engine for AI coding agents — single static binary MCP server
5
5
  Project-URL: Homepage, https://github.com/DeusData/codebase-memory-mcp
6
6
  Project-URL: Repository, https://github.com/DeusData/codebase-memory-mcp
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codebase-memory-mcp"
7
- version = "0.6.0"
7
+ version = "0.7.0"
8
8
  description = "Fast code intelligence engine for AI coding agents — single static binary MCP server"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -0,0 +1,220 @@
1
+ """Downloads the codebase-memory-mcp binary on first run, then exec's it."""
2
+
3
+ import hashlib
4
+ import os
5
+ import sys
6
+ import platform
7
+ import stat
8
+ import shutil
9
+ import tempfile
10
+ import urllib.request
11
+ import urllib.error
12
+ import urllib.parse
13
+ from pathlib import Path
14
+
15
+ REPO = "DeusData/codebase-memory-mcp"
16
+
17
+ # Security: only permit https fetches. urllib's default handlers accept
18
+ # file://, ftp://, and custom schemes — a redirect or tainted URL source
19
+ # could otherwise turn a download into an arbitrary-local-file read.
20
+ _ALLOWED_SCHEMES = frozenset({"https"})
21
+
22
+
23
+ def _validate_url_scheme(url: str) -> None:
24
+ """Reject non-https URLs before any network fetch."""
25
+ scheme = urllib.parse.urlparse(url).scheme
26
+ if scheme not in _ALLOWED_SCHEMES:
27
+ sys.exit(
28
+ f"codebase-memory-mcp: refusing to fetch non-https URL "
29
+ f"(scheme={scheme!r}): {url}"
30
+ )
31
+
32
+
33
+ def _safe_extract_tar(tf, dest: str) -> None:
34
+ """Extract a tarfile to dest, rejecting path-traversal entries.
35
+
36
+ Uses the tarfile data filter on Python >=3.12 (PEP 706), falls back to
37
+ manual per-member path validation on older Pythons. Mitigates the
38
+ classic tar-slip / Zip Slip vulnerability (CWE-22).
39
+ """
40
+ if hasattr(tf, "extraction_filter") or sys.version_info >= (3, 12):
41
+ tf.extractall(dest, filter="data")
42
+ return
43
+
44
+ dest_abs = os.path.abspath(dest)
45
+ for member in tf.getmembers():
46
+ if member.issym() or member.islnk():
47
+ sys.exit(
48
+ f"codebase-memory-mcp: refusing unsafe tar entry "
49
+ f"(link: {member.name!r})"
50
+ )
51
+ member_abs = os.path.abspath(os.path.join(dest_abs, member.name))
52
+ if not (member_abs == dest_abs or member_abs.startswith(dest_abs + os.sep)):
53
+ sys.exit(
54
+ f"codebase-memory-mcp: refusing unsafe tar entry "
55
+ f"(escapes dest: {member.name!r})"
56
+ )
57
+ tf.extractall(dest)
58
+
59
+
60
+ def _safe_extract_zip(zf, dest: str) -> None:
61
+ """Extract a zipfile to dest, rejecting path-traversal entries."""
62
+ dest_abs = os.path.abspath(dest)
63
+ for name in zf.namelist():
64
+ member_abs = os.path.abspath(os.path.join(dest_abs, name))
65
+ if not (member_abs == dest_abs or member_abs.startswith(dest_abs + os.sep)):
66
+ sys.exit(
67
+ f"codebase-memory-mcp: refusing unsafe zip entry "
68
+ f"(escapes dest: {name!r})"
69
+ )
70
+ zf.extractall(dest)
71
+
72
+
73
+ def _verify_checksum(archive_path: str, archive_name: str, version: str) -> None:
74
+ """Verify SHA256 checksum against checksums.txt from the release."""
75
+ url = f"https://github.com/{REPO}/releases/download/v{version}/checksums.txt"
76
+ try:
77
+ _validate_url_scheme(url)
78
+ with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp:
79
+ tmp_path = tmp.name
80
+ urllib.request.urlretrieve(url, tmp_path) # noqa: S310 — scheme validated above
81
+ with open(tmp_path) as f:
82
+ for line in f:
83
+ if archive_name in line:
84
+ expected = line.split()[0]
85
+ h = hashlib.sha256()
86
+ with open(archive_path, "rb") as af:
87
+ for chunk in iter(lambda: af.read(65536), b""):
88
+ h.update(chunk)
89
+ actual = h.hexdigest()
90
+ if expected != actual:
91
+ sys.exit(
92
+ f"codebase-memory-mcp: CHECKSUM MISMATCH for {archive_name}\n"
93
+ f" expected: {expected}\n"
94
+ f" actual: {actual}"
95
+ )
96
+ print("codebase-memory-mcp: checksum verified.", file=sys.stderr)
97
+ break
98
+ except SystemExit:
99
+ raise
100
+ except Exception:
101
+ pass # Non-fatal: checksum unavailable
102
+ finally:
103
+ try:
104
+ os.unlink(tmp_path)
105
+ except Exception:
106
+ pass
107
+
108
+
109
+ def _version() -> str:
110
+ try:
111
+ from importlib.metadata import version
112
+ return version("codebase-memory-mcp")
113
+ except Exception:
114
+ return "0.6.0"
115
+
116
+
117
+ def _os_name() -> str:
118
+ p = sys.platform
119
+ if p == "linux":
120
+ return "linux"
121
+ if p == "darwin":
122
+ return "darwin"
123
+ if p == "win32":
124
+ return "windows"
125
+ sys.exit(f"codebase-memory-mcp: unsupported platform: {p}")
126
+
127
+
128
+ def _arch() -> str:
129
+ m = platform.machine().lower()
130
+ if m in ("arm64", "aarch64"):
131
+ return "arm64"
132
+ if m in ("x86_64", "amd64"):
133
+ return "amd64"
134
+ sys.exit(f"codebase-memory-mcp: unsupported architecture: {m}")
135
+
136
+
137
+ def _cache_dir() -> Path:
138
+ if sys.platform == "win32":
139
+ base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
140
+ elif sys.platform == "darwin":
141
+ base = Path.home() / "Library" / "Caches"
142
+ else:
143
+ base = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
144
+ return base / "codebase-memory-mcp"
145
+
146
+
147
+ def _bin_path(version: str) -> Path:
148
+ name = "codebase-memory-mcp.exe" if sys.platform == "win32" else "codebase-memory-mcp"
149
+ return _cache_dir() / version / name
150
+
151
+
152
+ def _download(version: str) -> Path:
153
+ os_name = _os_name()
154
+ arch = _arch()
155
+ ext = "zip" if os_name == "windows" else "tar.gz"
156
+ archive = f"codebase-memory-mcp-{os_name}-{arch}.{ext}"
157
+ url = f"https://github.com/{REPO}/releases/download/v{version}/{archive}"
158
+ _validate_url_scheme(url)
159
+
160
+ dest = _bin_path(version)
161
+ dest.parent.mkdir(parents=True, exist_ok=True)
162
+
163
+ print(
164
+ f"codebase-memory-mcp: downloading v{version} for {os_name}/{arch}...",
165
+ file=sys.stderr,
166
+ )
167
+
168
+ with tempfile.TemporaryDirectory() as tmp:
169
+ tmp_archive = os.path.join(tmp, f"cbm.{ext}")
170
+ try:
171
+ urllib.request.urlretrieve(url, tmp_archive) # noqa: S310 — scheme validated above
172
+ except urllib.error.HTTPError as e:
173
+ sys.exit(
174
+ f"codebase-memory-mcp: download failed ({e})\n"
175
+ f"URL: {url}\n"
176
+ f"See https://github.com/{REPO}/releases for available versions."
177
+ )
178
+
179
+ _verify_checksum(tmp_archive, archive, version)
180
+
181
+ if ext == "tar.gz":
182
+ import tarfile
183
+ with tarfile.open(tmp_archive) as tf:
184
+ _safe_extract_tar(tf, tmp)
185
+ else:
186
+ import zipfile
187
+ with zipfile.ZipFile(tmp_archive) as zf:
188
+ _safe_extract_zip(zf, tmp)
189
+
190
+ bin_name = "codebase-memory-mcp.exe" if os_name == "windows" else "codebase-memory-mcp"
191
+ extracted = os.path.join(tmp, bin_name)
192
+ if not os.path.exists(extracted):
193
+ sys.exit("codebase-memory-mcp: binary not found after extraction")
194
+
195
+ shutil.copy2(extracted, dest)
196
+ current = dest.stat().st_mode
197
+ dest.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
198
+
199
+ return dest
200
+
201
+
202
+ def main() -> None:
203
+ version = _version()
204
+ bin_path = _bin_path(version)
205
+
206
+ if not bin_path.exists():
207
+ bin_path = _download(version)
208
+
209
+ # args is a list (not a shell string), so exec/subprocess treat each
210
+ # element as a discrete argv entry — no shell interpretation, no
211
+ # injection vector. sys.argv forwarding is the whole point of this
212
+ # shim, so tainted-input suppression is intentional.
213
+ args = [str(bin_path)] + sys.argv[1:]
214
+
215
+ if sys.platform != "win32":
216
+ os.execv(str(bin_path), args) # noqa: S606 — list form, no shell
217
+ else:
218
+ import subprocess
219
+ result = subprocess.run(args) # noqa: S603 — list form, no shell=True
220
+ sys.exit(result.returncode)
@@ -1,122 +0,0 @@
1
- """Downloads the codebase-memory-mcp binary on first run, then exec's it."""
2
-
3
- import os
4
- import sys
5
- import platform
6
- import stat
7
- import shutil
8
- import tempfile
9
- import urllib.request
10
- import urllib.error
11
- from pathlib import Path
12
-
13
- REPO = "DeusData/codebase-memory-mcp"
14
-
15
-
16
- def _version() -> str:
17
- try:
18
- from importlib.metadata import version
19
- return version("codebase-memory-mcp")
20
- except Exception:
21
- return "0.6.0"
22
-
23
-
24
- def _os_name() -> str:
25
- p = sys.platform
26
- if p == "linux":
27
- return "linux"
28
- if p == "darwin":
29
- return "darwin"
30
- if p == "win32":
31
- return "windows"
32
- sys.exit(f"codebase-memory-mcp: unsupported platform: {p}")
33
-
34
-
35
- def _arch() -> str:
36
- m = platform.machine().lower()
37
- if m in ("arm64", "aarch64"):
38
- return "arm64"
39
- if m in ("x86_64", "amd64"):
40
- return "amd64"
41
- sys.exit(f"codebase-memory-mcp: unsupported architecture: {m}")
42
-
43
-
44
- def _cache_dir() -> Path:
45
- if sys.platform == "win32":
46
- base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
47
- elif sys.platform == "darwin":
48
- base = Path.home() / "Library" / "Caches"
49
- else:
50
- base = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
51
- return base / "codebase-memory-mcp"
52
-
53
-
54
- def _bin_path(version: str) -> Path:
55
- name = "codebase-memory-mcp.exe" if sys.platform == "win32" else "codebase-memory-mcp"
56
- return _cache_dir() / version / name
57
-
58
-
59
- def _download(version: str) -> Path:
60
- os_name = _os_name()
61
- arch = _arch()
62
- ext = "zip" if os_name == "windows" else "tar.gz"
63
- url = (
64
- f"https://github.com/{REPO}/releases/download/v{version}"
65
- f"/codebase-memory-mcp-{os_name}-{arch}.{ext}"
66
- )
67
-
68
- dest = _bin_path(version)
69
- dest.parent.mkdir(parents=True, exist_ok=True)
70
-
71
- print(
72
- f"codebase-memory-mcp: downloading v{version} for {os_name}/{arch}...",
73
- file=sys.stderr,
74
- )
75
-
76
- with tempfile.TemporaryDirectory() as tmp:
77
- tmp_archive = os.path.join(tmp, f"cbm.{ext}")
78
- try:
79
- urllib.request.urlretrieve(url, tmp_archive)
80
- except urllib.error.HTTPError as e:
81
- sys.exit(
82
- f"codebase-memory-mcp: download failed ({e})\n"
83
- f"URL: {url}\n"
84
- f"See https://github.com/{REPO}/releases for available versions."
85
- )
86
-
87
- if ext == "tar.gz":
88
- import tarfile
89
- with tarfile.open(tmp_archive) as tf:
90
- tf.extractall(tmp)
91
- else:
92
- import zipfile
93
- with zipfile.ZipFile(tmp_archive) as zf:
94
- zf.extractall(tmp)
95
-
96
- bin_name = "codebase-memory-mcp.exe" if os_name == "windows" else "codebase-memory-mcp"
97
- extracted = os.path.join(tmp, bin_name)
98
- if not os.path.exists(extracted):
99
- sys.exit(f"codebase-memory-mcp: binary not found after extraction")
100
-
101
- shutil.copy2(extracted, dest)
102
- current = dest.stat().st_mode
103
- dest.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
104
-
105
- return dest
106
-
107
-
108
- def main() -> None:
109
- version = _version()
110
- bin_path = _bin_path(version)
111
-
112
- if not bin_path.exists():
113
- bin_path = _download(version)
114
-
115
- args = [str(bin_path)] + sys.argv[1:]
116
-
117
- if sys.platform != "win32":
118
- os.execv(str(bin_path), args)
119
- else:
120
- import subprocess
121
- result = subprocess.run(args)
122
- sys.exit(result.returncode)