Skip to content

installer: make checksum verification fail-closed on missing/malformed checksums.txt #1134

Closed

Description

@DeusData

Follow-up from #1087 review.

install.sh currently fail-opens on checksum verification: if checksums.txt can't be fetched (network error, MITM/blocked, 404), CHECKSUM_READY stays false and the compare block is silently skipped — the archive installs unverified. This is pre-existing (not introduced by #1087; that PR actually strengthened the match to exact-filename and added a wget fetch fallback).

Risk: an attacker who can block or MITM the checksums.txt fetch while serving a tampered archive bypasses integrity verification entirely.

Ask: make the installer fail-closed — if the checksum file is missing, unfetchable, or malformed (or the specific archive line is absent), abort the install with a clear error rather than proceeding unverified. Consider an explicit --skip-checksum opt-out for the rare legitimate case, so the default is safe.

Scope: install.sh (and mirror the same in scripts/smoke-test.sh:1682, which still uses the old substring grep form).

Activity

  1. added
    securitySecurity vulnerabilities, hardening
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Jul 16, 2026
  2. added this to the 0.9.1-rc milestone on Jul 16, 2026
  3. maheshmirchandani commented on Aug 10, 2026

    @maheshmirchandani

    Environment: v0.9.0 (also reproduced after the 2026-08-10 18:05 self-update), Claude Code 2.1.220 installMethod: native, macOS 26.5.2 (25F84) arm64.

    Supporting this with the exact line numbers, and adding two adjacent findings in the same script.

    Worth saying first that the signing infrastructure is real and works. gh attestation verify against .github/workflows/release.yml succeeds with the subject digest matching the downloaded archive byte for byte. The gap is that the documented install path does not reliably use it.

    The three paths where verification degrades to none

    Each proceeds with an unverified binary and prints no warning:

    1. L130 — if curl … 2>/dev/null; then, closed at L149 with no else. A failed checksums.txt fetch skips verification entirely.
    2. L132 — an empty $EXPECTED skips it.
    3. L138 — sets ACTUAL="" when neither sha256sum nor shasum exists, and L140 requires -n "$ACTUAL".

    Fail-closed on all three, as this issue proposes, would close it.

    Two adjacent findings

    --skip-config does not prevent the download. It gates only the "$DEST" install -y call at L193–204; the download at L118–126 runs unconditionally, and L24 defaults to releases/latest/download. A user who verifies a pinned artifact by hand and then runs install.sh --skip-config to place it will silently install a different, freshly downloaded one. We hit this exactly, and only noticed because the install was being audited.

    Post-install verification is impossible. codesign --force at L169–170 rewrites the signature in place, so the installed binary is 272,013,520 bytes against the attested 273,604,144. Anyone verifying after install gets a mismatch and would reasonably suspect tampering. Benign cause, confusing consequence — but it means the SLSA/Sigstore guarantee cannot be re-checked once the thing is on disk.

    Documentation defect in the same area

    v0.9.0 publishes two undifferentiated SHA-256 digests for what a reader downloading .tar.gz files would take to be one artifact. Both are legitimate — faa02f04… is the archive, d9fbdd7d… is the inner binary — but the release-notes column is headed "Binary" with nothing distinguishing them. Confirmed by extracting and hashing. Labelling the two would save the next person the detour.

    Two attestation qualifications worth documenting

    buildTrigger is workflow_dispatch rather than release, and the signer URI ends @refs/heads/main rather than @refs/tags/v0.9.0. The artifact is attested to the repository and workflow, but not tied to the tagged commit. A stable non-prerelease tag built by manual dispatch from a moving branch is a weaker guarantee than a tag-triggered build, and users verifying provenance may reasonably assume the stronger one.

  4. added
    awaiting-reporterWaiting on the reporter for info/repro; stale bot will warn then close
    on Aug 10, 2026
  5. github-actions commented on Aug 10, 2026

    @github-actions

    Thanks for the report! To move this forward we need a bit more so we can reproduce it ourselves:

    • the codebase-memory-mcp --version you're on
    • the exact steps or command you ran
    • a public repo (or a small dummy snippet) that shows the problem — please don't paste proprietary code

    Once that's here we'll pick it straight back up. Heads-up: issues left awaiting-reporter are automatically closed after a few weeks of silence, but a comment reopens the door anytime.

  6. removed
    awaiting-reporterWaiting on the reporter for info/repro; stale bot will warn then close
    on Aug 10, 2026
  7. AmooAti commented on Aug 12, 2026

    @AmooAti

    Same issue here, updating from 0.9.0 to 0.10.2 on mac

    
    > codebase-memory-mcp update
    codebase-memory-mcp update (current: 0.9.0)
    
    Update available: 0.9.0 -> v0.10.2
    Found 1 existing index(es) that must be rebuilt after update:
      /Users/myuser/.cache/codebase-memory-mcp/a_project.db
    
    Delete these indexes and continue with update? (y/n): y
    Removed 1 index(es).
    
    Which binary variant do you want?
      1) standard  — MCP server only
      2) ui        — MCP server + embedded graph visualization
    Choose (1/2): 2
    
    Downloading ui binary for darwin/arm64 ...
      https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/codebase-memory-mcp-ui-darwin-arm64.tar.gz
    ########################################################################################################################################################################################################### 100.0%
    warning: codebase-memory-mcp-ui-darwin-arm64.tar.gz not found in checksums.txt
    error: refusing to install an unverified download
    
    
  8. DeusData commented on Aug 15, 2026

    @DeusData
    OwnerAuthor

    Thank you for this, and I'm sorry — you were blocked from updating at all, you reported it with a full transcript, and it sat for three days without an answer.

    You were right, and the cause is entirely ours. I verified it against the live v0.10.4 release rather than reasoning about it:

    • 8 codebase-memory-mcp-ui-* archives are published on that release.
    • 0 of them appear in checksums.txt.

    So the failure you hit is exact and reproducible for anyone in your position:

    warning: codebase-memory-mcp-ui-darwin-arm64.tar.gz not found in checksums.txt
    error: refusing to install an unverified download
    

    How it happened. v0.10.0 consolidated to one archive per platform, so the ui-* names 0.9.x updaters ask for stopped existing and those updaters 404'd (#1538). We fixed that by publishing byte-identical copies under the legacy names — but that step runs after checksums.txt is generated and signed, so the aliases were never listed in it. The publishing script even documented the omission as deliberate, reasoning that "checksums.txt covers the canonical names current installers request". That reasoning is wrong in exactly your case: the aliases exist only for older updaters, and those verify the name they asked for. We fixed the 404 and moved the failure to verification.

    Your installer is behaving correctly. Refusing an archive it cannot verify is the right call — the bug is that we gave it nothing to verify against.

    Workaround right now. The ui- archive and the plain one are byte-identical; only the name differs, and the plain name is in checksums.txt. So download:

    codebase-memory-mcp-darwin-arm64.tar.gz
    

    instead of codebase-memory-mcp-ui-darwin-arm64.tar.gz (substitute your platform). Same bytes, and it will verify.

    The fix is in review as #1651: the alias digests are emitted at generation time, before the attestation step, so the attested checksum file covers both names. It adds no new bytes — an alias is a copy, so its sha256 is by construction the digest already computed for the canonical archive. I validated the generator against your release's real checksum file: it produces exactly those 8 names, no more and no fewer, and fails closed if it ever matches nothing.

    Thank you for the clear report. Being unable to update by any path is about as bad as it gets, and it would have kept shipping unnoticed without you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.securitySecurity vulnerabilities, hardening

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions