Skip to main content
hooksSource-backed

Package Download Checksum Guard - Claude Code Hook

PreToolUse hook that reviews proposed Bash commands for package, installer, and archive downloads, then blocks curl or wget download commands that do not include an adjacent checksum or signature verification step.

by MkDev11·added 2026-06-05·
Trigger:PreToolUse
Review first review before installing

Open the source and read safety notes before installing.

Citation facts

Source-backed facts for citing this resource, derived directly from the registry — also available as plain text for AI assistants.

Source URLs
https://www.gnu.org/software/coreutils/manual/html_node/sha2-utilities.html, https://cgit.git.savannah.gnu.org/cgit/coreutils.git
Safety notes
Runs before Bash tool calls and reads only the proposed command string from Claude Code hook input., Blocks matching curl/wget package or archive downloads with exit code 2 unless a later executable verification step is chained with && and references the downloaded artifact name. Downloader-to-shell installer pipelines are blocked unless allowlisted., Does not download, execute, hash, delete, install, or modify files; it is a pre-execution guard around the proposed command text., Regex matching can miss unusual shell constructs or flag legitimate internal download workflows; comments and digest-print-only commands are not accepted as verification, and `PACKAGE_DOWNLOAD_GUARD_ALLOWLIST` is available for reviewed patterns., Set `PACKAGE_DOWNLOAD_GUARD_MODE=advisory` to warn without blocking while a team tunes the policy.
Privacy notes
Reads the proposed Bash command text locally; it makes no network calls and writes no logs., Default output does not echo the full command or URL, reducing the chance of exposing private package hosts, tokenized URLs, or internal release names., Terminal scrollback, Claude Code transcripts, CI logs, or screenshots can still retain the warning text and environment variable names.
Author
MkDev11
Submitted by
MkDev11
Claim status
unclaimed
Last verified
2026-06-05

Decision playbook

Review trust signals before you adopt

Signals are present but mixed. Use the checklist below to confirm the source and operational safety for your environment.

Compare context
Selected

0

Current score

63

Baseline

Delta

No baseline selected

No major trust-signal divergence detected in the current selection.

Source and provenance checks

Needs review

Confirm ownership and provenance before trusting install instructions.

  • Source link availableRequired

    Open the canonical repository and verify ownership.

    Done
  • Source provenance statusRequired

    Marked as source-backed.

    Done
  • Metadata reviewed

    No reviewed flag detected in metadata.

    Pending

Safety and privacy checks

Complete

Validate risk disclosures before installation or API wiring.

  • Safety notes presentRequired

    Review the listed safety guidance before running commands.

    Done
  • Privacy notes presentRequired

    Review data handling notes before connecting accounts or secrets.

    Done
  • Trust level risk gateRequired

    Trust level does not block evaluation.

    Done

Package and install checks

Needs review

Check package metadata and artifact integrity signals.

  • Install payload available

    Install or copy payload is available for review.

    Done
  • Package verification flag

    No package verification flag provided.

    Pending
  • Checksum metadata

    No checksum provided for downloaded artifact.

    Pending

Compare-driven decision checks

Needs review

Use compare context to validate trade-offs before adoption.

  • Compare tray has multiple entries

    Add at least one more entry to compare trust differences.

    Pending
  • Baseline comparison available

    No baseline peer selected yet.

    Pending
  • Diverging trust signals identified

    No major trust-signal divergence found.

    Pending

Setup at a glance

CLI install

Copy-ready — paste the snippet to get started.

5 minutes

Adoption plan

Balanced adoption plan

Current risk score 24/100. Use staged verification before broader rollout.

Risk 24

Pre-adoption checks

Validate source and review signals before any execution.

  • Confirm source provenanceRequired

    Source URL/provenance metadata is present.

    Done
  • Confirm metadata review state

    No review metadata found; increase manual validation.

    Pending
  • Verify install payload

    Install/config payload exists and can be inspected.

    Done

Security checks

Confirm safety, privacy, and package integrity signals.

  • Review safety notesRequired

    Safety notes are present.

    Done
  • Review privacy notesRequired

    Privacy notes are present.

    Done
  • Verify package integrity metadata

    No package verification/checksum metadata.

    Pending

Rollout

Adopt in controlled steps based on the selected plan.

  • Run in isolated sandbox firstRequired

    Use a constrained sandbox and observe behavior across multiple tasks.

    Pending
  • Roll out graduallyRequired

    Roll out to a small cohort before wider usage.

    Pending
  • Set monitoring and fallback

    Define rollback path and monitor errors after adoption.

    Pending

Evidence readiness

Evidence readiness matrix · balanced

