Security
On this page
block fetches prebuilt binaries from the internet and puts them on your
PATH. These are the promises it keeps while doing that, and the ones it does
not make.
What block installs #
block installs artifacts an upstream has already built and published: a GitHub
release asset, or a prebuilt archive on the vendor’s own download server. It
does not build a tool from source. go install, cargo install, go build,
cargo build, make, cmake, forge build and any other build or install
script an upstream documents are outside what a recipe can ask for, because a
recipe is data and has no field to put a command in.
Reference has the two source types and
what each may point at.
That is a boundary block draws on purpose rather than a gap in the recipes. Building from source makes the source tree, the dependency resolver, every transitive dependency, the build script, the compiler and runtime versions, the native libraries and the system toolchain part of what runs while a tool is installed, with network access throughout. Leaving all of it out keeps installation to one shape, which is the shape the rest of this page is about:
published artifact -> resolve -> download -> digest check -> block.lock -> sync
Fewer inputs at install time is worth more in this domain than in most. A blockchain repository’s CI and its developers’ machines often hold signing keys, deployer credentials, RPC keys and cloud tokens, and a tool installation runs with all of that within reach. block would rather install fewer kinds of thing than run more code there. General-purpose managers such as mise and aqua support a wider set of installation sources, including source builds, which is what a general-purpose manager is for; block’s set is narrower, for a narrower catalogue.
What this does not claim: a digest says the bytes are the bytes that were
locked, not that they are safe, and not that whoever published them is who they
say they are. An upstream whose account is taken over can publish a malicious
release, and block will pin that release like any other. What block.lock
rules out is the artifact changing underneath a lockfile that did not: the same
lockfile installs the same bytes on every machine, and anything else is a
refusal rather than a substitution.
Transport #
Artifacts are fetched over HTTPS only, and every redirect is held to the same
rule: an https URL that redirects to plain http is refused rather than
followed. Plain HTTP is accepted only for loopback addresses, so offline test
servers can stand in for GitHub.
Checksums #
Every download is hashed while streaming and must match the SHA-256 in
block.lock before anything is extracted. The digest comes from the upstream
when it publishes one — GitHub records a SHA-256 for release assets uploaded
since 2025 — and from the first download otherwise.
The cache is content-addressed, and the name is not taken as proof: a cache hit is re-hashed before it is used, so a truncated download or a half-restored CI cache is discarded and fetched again instead of installed.
Unpacking #
Archives are extracted defensively. Absolute paths, .. components, symlinks
and hard links are refused — also after strip_components — and only the
executable bit is preserved from archive permissions. Executable paths are
validated the same way in a recipe and in a lockfile, because a lockfile
arrives through pull requests and hand edits too.
Installing #
Installs are atomic and self-attesting: everything is unpacked into a temporary directory, every declared executable is confirmed to be present and runnable, a completion marker is written, and only then is the directory renamed into place. An install without that marker — or missing one of its executables — is replaced, never run.
One command name may not mean two executables — across tools or inside one.
Which of them runs would otherwise depend on how it was called: a shim
resolves a command through the lockfile, PATH resolves it by directory
order, and the two can disagree. Names are compared without regard to case on
every platform, because Windows resolves PATH that way and a lockfile is
committed and read everywhere; block refuses the ambiguity where it is
written rather than where it happens to break.
Boundaries #
sync never resolves and exec never installs. exec re-checks offline that
block.lock still matches block.toml. Nothing falls back to an artifact the
lockfile does not name, and no registry recipe can run a command: a recipe is
data, and there is no install = "curl … | bash" escape hatch.
Verifying a block release #
Every published artifact — the archives and the .deb, .rpm and .apk
packages — is listed in checksums.txt, ships with an SPDX SBOM beside it,
and carries SLSA build provenance attested through GitHub OIDC.
checksums.txt is the file cosign signs, keyless, and it is what the
signature covers the rest through: verify the signature over the list, then
check your download against the line that names it. Build provenance is
attested per artifact, so gh attestation verify works directly on whichever
file you downloaded.
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-identity-regexp 'https://github.com/nao1215/block/.github/workflows/release.yml@refs/tags/v.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
sha256sum --check --ignore-missing checksums.txt
Reading a refusal #
Every refusal on this page carries a code, and the ones in this section are the
BLK6xxx family: block declining, rather than failing. BLK6001 is a URL it
will not fetch, BLK6002 an entry that would be written outside its directory,
BLK6003 a link or a device node in an archive, BLK6004 a member larger than
block will extract, BLK6005 a name or version from block.lock that is not a
path component.
block explain BLK6002
Every code, and what to do about each, is at Error codes.
Reporting a vulnerability #
Privately, by email to n.chika156@gmail.com or through the repository’s Security tab. See SECURITY.md.