Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).

<details>
<summary>Manual install</summary>

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
48 changes: 48 additions & 0 deletions install-pinned.ps1
Original file line number Diff line number Diff line change
@@ -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
46 changes: 46 additions & 0 deletions install-pinned.sh
Original file line number Diff line number Diff line change
@@ -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 -- "$@"
56 changes: 38 additions & 18 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ 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
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) }
}

Expand Down Expand Up @@ -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
Expand Down
75 changes: 50 additions & 25 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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*) ;;
Expand All @@ -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=<path>] [--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=<path>] [--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
Expand Down Expand Up @@ -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
Expand Down
37 changes: 29 additions & 8 deletions pkg/npm/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,29 +64,46 @@ 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')
.update(fs.readFileSync(archivePath))
.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 */ }
}
Expand Down Expand Up @@ -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);
});
Loading