Missing required evidence: Metadata review. Risk score 31.

Risk 31

Source provenance

Present

Source repository/provenance is listed.

Required in this preset

Metadata review

Missing

Review metadata is missing.

Required in this preset

Safety notes

Present

Safety notes are present.

Required in this preset

Privacy notes

Present

Privacy notes are present.

Optional in this preset

Package integrity

Missing

Package integrity metadata is missing.

Optional in this preset

Install payload

Present

Install payload is available.

Required in this preset

Required gaps: Metadata review

Decision timeline

Decision timeline · balanced

Blocking gaps: Check metadata review status. Risk 28.

Risk 28

triage

Confirm source provenanceRequired

Source/provenance metadata is available.

Done

triage

Check metadata review statusRequired

Review metadata is missing.

Pending

verify

Review safety notesRequired

Safety notes are available.

Done

verify

Review privacy notes

Privacy notes are available.

Done

verify

Validate package integrity metadata

Package integrity metadata is missing.

Pending

rollout

Verify install payload and commandsRequired

Install payload is available.

Done

Blockers: Check metadata review status

Prerequisite readiness

Prerequisite readiness

3 prerequisites to line up before setup. Includes a review or approval gate.

0/3 ready
Install & runtime1Review & approval25 minutes

Safety & privacy surface

Safety & privacy surface

5 safety and 3 privacy notes across 3 risk areas. Review closely: credentials & tokens, network access.

3 areas
  • SafetyExecution & processesRuns before Bash tool calls and reads only the proposed command string from Claude Code hook input.
  • SafetyNetwork accessBlocks matching curl/wget package or archive downloads with exit code 2 unless a later executable verification step is chained with && and references the downloaded artifact name. Downloader-to-shell installer pipelines are blocked unless allowlisted.
  • SafetyNetwork accessDoes not download, execute, hash, delete, install, or modify files; it is a pre-execution guard around the proposed command text.
  • SafetyNetwork accessRegex matching can miss unusual shell constructs or flag legitimate internal download workflows; comments and digest-print-only commands are not accepted as verification, and `PACKAGE_DOWNLOAD_GUARD_ALLOWLIST` is available for reviewed patterns.
  • SafetyNetwork accessSet `PACKAGE_DOWNLOAD_GUARD_MODE=advisory` to warn without blocking while a team tunes the policy.
  • PrivacyNetwork accessReads the proposed Bash command text locally; it makes no network calls and writes no logs.
  • PrivacyCredentials & tokensDefault output does not echo the full command or URL, reducing the chance of exposing private package hosts, tokenized URLs, or internal release names.
  • PrivacyExecution & processesTerminal scrollback, Claude Code transcripts, CI logs, or screenshots can still retain the warning text and environment variable names.

Safety notes

  • Runs before Bash tool calls and reads only the proposed command string from Claude Code hook input.
  • Blocks matching curl/wget package or archive downloads with exit code 2 unless a later executable verification step is chained with && and references the downloaded artifact name. Downloader-to-shell installer pipelines are blocked unless allowlisted.
  • Does not download, execute, hash, delete, install, or modify files; it is a pre-execution guard around the proposed command text.
  • Regex matching can miss unusual shell constructs or flag legitimate internal download workflows; comments and digest-print-only commands are not accepted as verification, and `PACKAGE_DOWNLOAD_GUARD_ALLOWLIST` is available for reviewed patterns.
  • Set `PACKAGE_DOWNLOAD_GUARD_MODE=advisory` to warn without blocking while a team tunes the policy.

Privacy notes

  • Reads the proposed Bash command text locally; it makes no network calls and writes no logs.
  • Default output does not echo the full command or URL, reducing the chance of exposing private package hosts, tokenized URLs, or internal release names.
  • Terminal scrollback, Claude Code transcripts, CI logs, or screenshots can still retain the warning text and environment variable names.

Prerequisites

  • Claude Code CLI with hooks enabled.
  • bash, jq, grep, and a reviewed `.claude/settings.json` or user-level hook configuration.
  • A team policy for when package, installer, or archive downloads require checksums, signatures, pinned releases, or manual source review.

Schema details

Install type
cli
Reading time
6 min
Difficulty score
42
Troubleshooting
Yes
Breaking changes
No
Source repository stats
Scope
Source repo
Runtime and command metadata
Trigger
PreToolUse
Script language
bash
Script body
#!/usr/bin/env bash
set -u

# Claude Code PreToolUse hook for Bash. It looks for curl/wget commands that
# download packages, installers, or archives, and blocks the command unless
# the same proposed command also includes a checksum check or signature
# verification command. It reads the proposed command only; it never downloads
# anything.

