Where the Scorecard number is capped for a one-maintainer project, and the Best Practices questionnaire answer sheet to enter at bestpractices.dev. Work already shipped for either badge is not repeated here; git history is the ledger, and SECURITY.md#assurance-case is the security half. The account-side steps still open are in README’s Roadmap.
Scorecard’s total is a risk-weighted average of its checks. Where it falls short, and whether each is fixable here:
Code-Review sits at 0 — no recent changeset carries an approved review: one maintainer, nobody else to approve a PR. A Reviewed-by: trailer would satisfy the scanner without a review having happened, so it won’t be added. Not fixable without a second person.
Fuzzing sits at 0 — Scorecard detects OSS-Fuzz, ClusterFuzzLite, Go native fuzzing, cargo-fuzz, and OneFuzz, none of which targets shell. .scorecard.yml marks it not-applicable.
Contributors sits at 3 — the check wants ≥2 contributing organizations among recent contributors; there’s one. not-applicable in .scorecard.yml too.
CII-Best-Practices scores the badge level at bestpractices.dev, a self-assessment separate from Scorecard. Passing is met; silver waits on access_continuity (below).
Signed-Releases scores off release assets alone. release.yml ships dist/SHA256SUMS.minisig, which the check’s signature probe recognizes for 8/10, and publish re-uploads build’s provenance attestation as dist/say-hi.intoto.jsonl - the literal filename the provenance probe looks for - for the full 10/10. The score is per-tag and never moves retroactively, so read it against the newest release, not an older one.
Local actions stay uses: ./.... GitHub shipped same-repository uses: $/... references in July 2026 (changelog), but Scorecard’s dependency extraction reads every $/... reference as an unpinned third-party action, so .github/zizmor.yml disables zizmor’s self-repository audit and the workflows keep ./. Every third-party action is SHA-pinned (re-count with grep -rhoE 'uses: +[^ ]+' .github/workflows .github/actions). The tests/dockerfiles/ findings are annotated test-data (TESTING.md).
Branch-Protection sits at 8, by choice. The next tier up requires “include administrators”, which would take away the maintainer’s ability to push past a failing check - the emergency valve for a one-person project. 10 also needs two required approving reviews, so a second person regardless.
The Best Practices answer sheet
Enter these at bestpractices.dev/en/projects/14397 (signed in, Edit; the edit URL itself 404s without a session). M = Met, N/A = not applicable, U = Unmet. access_continuity (a silver MUST) is answered Unmet on purpose - see its row below - so silver will not be awarded by filling in the rest; do it anyway, since a complete honest entry is the point and it’s a prerequisite the day a second maintainer exists.
Passing level
Already 100%. One correction worth making, and one answer worth keeping:
Criterion
Now
Change to
Why
dynamic_analysis_enable_assertions
U, “No fuzzer for bash/shell scripts.”
M
Wrong question answered - this criterion is about run-time assertions during testing, not fuzzing. Every entry point runs set -euo pipefail; the suites assert invariants directly, and --require-run turns a stood-down backend into a failure rather than a silent pass.
dynamic_analysis
U, “No sanitizer/fuzzer works for bash/shell scripts that I’m aware of.”
U, tighten the text
Unmet is right. The alternate route (“an automated test suite with at least 80% branch coverage”) doesn’t apply either: kcov and bashcov both report statement coverage (the README badges), not branch.
The lint gate mechanically fails on doc drift - GLOSSARY tags both ways, SETTINGS.md’s roster against _HI_TOGGLES, runner_test.sh against ci.yml’s --group roster, packaging_test.sh against release.yml.
documentation_achievements
M
README’s badge block links Best Practices, Scorecard, and Baseline.
Basics / Accessibility, i18n, other
Criterion
Answer
Evidence
accessibility_best_practices
M
NO_COLOR honored (_hi_has_color in common/core.sh) and propagated to the target (_hi_client_verdicts in hi.sh); a no-Unicode rendering when the locale is not UTF-8 (_hi_use_ascii in common/core.sh), propagated the same way.
internationalization
U (SHOULD, doesn’t block)
Output is short English status text; no message catalog, not planned before 1.0.
sites_password_security
N/A
GitHub/GitHub Pages; the project stores no passwords.
--group lint, a required check: shellcheck, shfmt, checkbashisms, zsh -n/fish --no-execute, markdownlint and prettier for the docs. Exceptions are per-line # shellcheck disable= comments at their location.
build_standard_variables / build_non_recursive
N/A
No native binaries, no compile step.
build_preserve_debug
N/A
Shell sources ship as-is.
build_repeatable
M
packaging-smoke builds the deb/rpm/apk twice and diffs SHA256SUMS for byte-identical output; RELEASING.md#reproducibility.
Quality / Installation and dependencies
Criterion
Answer
Evidence
installation_common
M
apt/dnf/apk repo, Homebrew tap, scripts/install.sh; --uninstall is the exact inverse.
installation_standard_variables
M
scripts/install.sh honors $DESTDIR and --prefix (its flag parsing, and install_tree in scripts/install.sh).
installation_development_quick
M
git clone then tests/test_runner.sh; fast suites are dependency-free.
Dependabot weekly (actions, npm, docker); tool-versions.yml weekly against tools.txt; image-scan.yml runs Trivy and tracks findings via an issue; dependency-review.yml fails a PR adding a dependency with a high or critical advisory.
updateable_reused_components
M
Nothing is vendored; the packaged install declares its tools as package dependencies.
interfaces_current
M
Bash-3.2-floor grep; tests/lint/dialects_test.sh parses the zsh and fish files under pinned builds of zsh’s floor and fish’s floor and ceiling.
Quality / Tests and warnings
Criterion
Answer
Evidence
automated_integration_testing
M
ci.yml on every pull_request and push to main; seven required checks before a merge.
regression_tests_added50
M
~50 suites under tests/; .github/pull_request_template.md sets a 90% coverage target for new code, and coverage.yml’s PR comment flags a miss.
test_statement_coverage80
M
README’s kcov and bashcov badges, both past the bar, measured over the shipped product.
test_policy_mandated
M
CONTRIBUTING.md - a new suite has a home and a test_runner.sh registration.
tests_documented_added
M
.github/pull_request_template.md checklist.
warnings_strict
M
.shellcheckrc disables nothing globally; shellcheck runs -x as a required gate alongside actionlint, zizmor, and mandoc -T lint -W warning.
SHA256SUMS signed with minisign, public key in PACKAGING.md#verifying-a-release-download; GPG signs the rpm and the apt/rpm repo metadata; a separate key signs the apk index. Private keys live in environment secrets on the release environment, readable only by the release jobs, never on the Pages site that distributes the packages.
version_tags_signed
M
Tags are SSH-signed and verify with git -c gpg.ssh.allowedSignersFile=.github/allowed_signers tag -v <tag>; release.yml’s gate job refuses to build one that doesn’t (RELEASING.md#signing-the-tag).
input_validation
M
_hi_safe_path in hi.sh, _hi_ssh_host_tag/_hi_ssh_pattern_hit in common/core.sh - allowlisted, not evaluated.
hardening
M
set -euo pipefail in every entry point; session payload lands in a directory removed on exit.
Analysis - static_analysis_common_vulnerabilities and dynamic_analysis_unsafe are Met.
Gold level
Three MUSTs need a second person regardless of repo state: bus_factor, contributors_unassociated, two_person_review. achieve_silver is Unmet as a consequence of access_continuity. The rest, for a complete entry:
Dropped: no shell source file carries a per-file copyright line any more, only # SPDX-License-Identifier: MIT (see license_per_file below). The criterion is suggested, not required at any level, and git history is the copyright record.
license_per_file
M
# SPDX-License-Identifier: MIT at the top of every shell source file, the CI helpers included; LICENSE.md holds the full text.
repo_distributed
M
git, hosted on GitHub.
small_tasks
M (needs one GitHub action)
Label two or three open issues good first issue and link the label URL - none exist yet.
Same evidence as silver, with the assurance-case URL.
dynamic_analysis
U
Same reasoning as passing’s entry - the branch-coverage alternate route is unmeasurable here.
dynamic_analysis_enable_assertions
M
set -euo pipefail throughout; e2e suites exercise real ssh/docker/podman/nomad/kube backends, and --require-run turns a stood-down backend into a failure.