From ef41cd00defe34ef1decdc9e613360a23ecbcb5b Mon Sep 17 00:00:00 2001 From: Johannes Fischer Date: Fri, 10 Jul 2026 15:39:55 -0600 Subject: [PATCH 1/2] fix(install): make bootstrap installer checksum verification fail closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four first-install paths (install.sh, install.ps1, npm postinstall, PyPI shim) previously treated checksum verification as best-effort: if checksums.txt could not be fetched, the archive was missing from the list, or (in the shell installer) no SHA-256 tool was available, they silently skipped verification and installed the binary anyway. Only the in-binary `update` command failed closed. Make all four abort rather than install an unverified binary when a positive checksum match cannot be obtained. An explicit bypass — CBM_SKIP_CHECKSUM=1 (or --skip-checksum on the shell installers) — is provided for local development against unsigned test builds, with a warning. This aligns the installers with the fail-closed `update` flow and the SHA-256 verification claim in SECURITY.md, which is updated to match. Also switch the PowerShell installer's new error strings from em-dashes to ASCII hyphens: em-dashes inside double-quoted strings break parsing when the shipped UTF-8 (no BOM) script is run via `-File` on Windows PowerShell 5.1, which mis-decodes the multibyte character. Verified end-to-end: valid checksum installs; mismatch, unavailable checksums, and archive-not-listed all abort with no binary written; bypass warns and proceeds. Co-Authored-By: Claude Opus 4.8 --- SECURITY.md | 2 +- install.ps1 | 56 +++++++++++------ install.sh | 75 +++++++++++++++-------- pkg/npm/install.js | 37 +++++++++--- pkg/pypi/src/codebase_memory_mcp/_cli.py | 77 +++++++++++++++++------- 5 files changed, 172 insertions(+), 75 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index d5153f62e..a61fcb361 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -146,7 +146,7 @@ If ANY antivirus engine flags ANY binary, the release stays as a draft and is no - **CORS locked to localhost** — graph UI only accessible from localhost origins - **Path containment** — `realpath()` check prevents reading files outside project root - **Process-kill restriction** — only server-spawned PIDs can be terminated -- **SHA-256 checksum verification** — update command verifies downloaded binary before installing +- **SHA-256 checksum verification (fail closed)** — the `update` command and every bootstrap installer (`install.sh`, `install.ps1`, npm, PyPI) verify the downloaded archive against `checksums.txt` and **abort** if it cannot be positively verified (checksums unavailable, archive missing from the list, or a mismatch). Verification can be bypassed only for local/unsigned test builds by setting `CBM_SKIP_CHECKSUM=1` (or passing `--skip-checksum` to the shell installers). ### Verification diff --git a/install.ps1 b/install.ps1 index 9eebc7659..b82c77161 100644 --- a/install.ps1 +++ b/install.ps1 @@ -21,6 +21,10 @@ if (-not $BaseUrl.StartsWith("https://") -and -not $BaseUrl.StartsWith("http://l exit 1 } +# Checksum verification is mandatory (fail closed) unless explicitly bypassed. +# The bypass exists only for local development against unsigned test builds. +$SkipChecksum = @("1", "true", "yes") -contains ("$env:CBM_SKIP_CHECKSUM").ToLower() + # Detect variant from args (--ui or --standard) $Variant = "standard" $SkipConfig = $false @@ -28,6 +32,7 @@ foreach ($arg in $args) { if ($arg -eq "--ui") { $Variant = "ui" } if ($arg -eq "--standard") { $Variant = "standard" } if ($arg -eq "--skip-config") { $SkipConfig = $true } + if ($arg -eq "--skip-checksum") { $SkipChecksum = $true } if ($arg -like "--dir=*") { $InstallDir = $arg.Substring(6) } } @@ -81,25 +86,40 @@ try { } -# Checksum verification -$ChecksumUrl = "$BaseUrl/checksums.txt" -try { - Invoke-WebRequest -Uri $ChecksumUrl -OutFile "$TmpDir\checksums.txt" -UseBasicParsing - $checksumLine = Get-Content "$TmpDir\checksums.txt" | Where-Object { $_ -like "*$Archive*" } - if ($checksumLine) { - $expected = ($checksumLine -split '\s+')[0] - $actual = (Get-FileHash -Path "$TmpDir\$Archive" -Algorithm SHA256).Hash.ToLower() - if ($expected -ne $actual) { - Write-Host "error: CHECKSUM MISMATCH!" -ForegroundColor Red - Write-Host " expected: $expected" - Write-Host " actual: $actual" - Remove-Item -Recurse -Force $TmpDir - exit 1 - } - Write-Host "Checksum verified." +# Checksum verification (fail closed). +# Verify the downloaded archive against checksums.txt from the same release. +# Any failure to obtain a positive match — checksums unavailable, the archive +# missing from the list, or a mismatch — aborts the install rather than running +# an unverified binary. Set CBM_SKIP_CHECKSUM=1 (or pass --skip-checksum) to +# bypass; intended only for local/unsigned test builds. +if ($SkipChecksum) { + Write-Host "warning: skipping checksum verification (CBM_SKIP_CHECKSUM/--skip-checksum set)" -ForegroundColor Yellow +} else { + $ChecksumUrl = "$BaseUrl/checksums.txt" + try { + Invoke-WebRequest -Uri $ChecksumUrl -OutFile "$TmpDir\checksums.txt" -UseBasicParsing + } catch { + Write-Host "error: could not download checksums.txt - refusing to install an unverified binary" -ForegroundColor Red + Write-Host " (set CBM_SKIP_CHECKSUM=1 to bypass for local/unsigned builds)" + Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue + exit 1 } -} catch { - Write-Host "warning: could not verify checksum (non-fatal)" + $checksumLine = Get-Content "$TmpDir\checksums.txt" | Where-Object { $_ -like "*$Archive*" } | Select-Object -First 1 + if (-not $checksumLine) { + Write-Host "error: $Archive not found in checksums.txt - cannot verify download" -ForegroundColor Red + Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue + exit 1 + } + $expected = ($checksumLine -split '\s+')[0] + $actual = (Get-FileHash -Path "$TmpDir\$Archive" -Algorithm SHA256).Hash.ToLower() + if ($expected -ne $actual) { + Write-Host "error: CHECKSUM MISMATCH - download may be corrupted or tampered!" -ForegroundColor Red + Write-Host " expected: $expected" + Write-Host " actual: $actual" + Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue + exit 1 + } + Write-Host "Checksum verified." } # Extract diff --git a/install.sh b/install.sh index d0678011d..750ce69f0 100755 --- a/install.sh +++ b/install.sh @@ -23,6 +23,13 @@ VARIANT="standard" SKIP_CONFIG=false CBM_DOWNLOAD_URL="${CBM_DOWNLOAD_URL:-https://github.com/${REPO}/releases/latest/download}" +# Checksum verification is mandatory (fail closed) unless explicitly bypassed. +# The bypass exists only for local development against unsigned test builds. +case "${CBM_SKIP_CHECKSUM:-}" in + 1|true|TRUE|yes|YES) SKIP_CHECKSUM=true ;; + *) SKIP_CHECKSUM=false ;; +esac + # Security: reject non-HTTPS download URLs (defense-in-depth) case "$CBM_DOWNLOAD_URL" in https://*|http://localhost*|http://127.0.0.1*) ;; @@ -35,12 +42,14 @@ for arg in "$@"; do --standard) VARIANT="standard" ;; --dir=*) INSTALL_DIR="${arg#--dir=}" ;; --skip-config) SKIP_CONFIG=true ;; + --skip-checksum) SKIP_CHECKSUM=true ;; --help|-h) - echo "Usage: install.sh [--ui] [--dir=] [--skip-config]" - echo " --ui Install the UI variant (with graph visualization)" - echo " --standard Install the standard variant (default)" - echo " --dir PATH Install directory (default: ~/.local/bin)" - echo " --skip-config Skip automatic agent configuration" + echo "Usage: install.sh [--ui] [--dir=] [--skip-config] [--skip-checksum]" + echo " --ui Install the UI variant (with graph visualization)" + echo " --standard Install the standard variant (default)" + echo " --dir PATH Install directory (default: ~/.local/bin)" + echo " --skip-config Skip automatic agent configuration" + echo " --skip-checksum Skip SHA-256 verification (unsigned/local builds only)" exit 0 ;; esac @@ -125,27 +134,43 @@ else exit 1 fi -# Checksum verification -CHECKSUM_URL="${CBM_DOWNLOAD_URL}/checksums.txt" -if curl -fsSL -o "$DLDIR/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then - EXPECTED=$(grep "$ARCHIVE" "$DLDIR/checksums.txt" | awk '{print $1}') - if [ -n "$EXPECTED" ]; then - if command -v sha256sum &>/dev/null; then - ACTUAL=$(sha256sum "$DLDIR/$ARCHIVE" | awk '{print $1}') - elif command -v shasum &>/dev/null; then - ACTUAL=$(shasum -a 256 "$DLDIR/$ARCHIVE" | awk '{print $1}') - else - ACTUAL="" - fi - if [ -n "$ACTUAL" ] && [ "$EXPECTED" != "$ACTUAL" ]; then - echo "error: CHECKSUM MISMATCH — download may be corrupted!" >&2 - echo " expected: $EXPECTED" >&2 - echo " actual: $ACTUAL" >&2 - exit 1 - elif [ -n "$ACTUAL" ]; then - echo "Checksum verified." - fi +# Checksum verification (fail closed). +# Verify the downloaded archive against checksums.txt from the same release. +# Any failure to obtain a positive match — checksums unavailable, the archive +# missing from the list, no SHA-256 tool, or a mismatch — aborts the install +# rather than running an unverified binary. Set CBM_SKIP_CHECKSUM=1 (or pass +# --skip-checksum) to bypass; intended only for local/unsigned test builds. +if [ "$SKIP_CHECKSUM" = true ]; then + echo "warning: skipping checksum verification (CBM_SKIP_CHECKSUM/--skip-checksum set)" >&2 +else + CHECKSUM_URL="${CBM_DOWNLOAD_URL}/checksums.txt" + if ! curl -fsSL -o "$DLDIR/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then + echo "error: could not download checksums.txt — refusing to install an unverified binary" >&2 + echo " (set CBM_SKIP_CHECKSUM=1 to bypass for local/unsigned builds)" >&2 + exit 1 + fi + # `|| true`: grep exits non-zero on no match, which under `set -e`/pipefail + # would abort before our explicit "not found" message below. + EXPECTED=$(grep "$ARCHIVE" "$DLDIR/checksums.txt" | awk '{print $1}') || true + if [ -z "$EXPECTED" ]; then + echo "error: $ARCHIVE not found in checksums.txt — cannot verify download" >&2 + exit 1 + fi + if command -v sha256sum &>/dev/null; then + ACTUAL=$(sha256sum "$DLDIR/$ARCHIVE" | awk '{print $1}') + elif command -v shasum &>/dev/null; then + ACTUAL=$(shasum -a 256 "$DLDIR/$ARCHIVE" | awk '{print $1}') + else + echo "error: no SHA-256 tool (sha256sum/shasum) available to verify download" >&2 + exit 1 + fi + if [ "$EXPECTED" != "$ACTUAL" ]; then + echo "error: CHECKSUM MISMATCH — download may be corrupted or tampered!" >&2 + echo " expected: $EXPECTED" >&2 + echo " actual: $ACTUAL" >&2 + exit 1 fi + echo "Checksum verified." fi # Extract diff --git a/pkg/npm/install.js b/pkg/npm/install.js index 25bb6a80f..413cbed8c 100644 --- a/pkg/npm/install.js +++ b/pkg/npm/install.js @@ -64,15 +64,35 @@ function download(url, dest) { }); } -// Fetch checksums.txt and verify the archive hash. +// Truthy check for the CBM_SKIP_CHECKSUM bypass (local/unsigned builds only). +function skipChecksum() { + return ['1', 'true', 'yes'].includes((process.env.CBM_SKIP_CHECKSUM || '').toLowerCase()); +} + +// Fetch checksums.txt and verify the archive hash (fail closed). +// Any failure to obtain a positive match — checksums unavailable, the archive +// missing from the list, or a mismatch — throws so the caller aborts before +// installing an unverified binary. Bypass with CBM_SKIP_CHECKSUM=1. async function verifyChecksum(archivePath, archiveName) { + if (skipChecksum()) { + process.stderr.write('codebase-memory-mcp: skipping checksum verification (CBM_SKIP_CHECKSUM set).\n'); + return; + } const url = `https://github.com/${REPO}/releases/download/v${VERSION}/checksums.txt`; const tmpChecksums = archivePath + '.checksums'; try { - await download(url, tmpChecksums); + try { + await download(url, tmpChecksums); + } catch (err) { + throw new Error( + `could not download checksums.txt (${err.message}) — refusing to install an unverified binary`, + ); + } const lines = fs.readFileSync(tmpChecksums, 'utf-8').split('\n'); const match = lines.find((l) => l.includes(archiveName)); - if (!match) return; // checksum line not found — non-fatal + if (!match) { + throw new Error(`${archiveName} not found in checksums.txt — cannot verify download`); + } const expected = match.split(/\s+/)[0]; const actual = crypto .createHash('sha256') @@ -80,13 +100,10 @@ async function verifyChecksum(archivePath, archiveName) { .digest('hex'); if (expected !== actual) { throw new Error( - `Checksum mismatch for ${archiveName}:\n expected: ${expected}\n actual: ${actual}`, + `checksum mismatch for ${archiveName}:\n expected: ${expected}\n actual: ${actual}`, ); } process.stdout.write('codebase-memory-mcp: checksum verified.\n'); - } catch (err) { - if (err.message.startsWith('Checksum mismatch')) throw err; - // Non-fatal: checksum unavailable (network issue, pre-release, etc.) } finally { try { fs.unlinkSync(tmpChecksums); } catch (_) { /* ignore */ } } @@ -158,6 +175,10 @@ async function main() { main().catch((err) => { process.stderr.write(`\ncodebase-memory-mcp: install failed — ${err.message}\n`); process.stderr.write(`You can install manually: https://github.com/${REPO}#installation\n`); - // Non-fatal: don't block the rest of npm install + // Exit 0 so the `bin` shim still links and the rest of `npm install` is not + // blocked. This does NOT weaken fail-closed verification: verifyChecksum() + // throws before the binary is copied, so a failed/unverified download leaves + // no binary on disk. bin.js detects the missing binary on first run, retries + // this verified download, and reports a hard failure if it still can't verify. process.exit(0); }); diff --git a/pkg/pypi/src/codebase_memory_mcp/_cli.py b/pkg/pypi/src/codebase_memory_mcp/_cli.py index d192f1352..703b41170 100644 --- a/pkg/pypi/src/codebase_memory_mcp/_cli.py +++ b/pkg/pypi/src/codebase_memory_mcp/_cli.py @@ -70,40 +70,71 @@ def _safe_extract_zip(zf, dest: str) -> None: zf.extractall(dest) +def _skip_checksum() -> bool: + """Whether the CBM_SKIP_CHECKSUM bypass is set (local/unsigned builds only).""" + return os.environ.get("CBM_SKIP_CHECKSUM", "").lower() in ("1", "true", "yes") + + def _verify_checksum(archive_path: str, archive_name: str, version: str) -> None: - """Verify SHA256 checksum against checksums.txt from the release.""" + """Verify SHA256 checksum against checksums.txt from the release (fail closed). + + Any failure to obtain a positive match — checksums unavailable, the archive + missing from the list, or a mismatch — aborts rather than running an + unverified binary. Set CBM_SKIP_CHECKSUM=1 to bypass (local/unsigned builds). + """ + if _skip_checksum(): + print( + "codebase-memory-mcp: skipping checksum verification " + "(CBM_SKIP_CHECKSUM set).", + file=sys.stderr, + ) + return + url = f"https://github.com/{REPO}/releases/download/v{version}/checksums.txt" + _validate_url_scheme(url) + tmp_path = None try: - _validate_url_scheme(url) with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp: tmp_path = tmp.name - urllib.request.urlretrieve(url, tmp_path) # noqa: S310 — scheme validated above + try: + urllib.request.urlretrieve(url, tmp_path) # noqa: S310 — scheme validated above + except Exception as e: + sys.exit( + f"codebase-memory-mcp: could not download checksums.txt ({e})\n" + " refusing to install an unverified binary\n" + " (set CBM_SKIP_CHECKSUM=1 to bypass for local/unsigned builds)" + ) + + expected = None with open(tmp_path) as f: for line in f: if archive_name in line: expected = line.split()[0] - h = hashlib.sha256() - with open(archive_path, "rb") as af: - for chunk in iter(lambda: af.read(65536), b""): - h.update(chunk) - actual = h.hexdigest() - if expected != actual: - sys.exit( - f"codebase-memory-mcp: CHECKSUM MISMATCH for {archive_name}\n" - f" expected: {expected}\n" - f" actual: {actual}" - ) - print("codebase-memory-mcp: checksum verified.", file=sys.stderr) break - except SystemExit: - raise - except Exception: - pass # Non-fatal: checksum unavailable + if expected is None: + sys.exit( + f"codebase-memory-mcp: {archive_name} not found in checksums.txt " + "— cannot verify download" + ) + + h = hashlib.sha256() + with open(archive_path, "rb") as af: + for chunk in iter(lambda: af.read(65536), b""): + h.update(chunk) + actual = h.hexdigest() + if expected != actual: + sys.exit( + f"codebase-memory-mcp: CHECKSUM MISMATCH for {archive_name}\n" + f" expected: {expected}\n" + f" actual: {actual}" + ) + print("codebase-memory-mcp: checksum verified.", file=sys.stderr) finally: - try: - os.unlink(tmp_path) - except Exception: - pass + if tmp_path is not None: + try: + os.unlink(tmp_path) + except Exception: + pass def _version() -> str: From d4a37e01c252071df8280b5c321e930045da2f2e Mon Sep 17 00:00:00 2001 From: Johannes Fischer Date: Thu, 16 Jul 2026 14:02:51 -0600 Subject: [PATCH 2/2] feat(install): add pinned, fail-closed team installer wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add install-pinned.sh / install-pinned.ps1 for controlled internal distribution. Unlike install.sh/install.ps1, which track the "latest" upstream release, these wrappers pin the download to a specific release (v0.8.1 — the version synced to this fork and reviewed for security) by exporting CBM_DOWNLOAD_URL to that release's versioned asset path, then delegate to the fork's install.sh/install.ps1. Because those launchers now verify checksums fail-closed, a teammate gets exactly the vetted build or a hard abort — never an unverified binary. The launcher ref defaults to `main` and is overridable via CBM_INSTALL_REF (e.g. a commit SHA for a fully immutable launcher). All install.sh/install.ps1 flags are forwarded. README documents the team/pinned install and notes this verifies integrity against a pinned checksums.txt; full authenticity still requires verifying the release's cosign/SLSA attestations. Co-Authored-By: Claude Opus 4.8 --- README.md | 16 ++++++++++++++++ install-pinned.ps1 | 48 ++++++++++++++++++++++++++++++++++++++++++++++ install-pinned.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 install-pinned.ps1 create mode 100755 install-pinned.sh diff --git a/README.md b/README.md index 8ff708dd7..124246cf6 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,22 @@ Options: `--ui` (graph visualization), `--skip-config` (binary only, no agent se Restart your coding agent. Say **"Index this project"** — done. +### Team / pinned install (this fork) + +The one-liners above track the **latest** upstream release. For a controlled internal rollout, this fork provides a pinned wrapper that installs a specific release (currently **v0.8.1**, synced to this fork and reviewed for security) and refuses to install if the SHA-256 checksum cannot be verified against that release's `checksums.txt` (fail closed). + +macOS / Linux: +```bash +curl -fsSL https://raw.githubusercontent.com/jfischer-motivity/codebase-memory-mcp/main/install-pinned.sh | bash +``` + +Windows (PowerShell): +```powershell +irm https://raw.githubusercontent.com/jfischer-motivity/codebase-memory-mcp/main/install-pinned.ps1 | iex +``` + +All `install.sh` / `install.ps1` flags are forwarded (e.g. `| bash -s -- --ui`). To roll the team to a newer release, sync + re-review it upstream, then bump `CBM_VERSION` in `install-pinned.sh` and `install-pinned.ps1`. This verifies **integrity** against a pinned `checksums.txt`; for full **authenticity** also verify the release's cosign/SLSA attestations (see [SECURITY.md](SECURITY.md#verification)). +
Manual install diff --git a/install-pinned.ps1 b/install-pinned.ps1 new file mode 100644 index 000000000..c5fd7537f --- /dev/null +++ b/install-pinned.ps1 @@ -0,0 +1,48 @@ +# install-pinned.ps1 - Pinned, checksum-verified installer for team/internal use (Windows). +# +# Unlike install.ps1 (which tracks "latest"), this wrapper installs one specific +# release that has been synced to this fork and reviewed for security. It pins +# the download to that exact release and delegates to the fork's install.ps1, +# which verifies the SHA-256 checksum and FAILS CLOSED - it refuses to install +# if the binary cannot be positively verified against the release checksums.txt. +# +# Usage (team members): +# irm https://raw.githubusercontent.com/jfischer-motivity/codebase-memory-mcp/main/install-pinned.ps1 | iex +# +# To pass flags (e.g. --ui), download and invoke as a scriptblock: +# & ([scriptblock]::Create((irm https://raw.githubusercontent.com/jfischer-motivity/codebase-memory-mcp/main/install-pinned.ps1))) --ui +# +# Environment: +# CBM_INSTALL_REF Fork ref (branch/tag/commit) to fetch install.ps1 from. +# Defaults to "main". Set to a commit SHA for a fully +# immutable launcher. + +$ErrorActionPreference = "Stop" + +# Enforce TLS 1.2+ (older PowerShell defaults to TLS 1.0 which GitHub rejects). +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 + +# --- Pinned release -------------------------------------------------------- +# Version currently synced to this fork and analyzed for security. +# To roll the team forward: sync + re-review the new release, then bump this. +$CbmVersion = "v0.8.1" + +# Upstream repository that publishes the signed, checksummed release assets. +$UpstreamRepo = "DeusData/codebase-memory-mcp" + +# This fork, which hosts the fail-closed install.ps1 launcher. +$ForkRepo = "jfischer-motivity/codebase-memory-mcp" +$InstallRef = if ($env:CBM_INSTALL_REF) { $env:CBM_INSTALL_REF } else { "main" } + +# Pin ALL downloads (binary archive AND checksums.txt) to the exact release. +# install.ps1 reads this as the download base and aborts if verification fails. +$env:CBM_DOWNLOAD_URL = "https://github.com/$UpstreamRepo/releases/download/$CbmVersion" + +Write-Host "codebase-memory-mcp: installing pinned release $CbmVersion" +Write-Host " source: $($env:CBM_DOWNLOAD_URL)" +Write-Host " launcher: $ForkRepo@$InstallRef/install.ps1" +Write-Host "" + +$installUrl = "https://raw.githubusercontent.com/$ForkRepo/$InstallRef/install.ps1" +$installScript = (Invoke-WebRequest -Uri $installUrl -UseBasicParsing).Content +& ([scriptblock]::Create($installScript)) @args diff --git a/install-pinned.sh b/install-pinned.sh new file mode 100755 index 000000000..92fe97d0f --- /dev/null +++ b/install-pinned.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +# install-pinned.sh — Pinned, checksum-verified installer for team/internal use. +# +# Unlike install.sh (which tracks "latest"), this wrapper installs one specific +# release that has been synced to this fork and reviewed for security. It pins +# the download to that exact release and delegates to the fork's install.sh, +# which verifies the SHA-256 checksum and FAILS CLOSED — it refuses to install +# if the binary cannot be positively verified against the release checksums.txt. +# +# Usage (team members): +# curl -fsSL https://raw.githubusercontent.com/jfischer-motivity/codebase-memory-mcp/main/install-pinned.sh | bash +# curl -fsSL ... | bash -s -- --ui # UI variant (graph visualization) +# curl -fsSL ... | bash -s -- --dir=/opt/bin # custom install directory +# +# Every flag accepted by install.sh is forwarded unchanged. +# +# Environment: +# CBM_INSTALL_REF Fork ref (branch/tag/commit) to fetch install.sh from. +# Defaults to "main". Set to a commit SHA for a fully +# immutable launcher. + +# ── Pinned release ──────────────────────────────────────────────────────── +# Version currently synced to this fork and analyzed for security. +# To roll the team forward: sync + re-review the new release, then bump this. +CBM_VERSION="v0.8.1" + +# Upstream repository that publishes the signed, checksummed release assets. +UPSTREAM_REPO="DeusData/codebase-memory-mcp" + +# This fork, which hosts the fail-closed install.sh launcher. +FORK_REPO="jfischer-motivity/codebase-memory-mcp" +INSTALL_REF="${CBM_INSTALL_REF:-main}" + +# Pin ALL downloads (binary archive AND checksums.txt) to the exact release. +# install.sh reads this as the download base and aborts if verification fails. +export CBM_DOWNLOAD_URL="https://github.com/${UPSTREAM_REPO}/releases/download/${CBM_VERSION}" + +echo "codebase-memory-mcp: installing pinned release ${CBM_VERSION}" +echo " source: ${CBM_DOWNLOAD_URL}" +echo " launcher: ${FORK_REPO}@${INSTALL_REF}/install.sh" +echo "" + +INSTALL_URL="https://raw.githubusercontent.com/${FORK_REPO}/${INSTALL_REF}/install.sh" +curl -fsSL "$INSTALL_URL" | bash -s -- "$@"