if ! command -v jq >/dev/null 2>&1; then
  exit 0
fi

input=$(cat)
tool_name=$(printf '%s' "$input" | jq -r '.tool_name // .toolName // empty')
command_text=$(printf '%s' "$input" | jq -r '.tool_input.command // .toolInput.command // empty')

case "$tool_name" in
  Bash|bash) ;;
  *) exit 0 ;;
esac

if [ -z "$command_text" ]; then
  exit 0
fi

if [ -n "${PACKAGE_DOWNLOAD_GUARD_ALLOWLIST:-}" ]; then
  if printf '%s\n' "$command_text" | grep -Eq -- "$PACKAGE_DOWNLOAD_GUARD_ALLOWLIST"; then
    exit 0
  fi
fi

strip_shell_comments() {
  local text=$1
  local out=""
  local char=""
  local in_single=0
  local in_double=0
  local escaped=0
  local at_word_start=1
  local i=0

  while [ "$i" -lt "${#text}" ]; do
    char=${text:i:1}

    if [ "$escaped" -eq 1 ]; then
      out+="$char"
      at_word_start=0
      escaped=0
      i=$((i + 1))
      continue
    fi

    if [ "$char" = "\\" ] && [ "$in_single" -eq 0 ]; then
      out+="$char"
      at_word_start=0
      escaped=1
      i=$((i + 1))
      continue
    fi

    if [ "$char" = "'" ] && [ "$in_double" -eq 0 ]; then
      if [ "$in_single" -eq 1 ]; then
        in_single=0
      else
        in_single=1
      fi
      out+="$char"
      at_word_start=0
      i=$((i + 1))
      continue
    fi

    if [ "$char" = '"' ] && [ "$in_single" -eq 0 ]; then
      if [ "$in_double" -eq 1 ]; then
        in_double=0
      else
        in_double=1
      fi
      out+="$char"
      at_word_start=0
      i=$((i + 1))
      continue
    fi

    if [ "$char" = "#" ] && [ "$in_single" -eq 0 ] && [ "$in_double" -eq 0 ] && [ "$at_word_start" -eq 1 ]; then
      while [ "$i" -lt "${#text}" ] && [ "$char" != $'\n' ]; do
        i=$((i + 1))
        char=${text:i:1}
      done
      continue
    fi

    out+="$char"

    if [ "$in_single" -eq 0 ] && [ "$in_double" -eq 0 ]; then
      case "$char" in
        [[:space:]]|";"|"&"|"|"|"("|")") at_word_start=1 ;;
        *) at_word_start=0 ;;
      esac
    fi

    i=$((i + 1))
  done

  printf '%s\n' "$out"
}

# Remove only real shell comments before matching policy tokens. This keeps a
# comment such as `# sha256sum -c` from being treated as verification without
# truncating quoted or escaped # characters that are part of the command.
command_without_comments=$(strip_shell_comments "$command_text")

downloader_a='curl'
downloader_b='wget'
shell_a='sh'
shell_b='bash'
shell_c='zsh'
pipe_char=$(printf '\174')
downloader_names="(${downloader_a}${pipe_char}${downloader_b})"
shell_names="(${shell_a}${pipe_char}${shell_b}${pipe_char}${shell_c})"
downloader_re="(^|[;&${pipe_char}[:space:]])${downloader_names}([[:space:]]|$)"
archive_re='https?://[^[:space:]]*(\.zip|\.tar\.gz|\.tgz|\.tar\.xz|\.tar\.bz2|\.mcpb|\.deb|\.rpm|\.pkg|\.dmg|\.exe|\.msi|\.AppImage|install\.sh|setup\.sh|bootstrap\.sh)([?#][^[:space:]]*)?'
pipe_installer_re="${downloader_names}[^${pipe_char}]*https?://[^${pipe_char}[:space:]]*(install|setup|bootstrap|\.sh)[^${pipe_char}]*\\${pipe_char}[[:space:]]*(sudo[[:space:]]+)?${shell_names}"
command_chain_re='(^|&&)'
checksum_re='(sha256sum[[:space:]][^;&|#]*(--check|-c)|shasum[[:space:]][^;&|#]*(-a[[:space:]]+256|--algorithm([=[:space:]]+)256)[^;&|#]*(--check|-c))'
signature_re='(cosign[[:space:]]+verify-blob|gpg[[:space:]][^;&|#]*--verify|minisign[[:space:]][^;&|#]*-V)'
verification_command_re="${command_chain_re}[[:space:]]*(${checksum_re}|${signature_re})[^;&|#]*"

escape_ere() {
  printf '%s' "$1" | sed -E 's/[][(){}.^$*+?|\]/\\&/g'
}

has_verification_for_download() {
  urls=$(printf '%s\n' "$command_without_comments" | grep -Eo -- "$archive_re" || true)
  [ -n "$urls" ] || return 1

  while IFS= read -r url; do
    [ -n "$url" ] || continue
    clean_url=$(printf '%s\n' "$url" | sed -E 's/[?#].*$//')
    artifact=${clean_url##*/}
    [ -n "$artifact" ] || continue
    artifact_re=$(escape_ere "$artifact")
    after_url=$(printf '%s\n' "$command_without_comments" | awk -v u="$url" '
      found { print }
      !found {
        index_at = index($0, u)
        if (index_at > 0) {
          print substr($0, index_at + length(u))
          found = 1
        }
      }
    ')
    artifact_verification_re="${verification_command_re}${artifact_re}[^;&|#]*($|&&)"
    if printf '%s\n' "$after_url" | grep -Eq -- "$artifact_verification_re"; then
      return 0
    fi
  done <<EOF
$urls
EOF

  return 1
}

if ! printf '%s\n' "$command_without_comments" | grep -Eq -- "$downloader_re"; then
  exit 0
fi

risky_download=0
if printf '%s\n' "$command_without_comments" | grep -Eq -- "$archive_re"; then
  risky_download=1
fi

pipe_installer=0
if printf '%s\n' "$command_without_comments" | grep -Eq -- "$pipe_installer_re"; then
  risky_download=1
  pipe_installer=1
fi

if [ "$risky_download" -ne 1 ]; then
  exit 0
fi

if [ "$pipe_installer" -eq 1 ]; then
  echo "Package download checksum guard: downloader-to-shell installer pipelines must be downloaded, verified, and run as separate steps." >&2
  echo "Download the script to a file, verify it with 'sha256sum -c', 'shasum -a 256 -c', 'cosign verify-blob', 'gpg --verify', or 'minisign -V', then run the reviewed file." >&2
  echo "Set PACKAGE_DOWNLOAD_GUARD_MODE=advisory to warn without blocking, or PACKAGE_DOWNLOAD_GUARD_ALLOWLIST to allow a reviewed command pattern." >&2
  if [ "${PACKAGE_DOWNLOAD_GUARD_MODE:-block}" = "advisory" ]; then
    exit 0
  fi
  exit 2
fi

if has_verification_for_download; then
  exit 0
fi

echo "Package download checksum guard: archive or installer download has no checksum/signature verification step." >&2
echo "Add verification such as 'sha256sum -c', 'shasum -a 256 -c', 'cosign verify-blob', 'gpg --verify', or 'minisign -V' before using the downloaded artifact." >&2
echo "Set PACKAGE_DOWNLOAD_GUARD_MODE=advisory to warn without blocking, or PACKAGE_DOWNLOAD_GUARD_ALLOWLIST to allow a reviewed command pattern." >&2

if [ "${PACKAGE_DOWNLOAD_GUARD_MODE:-block}" = "advisory" ]; then
  exit 0
fi

exit 2
Collection metadata
Estimated setup
5 minutes
Difficulty
intermediate
Full copyable content
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/package-download-checksum-guard.sh"
          }
        ]
      }
    ]
  }
}

About this resource

Overview

This hook catches a narrow supply-chain habit that agents can accidentally normalize: downloading package archives or installer scripts and immediately using them without verifying the artifact.

It runs before Claude Code executes a Bash command. If the proposed command uses curl or wget to fetch a package-like artifact, installer script, or archive, the hook expects the same command to include an adjacent verification step such as sha256sum -c, shasum -a 256 -c, cosign verify-blob, gpg --verify, or minisign -V.

Features

  • Blocks unverified curl and wget downloads of .zip, .tar.gz, .tgz, .tar.xz, .tar.bz2, .mcpb, .deb, .rpm, .pkg, .dmg, .exe, .msi, .AppImage, and common installer script names.
  • Detects downloader-to-shell installer pipelines when the URL looks like an installer script.
  • Accepts common checksum and signature verification commands without requiring one specific tool.
  • Makes no network calls and never executes the proposed command itself.
  • Supports PACKAGE_DOWNLOAD_GUARD_MODE=advisory for warning-only rollout and PACKAGE_DOWNLOAD_GUARD_ALLOWLIST for reviewed internal patterns.

How It Works

Claude Code passes the pending Bash tool call to the hook on stdin. The script extracts .tool_input.command, checks for curl or wget, and then looks for package/archive URL shapes or installer pipes. If a risky download is found, it requires a later executable checksum-check or signature-verification command in the same proposed command that is chained with && and references the downloaded artifact name. Without that command, it exits 2, which tells Claude Code to block the Bash call.

The guard is intentionally text-based. It strips shell comments before looking for verification commands and blocks downloader-to-shell installer pipelines so comment-only, unrelated, out-of-order, neutralized, or digest-print-only snippets do not count as verification. It does not claim to verify the checksum itself; its job is to stop the session long enough for a human or agent to add the reviewed verification step before using the downloaded artifact.

Use Cases

  • Stop an agent from fetching a release archive and unpacking it without first checking a digest or signature.
  • Nudge package bootstrap snippets toward sha256sum -c, shasum -a 256 -c, or signed-release verification before installation.
  • Keep unverified external source archives from becoming normal in project setup docs, skills, CI helpers, and local scripts.
  • Roll out a lightweight local guard before stricter CI supply-chain policy.

Installation

  1. Create the hooks directory: mkdir -p .claude/hooks
  2. Install the hook script with the install command above, or paste the script body into .claude/hooks/package-download-checksum-guard.sh.
  3. Make the hook executable: chmod +x .claude/hooks/package-download-checksum-guard.sh
  4. Add the configuration below to .claude/settings.json for a project hook or ~/.claude/settings.json for a user hook.

Hook Configuration

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/package-download-checksum-guard.sh"
          }
        ]
      }
    ]
  }
}

Configuration

  • PACKAGE_DOWNLOAD_GUARD_MODE=advisory - warn without blocking.
  • PACKAGE_DOWNLOAD_GUARD_ALLOWLIST - extended regular expression for reviewed commands that should bypass the guard.

Limitations

  • This hook checks command text, not the downloaded artifact. It cannot prove a checksum is correct or that a signature chains to a trusted identity.
  • Shell syntax is large; unusual quoting, variables, functions, aliases, or multi-line wrappers can evade simple text matching.
  • Some ecosystems rely on package manager lockfiles and integrity metadata instead of manual archive checks. Tune the allowlist for those workflows.
  • Verification commands can be present but wrong. Review the expected digest, signing identity, release source, and pinned version before trusting the artifact. Digest-print-only commands such as openssl dgst -sha256 file, unrelated checks, neutralized checks such as || true, or checks that do not reference the downloaded artifact name after the verification command do not satisfy the guard because they do not compare that artifact against a trusted value.

Duplicate And History Check

Checked current hooks, commands, skills, rules, open PRs, closed PR history, issue #772 timeline, and live-site search for package download checksum guards, download verification hooks, sha256 hooks, archive download blockers, Sigstore verification hooks, SLSA provenance hooks, and lockfile integrity entries.

Adjacent content includes lockfile-provenance-checker, package-vulnerability-scanner, dependency review rules, SLSA provenance review skill, and safe shell command rules. This entry is distinct because it is a runtime PreToolUse Bash guard for proposed package/archive downloads before they execute; it does not inspect edited lockfiles, scan known CVEs, review general shell safety, or perform provenance review after the artifact exists.

Source And References

Source citations

Add this badge to your README

Show that Package Download Checksum Guard - Claude Code Hook is listed on HeyClaude. Paste this Markdown into your README — it renders the badge and links back to this page.

