Skip to content

fix(security): add SHA-256 integrity verification for Ollama installer#2048

Merged
cv merged 16 commits intomainfrom
fix/ollama-sha256-verification
Apr 21, 2026
Merged

fix(security): add SHA-256 integrity verification for Ollama installer#2048
cv merged 16 commits intomainfrom
fix/ollama-sha256-verification

Conversation

@ericksoa
Copy link
Copy Markdown
Contributor

@ericksoa ericksoa commented Apr 18, 2026

Summary

  • verify_downloaded_script() computed a SHA-256 hash but only printed it — never compared against an expected value. The Ollama installer was downloaded and executed with no integrity verification.
  • Pins OLLAMA_INSTALL_SHA256 and compares before execution, matching the existing nvm verification pattern.
  • Extends scripts/check-installer-hash.sh (from fix(security): add SHA-256 integrity verification for k8s installer download #2046) into a registry-based multi-hash checker that monitors both the k8s NemoClaw installer and the Ollama installer for upstream staleness.
  • Adds the Ollama hash to the weekly CI workflow and make check-installer-hash.

Ordering dependency

This PR must merge after #2046. It extends the check-installer-hash.sh script and CI workflow introduced there. Expect a merge conflict on scripts/check-installer-hash.sh — resolve by taking this PR's version, which is a superset of #2046's single-hash script.

Files changed

File Change
scripts/install.sh verify_downloaded_script() accepts optional expected hash; OLLAMA_INSTALL_SHA256 constant; both Ollama download sites pass hash
install.sh Bootstrap copy gets same hash-checking capability
scripts/check-installer-hash.sh Generalized to check multiple pinned hashes (k8s + Ollama)
.github/workflows/installer-hash-check.yaml Weekly + PR + push CI check for all pinned hashes
Makefile make check-installer-hash target

Test plan

  • Run installer normally — Ollama install succeeds with "integrity verified" message
  • Set OLLAMA_INSTALL_SHA256 to a wrong value — installer aborts with "integrity check failed", Ollama script does NOT execute
  • Remove sha256sum and shasum from PATH — installer aborts with "No SHA-256 tool available" (not silently skipped)
  • Verify existing nvm hash verification still works unchanged
  • Run scripts/check-installer-hash.sh — reports both hashes OK
  • Run scripts/check-installer-hash.sh --update with a stale hash — rewrites the correct file

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Installer downloads now include SHA-256 hash verification to validate integrity and detect corrupted or compromised files.
    • Added automated verification workflow that continuously validates pinned installer hashes against upstream sources on a weekly schedule with manual trigger capability.

…r download

verify_downloaded_script() computed a SHA-256 hash but only printed it —
it never compared against an expected value. The Ollama installer was
downloaded from https://ollama.com/install.sh and executed with no
integrity verification, allowing MITM or CDN compromise to deliver
arbitrary code.

Pin the Ollama installer hash (OLLAMA_INSTALL_SHA256) and compare it
before execution, matching the existing nvm verification pattern.
Extend scripts/check-installer-hash.sh (from #2046) to also monitor
the Ollama hash for upstream staleness via the weekly CI workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8971e076-86fe-45c4-bc9d-e2630071705c

📥 Commits

Reviewing files that changed from the base of the PR and between 24ef020 and ccfcc35.

📒 Files selected for processing (2)
  • .github/workflows/installer-hash-check.yaml
  • scripts/check-installer-hash.sh
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/installer-hash-check.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/check-installer-hash.sh

📝 Walkthrough

Walkthrough

The PR introduces SHA-256 hash verification for installer scripts across three components: a new GitHub Actions workflow that runs hash checks on a schedule, a bash script that validates pinned SHA-256 hashes against upstream sources, and enhanced installation logic that verifies downloaded scripts against expected hash values.

Changes

Cohort / File(s) Summary
GitHub Actions Workflow
.github/workflows/installer-hash-check.yaml
New workflow that triggers on PRs, pushes to main, scheduled weekly, and manual dispatch. Runs hash verification job on ubuntu-latest with 5-minute timeout, conditionally executing only for the NVIDIA/NemoClaw repository.
Hash Verification Enhancement
install.sh, scripts/install.sh
Function verify_downloaded_script() signature expanded to accept optional expected_hash parameter. When provided, computes SHA-256 and enforces integrity checks; fails if hash utility unavailable or hash mismatch occurs. Ollama installer paths now define OLLAMA_INSTALL_SHA256 and pass it to verification. Minor indentation adjustments to control-flow structures without logic changes.
Installer Hash Check Script
scripts/check-installer-hash.sh
New bash script implementing strict validation (set -euo pipefail) that registers labeled hash checks, downloads upstream installer sources, computes SHA-256 digests, and compares against pinned repository values. Supports --update flag to rewrite stale hashes in-place. Exits with error status if mismatches found without update flag.

Sequence Diagram(s)

sequenceDiagram
    participant GHA as GitHub Actions<br/>(Workflow)
    participant CHS as check-installer-hash.sh
    participant Upstream as Upstream<br/>Source
    participant Repo as Repository<br/>Files

    GHA->>CHS: Execute (with or without --update)
    CHS->>Repo: Extract pinned hash
    alt Hash exists
        CHS->>Upstream: Download installer
        Upstream-->>CHS: Installer script
        CHS->>CHS: Compute SHA-256
        alt --update flag
            CHS->>Repo: Update pinned hash
            CHS-->>GHA: Log UPDATED
        else No --update flag
            alt Hashes match
                CHS-->>GHA: Log MATCH
            else Hashes mismatch
                CHS-->>GHA: Log STALE + exit 1
            end
        end
    else Hash missing
        CHS-->>GHA: Log SKIP
    end
Loading
sequenceDiagram
    participant User as User/<br/>Installer
    participant Install as install.sh
    participant Download as Download<br/>Mechanism
    participant Verify as verify_downloaded_script()

    User->>Install: Run installer
    Install->>Download: Fetch installer script
    Download-->>Install: Script downloaded
    Install->>Verify: Call verify_downloaded_script(file, label, expected_hash)
    alt expected_hash provided
        Verify->>Verify: Compute SHA-256
        alt Hash matches
            Verify-->>Install: Verification passed
            Install->>Install: Continue installation
        else Hash mismatch
            Verify->>Download: Remove file
            Verify-->>Install: Exit with error
        end
    else No expected_hash
        Verify->>Verify: Report SHA-256 (legacy behavior)
        Verify-->>Install: Continue
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • brandonpelfrey

Poem

🐰 Hashes pinned with care so bright,
Verification checks done right,
Scripts now blessed with SHA embrace,
No more trusting just the trace! ✨
Upstream sources now aligned,
Perfect matches we will find!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the primary change: adding SHA-256 integrity verification for the Ollama installer, which is the main security fix across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ollama-sha256-verification

Comment @coderabbitai help to get the list of available commands and usage tips.

The k8s NEMOCLAW_INSTALLER_SHA256 is added by #2046 which hasn't
merged yet. Treat a missing variable as a skip rather than a failure
so CI passes before and after #2046 lands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/check-installer-hash.sh`:
- Around line 65-68: The new registry registration calling register "NemoClaw
k8s installer" references the variable NEMOCLAW_INSTALLER_SHA256 required by
k8s/nemoclaw-k8s.yaml and currently causes CI to fail; either add the missing
SHA256 pin (set NEMOCLAW_INSTALLER_SHA256 to the correct hash and commit it) or
guard/defer the register call until the dependent branch is merged by wrapping
or conditionally executing the register invocation (the register invocation with
arguments "${REPO_ROOT}/k8s/nemoclaw-k8s.yaml", "NEMOCLAW_INSTALLER_SHA256",
"https://www.nvidia.com/nemoclaw.sh") behind a feature flag or branch-check so
CI does not require the variable yet.
- Around line 31-42: fetch_hash() uses sha256sum which is unavailable on macOS;
update fetch_hash to detect if sha256sum exists and, if not, fall back to using
shasum -a 256 (preserving the same output parsing to return only the hex
digest), e.g., try sha256sum first and otherwise call shasum -a 256 on the
temporary file created by mktemp and return the first field; keep the existing
trap and curl behavior and reference the fetch_hash function and its temporary
file handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: edf4f852-e0ec-4e27-99f0-e88d47d207af

📥 Commits

Reviewing files that changed from the base of the PR and between 1a8ea87 and 52a0efe.

📒 Files selected for processing (5)
  • .github/workflows/installer-hash-check.yaml
  • Makefile
  • install.sh
  • scripts/check-installer-hash.sh
  • scripts/install.sh

Comment thread scripts/check-installer-hash.sh
Comment thread scripts/check-installer-hash.sh Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
scripts/check-installer-hash.sh (1)

31-42: ⚠️ Potential issue | 🟠 Major

Add shasum -a 256 fallback in fetch_hash().

fetch_hash() currently assumes sha256sum; on macOS this often fails, causing the checker to abort before comparison/update. Mirror the fallback pattern already used in scripts/install.sh.

♻️ Proposed fix
 fetch_hash() {
   local url="$1" tmpfile
   tmpfile=$(mktemp)
   trap 'rm -f "$tmpfile"' RETURN
@@
-  sha256sum "$tmpfile" | cut -d' ' -f1
+  if command -v sha256sum >/dev/null 2>&1; then
+    sha256sum "$tmpfile" | awk '{print $1}'
+  elif command -v shasum >/dev/null 2>&1; then
+    shasum -a 256 "$tmpfile" | awk '{print $1}'
+  else
+    echo "ERROR: No SHA-256 tool available (sha256sum/shasum)." >&2
+    return 1
+  fi
 }
#!/bin/bash
set -euo pipefail
# Verify hashing-tool handling consistency between scripts.
rg -n --type=sh 'fetch_hash|sha256sum|shasum' scripts/check-installer-hash.sh scripts/install.sh install.sh
sed -n '31,45p' scripts/check-installer-hash.sh
sed -n '136,162p' scripts/install.sh
# Expected: check-installer-hash.sh should contain both sha256sum and shasum branches.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/check-installer-hash.sh` around lines 31 - 42, The fetch_hash()
function currently assumes sha256sum and fails on macOS; modify fetch_hash to
try sha256sum first and if that command is not available or fails, fall back to
shasum -a 256 (same pattern used in scripts/install.sh), ensuring you handle
errors/exit codes and still return only the hex digest; reference the function
name fetch_hash and the utilities sha256sum and shasum -a 256 when making the
change.
🧹 Nitpick comments (1)
scripts/check-installer-hash.sh (1)

49-53: Scope in-place updates to the target variable assignment.

Current replacement is value-only; if the same digest appears elsewhere in the file, the wrong location can be rewritten. Update by variable name.

♻️ Proposed refactor
-update_pinned() {
-  local file="$1" old_hash="$2" new_hash="$3"
-  sed -i.bak "s/${old_hash}/${new_hash}/" "$file"
+update_pinned() {
+  local file="$1" var_name="$2" new_hash="$3"
+  sed -i.bak -E "s#^([[:space:]]*${var_name}=\")[a-f0-9]{64}(\".*)#\1${new_hash}\2#" "$file"
   rm -f "${file}.bak"
 }
@@
-    update_pinned "$file" "$pinned" "$upstream"
+    update_pinned "$file" "$var" "$upstream"

Also applies to: 102-104

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/check-installer-hash.sh` around lines 49 - 53, The replacement
currently swaps any matching digest anywhere in the file; change update_pinned
to target the specific variable assignment by adding a variable-name parameter
and restricting the sed regex to only replace the assignment (e.g., match
^\s*VAR_NAME\s*=\s*"OLD_HASH" and replace with VAR_NAME="NEW_HASH"); update the
function signature (update_pinned) and all call sites that invoke it (the other
occurrences noted) to pass the variable name so only the intended assignment is
updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@scripts/check-installer-hash.sh`:
- Around line 31-42: The fetch_hash() function currently assumes sha256sum and
fails on macOS; modify fetch_hash to try sha256sum first and if that command is
not available or fails, fall back to shasum -a 256 (same pattern used in
scripts/install.sh), ensuring you handle errors/exit codes and still return only
the hex digest; reference the function name fetch_hash and the utilities
sha256sum and shasum -a 256 when making the change.

---

Nitpick comments:
In `@scripts/check-installer-hash.sh`:
- Around line 49-53: The replacement currently swaps any matching digest
anywhere in the file; change update_pinned to target the specific variable
assignment by adding a variable-name parameter and restricting the sed regex to
only replace the assignment (e.g., match ^\s*VAR_NAME\s*=\s*"OLD_HASH" and
replace with VAR_NAME="NEW_HASH"); update the function signature (update_pinned)
and all call sites that invoke it (the other occurrences noted) to pass the
variable name so only the intended assignment is updated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 254040db-99e6-423a-960a-5a9058008066

📥 Commits

Reviewing files that changed from the base of the PR and between 52a0efe and a5af19a.

📒 Files selected for processing (1)
  • scripts/check-installer-hash.sh

ericksoa and others added 9 commits April 17, 2026 17:53
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS does not ship sha256sum — only shasum is available by default.
fetch_hash() now tries sha256sum first, falls back to shasum -a 256,
matching the pattern used in scripts/install.sh and install.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-existing case statement indentation didn't match shfmt style.
The CI "Files were modified by hooks" check runs shfmt on all touched
files, so these must be formatted for the check to pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous formatting commit missed case statement body indentation
(-ci flag) and binary operator line-continuation positioning (-bn flag)
across all three shell files. This caused the "Files were modified by
following hooks" CI check to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wscurran wscurran added security Something isn't secure CI/CD Use this label to identify issues with NemoClaw CI/CD pipeline or GitHub Actions. Local Models Running NemoClaw with local models priority: high Important issue that should be resolved in the next release labels Apr 20, 2026
@ericksoa ericksoa self-assigned this Apr 20, 2026
The k8s/ directory and its associated hash-check script/workflow were
removed on main (#2107). This PR's versions of check-installer-hash.sh
and installer-hash-check.yaml are retained because they now serve the
Ollama installer hash verification. Removed the NemoClaw k8s installer
registry entry that referenced the deleted k8s/nemoclaw-k8s.yaml.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@cv cv merged commit 48c0d54 into main Apr 21, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD Use this label to identify issues with NemoClaw CI/CD pipeline or GitHub Actions. Local Models Running NemoClaw with local models priority: high Important issue that should be resolved in the next release security Something isn't secure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants