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.
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.
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
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
Create the hooks directory: mkdir -p .claude/hooks
Install the hook script with the install command above, or paste the script
body into .claude/hooks/package-download-checksum-guard.sh.
Make the hook executable:
chmod +x .claude/hooks/package-download-checksum-guard.sh
Add the configuration below to .claude/settings.json for a project hook or
~/.claude/settings.json for a user hook.
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.
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.
[](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).
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.
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.
✓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.
✓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 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.
✓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