Listed on HeyClaude
[![Listed on HeyClaude](https://heyclau.de/badge/hooks/package-download-checksum-guard-hook.svg)](https://heyclau.de/entry/hooks/package-download-checksum-guard-hook)

How it compares

Package Download Checksum Guard - Claude Code Hook side by side with 3 alternatives on trust, install, platform support, and disclosed safety notes — all from reviewed registry metadata.

2 trust signals differ across this comparison (Source provenance, Submitter).

Field

PreToolUse hook that reviews proposed Bash commands for package, installer, and archive downloads, then blocks curl or wget download commands that do not include an adjacent checksum or signature verification step.

Open dossier

PostToolUse hook that inspects an edited npm package-lock.json for supply-chain provenance risk rather than known CVEs — dependencies resolved from outside the public npm registry (git, alternate-registry, or insecure transports) and registry tarballs missing an integrity hash.

Open dossier

PostToolUse hook that watches dependency manifest and lockfile edits, then prompts or runs an OSS Review Toolkit dependency license analysis.

Open dossier

PostToolUse hook that flags risky package-lock.json, yarn.lock, and pnpm-lock.yaml edits: missing lockfile updates, unexpected registry hosts, and dependency count spikes before merge.

Open dossier
Next steps
Trust
Review statusNot reviewedNot reviewedNot reviewedNot reviewed
Package trustPackage not verifiedPackage not verifiedPackage not verifiedPackage not verified
Source provenanceDiffersSource-backedSource-backedSource-backedSubmission linkedSource submission
SubmitterDiffersMkDev11techforgeworksoktofeesh1kiannidev
Install riskReview firstReview firstReview firstReview first
Notes Safety ✓ Privacy ✓ Safety ✓ Privacy ✓ Safety ✓ Privacy ✓ Safety ✓ Privacy ✓
Brand
Categoryhookshookshookshooks
SourceSource-backedSource-backedSource-backedSource-backed
AuthorMkDev11techforgeworksOSS Review Toolkitkiannidev
Added2026-06-052026-06-042026-06-042026-06-17
Platforms
Harness
Source repo
Safety notesRuns before Bash tool calls and reads only the proposed command string from Claude Code hook input. Blocks matching curl/wget package or archive downloads with exit code 2 unless a later executable verification step is chained with && and references the downloaded artifact name. Downloader-to-shell installer pipelines are blocked unless allowlisted. Does not download, execute, hash, delete, install, or modify files; it is a pre-execution guard around the proposed command text. Regex matching can miss unusual shell constructs or flag legitimate internal download workflows; comments and digest-print-only commands are not accepted as verification, and `PACKAGE_DOWNLOAD_GUARD_ALLOWLIST` is available for reviewed patterns. Set `PACKAGE_DOWNLOAD_GUARD_MODE=advisory` to warn without blocking while a team tunes the policy.Runs after every Write, Edit, and MultiEdit and inspects only npm package-lock.json or npm-shrinkwrap.json content; for yarn.lock and pnpm-lock.yaml it prints a one-line reminder to run lockfile-lint. Read-only and advisory - it parses the lockfile JSON, never installs packages, runs npm, or makes a network call, and always exits 0. Uses the resolved-URL and integrity fields to flag provenance risk (sources outside the public registry, missing integrity); it does not assess known vulnerabilities, so pair it with an audit tool.Runs after Write, Edit, and MultiEdit and inspects only the edited file path to decide whether a dependency manifest or lockfile changed. Install this project-relative command only in the trusted project's `.claude/settings.json`; user/global hooks must point to a trusted absolute path outside project directories. Default mode is advisory, prints the ORT command to run, and always exits 0 without reading dependency contents, creating reports, or contacting registries. When `ORT_LICENSE_HOOK_RUN=1` is set, it runs `ort analyze` in the project root and writes reports under `.claude/ort-license-checks/` by default. An opted-in ORT run may invoke package managers, inspect dependency graphs, read project configuration, fetch package metadata, and take several minutes on large repositories. Strict mode exits 2 only when `ORT_LICENSE_HOOK_STRICT=1` is also set and the opted-in ORT analysis fails.Read-only advisory hook; it does not block writes unless you wrap it with strict exit handling. Does not substitute for npm audit, OSV scans, or CI dependency review.
Privacy notesReads the proposed Bash command text locally; it makes no network calls and writes no logs. Default output does not echo the full command or URL, reducing the chance of exposing private package hosts, tokenized URLs, or internal release names. Terminal scrollback, Claude Code transcripts, CI logs, or screenshots can still retain the warning text and environment variable names.Reads only the local lockfile from disk; it makes no network or registry calls. Prints dependency paths and their resolved URLs to local hook stderr; it writes no logs. Resolved URLs shown in output may include internal registry or git host names if your project depends on them.Default advisory mode prints only the changed dependency file path and the suggested local ORT command. Opted-in ORT runs can record dependency names, versions, package metadata, source URLs, license findings, project paths, package-manager output, and configuration details in local report files. Package managers or ORT integrations may contact configured package registries, VCS hosts, or metadata services during analysis. Review generated reports before sharing them because internal package names, private registry hostnames, source URLs, and license-policy exceptions can be sensitive.Lockfile paths and registry hostnames are printed locally to stderr for the active session.
Prerequisites
  • Claude Code CLI with hooks enabled.
  • bash, jq, grep, and a reviewed `.claude/settings.json` or user-level hook configuration.
  • A team policy for when package, installer, or archive downloads require checksums, signatures, pinned releases, or manual source review.
  • Claude Code CLI with hooks enabled.
  • bash and jq on PATH; the hook fails open and stays silent when jq is missing.
  • Claude Code CLI with hooks enabled.
  • bash available on PATH; jq is recommended for parsing hook input.
  • OSS Review Toolkit installed and available as `ort` only when `ORT_LICENSE_HOOK_RUN=1` is used.
  • Java, package managers, and any project-specific ORT requirements prepared before running a real ORT analysis.
  • jq available when reviewing npm package-lock.json resolved URLs.
  • Team policy for allowed npm registries and lockfile update requirements.
Install
mkdir -p .claude/hooks
cat > .claude/hooks/package-download-checksum-guard.sh <<'HOOK'
#!/usr/bin/env bash
set -u

# Claude Code PreToolUse hook for Bash. It looks for curl/wget commands that
# download packages, installers, or archives, and blocks the command unless
# the same proposed command also includes a checksum check or signature
# verification command. It reads the proposed command only; it never downloads
# anything.

if ! command -v jq >/dev/null 2>&1; then
  exit 0
fi

input=$(cat)
tool_name=$(printf '%s' "$input" | jq -r '.tool_name // .toolName // empty')
command_text=$(printf '%s' "$input" | jq -r '.tool_input.command // .toolInput.command // empty')

case "$tool_name" in
  Bash|bash) ;;
  *) exit 0 ;;
esac

if [ -z "$command_text" ]; then
  exit 0
fi

if [ -n "${PACKAGE_DOWNLOAD_GUARD_ALLOWLIST:-}" ]; then
  if printf '%s\n' "$command_text" | grep -Eq -- "$PACKAGE_DOWNLOAD_GUARD_ALLOWLIST"; then
    exit 0
  fi
fi

strip_shell_comments() {
  local text=$1
  local out=""
  local char=""
  local in_single=0
  local in_double=0
  local escaped=0
  local at_word_start=1
  local i=0

  while [ "$i" -lt "${#text}" ]; do
    char=${text:i:1}

    if [ "$escaped" -eq 1 ]; then
      out+="$char"
      at_word_start=0
      escaped=0
      i=$((i + 1))
      continue
    fi

    if [ "$char" = "\\" ] && [ "$in_single" -eq 0 ]; then
      out+="$char"
      at_word_start=0
      escaped=1
      i=$((i + 1))
      continue
    fi

    if [ "$char" = "'" ] && [ "$in_double" -eq 0 ]; then
      if [ "$in_single" -eq 1 ]; then
        in_single=0
      else
        in_single=1
      fi
      out+="$char"
      at_word_start=0
      i=$((i + 1))
      continue
    fi

    if [ "$char" = '"' ] && [ "$in_single" -eq 0 ]; then
      if [ "$in_double" -eq 1 ]; then
        in_double=0
      else
        in_double=1
      fi
      out+="$char"
      at_word_start=0
      i=$((i + 1))
      continue
    fi

    if [ "$char" = "#" ] && [ "$in_single" -eq 0 ] && [ "$in_double" -eq 0 ] && [ "$at_word_start" -eq 1 ]; then
      while [ "$i" -lt "${#text}" ] && [ "$char" != $'\n' ]; do
        i=$((i + 1))
        char=${text:i:1}
      done
      continue
    fi

    out+="$char"

    if [ "$in_single" -eq 0 ] && [ "$in_double" -eq 0 ]; then
      case "$char" in
        [[:space:]]|";"|"&"|"|"|"("|")") at_word_start=1 ;;
        *) at_word_start=0 ;;
      esac
    fi

    i=$((i + 1))
  done

  printf '%s\n' "$out"
}

# Remove only real shell comments before matching policy tokens. This keeps a
# comment such as `# sha256sum -c` from being treated as verification without
# truncating quoted or escaped # characters that are part of the command.
command_without_comments=$(strip_shell_comments "$command_text")

downloader_a='curl'
downloader_b='wget'
shell_a='sh'
shell_b='bash'
shell_c='zsh'
pipe_char=$(printf '\174')
downloader_names="(${downloader_a}${pipe_char}${downloader_b})"
shell_names="(${shell_a}${pipe_char}${shell_b}${pipe_char}${shell_c})"
downloader_re="(^|[;&${pipe_char}[:space:]])${downloader_names}([[:space:]]|$)"
archive_re='https?://[^[:space:]]*(\.zip|\.tar\.gz|\.tgz|\.tar\.xz|\.tar\.bz2|\.mcpb|\.deb|\.rpm|\.pkg|\.dmg|\.exe|\.msi|\.AppImage|install\.sh|setup\.sh|bootstrap\.sh)([?#][^[:space:]]*)?'
pipe_installer_re="${downloader_names}[^${pipe_char}]*https?://[^${pipe_char}[:space:]]*(install|setup|bootstrap|\.sh)[^${pipe_char}]*\\${pipe_char}[[:space:]]*(sudo[[:space:]]+)?${shell_names}"
command_chain_re='(^|&&)'
checksum_re='(sha256sum[[:space:]][^;&|#]*(--check|-c)|shasum[[:space:]][^;&|#]*(-a[[:space:]]+256|--algorithm([=[:space:]]+)256)[^;&|#]*(--check|-c))'
signature_re='(cosign[[:space:]]+verify-blob|gpg[[:space:]][^;&|#]*--verify|minisign[[:space:]][^;&|#]*-V)'
verification_command_re="${command_chain_re}[[:space:]]*(${checksum_re}|${signature_re})[^;&|#]*"

escape_ere() {
  printf '%s' "$1" | sed -E 's/[][(){}.^$*+?|\]/\\&/g'
}

has_verification_for_download() {
  urls=$(printf '%s\n' "$command_without_comments" | grep -Eo -- "$archive_re" || true)
  [ -n "$urls" ] || return 1

  while IFS= read -r url; do
    [ -n "$url" ] || continue
    clean_url=$(printf '%s\n' "$url" | sed -E 's/[?#].*$//')
    artifact=${clean_url##*/}
    [ -n "$artifact" ] || continue
    artifact_re=$(escape_ere "$artifact")
    after_url=$(printf '%s\n' "$command_without_comments" | awk -v u="$url" '
      found { print }
      !found {
        index_at = index($0, u)
        if (index_at > 0) {
          print substr($0, index_at + length(u))
          found = 1
        }
      }
    ')
    artifact_verification_re="${verification_command_re}${artifact_re}[^;&|#]*($|&&)"
    if printf '%s\n' "$after_url" | grep -Eq -- "$artifact_verification_re"; then
      return 0
    fi
  done <<EOF
$urls
EOF

  return 1
}

if ! printf '%s\n' "$command_without_comments" | grep -Eq -- "$downloader_re"; then
  exit 0
fi

risky_download=0
if printf '%s\n' "$command_without_comments" | grep -Eq -- "$archive_re"; then
  risky_download=1
fi

pipe_installer=0
if printf '%s\n' "$command_without_comments" | grep -Eq -- "$pipe_installer_re"; then
  risky_download=1
  pipe_installer=1
fi

if [ "$risky_download" -ne 1 ]; then
  exit 0
fi

if [ "$pipe_installer" -eq 1 ]; then
  echo "Package download checksum guard: downloader-to-shell installer pipelines must be downloaded, verified, and run as separate steps." >&2
  echo "Download the script to a file, verify it with 'sha256sum -c', 'shasum -a 256 -c', 'cosign verify-blob', 'gpg --verify', or 'minisign -V', then run the reviewed file." >&2
  echo "Set PACKAGE_DOWNLOAD_GUARD_MODE=advisory to warn without blocking, or PACKAGE_DOWNLOAD_GUARD_ALLOWLIST to allow a reviewed command pattern." >&2
  if [ "${PACKAGE_DOWNLOAD_GUARD_MODE:-block}" = "advisory" ]; then
    exit 0
  fi
  exit 2
fi

if has_verification_for_download; then
  exit 0
fi

echo "Package download checksum guard: archive or installer download has no checksum/signature verification step." >&2
echo "Add verification such as 'sha256sum -c', 'shasum -a 256 -c', 'cosign verify-blob', 'gpg --verify', or 'minisign -V' before using the downloaded artifact." >&2
echo "Set PACKAGE_DOWNLOAD_GUARD_MODE=advisory to warn without blocking, or PACKAGE_DOWNLOAD_GUARD_ALLOWLIST to allow a reviewed command pattern." >&2

if [ "${PACKAGE_DOWNLOAD_GUARD_MODE:-block}" = "advisory" ]; then
  exit 0
fi

exit 2
HOOK
chmod +x .claude/hooks/package-download-checksum-guard.sh
mkdir -p .claude/hooks && touch .claude/hooks/lockfile-provenance-checker.sh && chmod +x .claude/hooks/lockfile-provenance-checker.sh
mkdir -p .claude/hooks && touch .claude/hooks/ort-dependency-license-checker.sh && chmod +x .claude/hooks/ort-dependency-license-checker.sh
mkdir -p .claude/hooks && install -m 0755 .claude/hooks/package-lock-risk-detector.sh .claude/hooks/
Config
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/package-download-checksum-guard.sh"
          }
        ]
      }
    ]
  }
}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/lockfile-provenance-checker.sh"
          }
        ]
      }
    ]
  }
}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/ort-dependency-license-checker.sh"
          }
        ]
      }
    ]
  }
}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "./.claude/hooks/package-lock-risk-detector.sh"
          }
        ]
      }
    ]
  }
}
Citations
ClaimUnclaimedUnclaimedUnclaimedUnclaimed
Open 4 picks in the interactive comparison tool

Related guides

Signals

Loading live community signals…

More like this, weekly

A short, calm digest of reviewed Claude resources. Unsubscribe any time.