Add the no-binaries gate with a path-scoped allowlist

621 binaries are tracked here and they are supposed to be: 155 PDFs, 275
PNGs, 68 JPGs, the submitted reports and figures. Decision 3 keeps them and
skips the history rewrite, so a blanket block would be wrong. What the gate
is for is stopping NEW junk.

So the allowlist is scoped by path rather than by extension alone. Documents,
figures and datasets are allowed under Programming/ and NotProgramming/;
compiled output (.o .so .a .exe .dll .dylib .class .pyc) is allowed nowhere
and has no entry at all; and the repo root, gates/, scripts/ and .github/
have no entry either, so a binary dropped in the tooling directories is
blocked. The .tgz entries are load-bearing -- EOPSY ships its simulators as
ftp/{sched,memory}.tgz and `make setup` extracts them.

Verified three ways, because a gate that passes everything is vacuous:
  - all 2749 tracked files          -> exit 0
  - probe.exe at root, probe.png in gates/ -> BLOCKED, exit 1
  - a real submitted PDF            -> exit 0
plus shellcheck clean.

The script is a copy of testsAndMisc's, which is where this gate lives today
-- it is not in the shared ~/utils set. The header says so and says to move
it to ~/utils rather than let the two drift, since a hand-copied gate is what
produced four forks of the 250-line cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE
This commit is contained in:
Krzysztof Rudnicki 2026-08-28 17:37:53 +02:00
parent d407527863
commit 94f965fe3a
2 changed files with 167 additions and 0 deletions

72
.binary-allowlist Normal file
View File

@ -0,0 +1,72 @@
# Binary files this repo commits on purpose. One bash glob per line.
#
# Read by scripts/check_no_binaries.sh. Note that matching uses bash `case`,
# where `*` DOES cross `/` -- so `Programming/*.pdf` matches a .pdf at any
# depth under Programming/.
#
# Policy for this repo (approved decision 3). This is an archive of submitted
# university coursework: the reports, figures, schematics and datasets ARE the
# deliverable, so they stay, and history is not rewritten. What the gate is
# actually here to stop is NEW junk -- compiled output, and binaries dropped
# into the tooling directories.
#
# So the allowlist is scoped by PATH, not blanket:
# - documents/figures/data are allowed under Programming/ and NotProgramming/
# - compiled output (.o .so .a .exe .dll .dylib .class .pyc .pyo) is allowed
# NOWHERE, and has no entry below. 189 such files were untracked in
# d4075278; .gitignore keeps them out.
# - the repo root, gates/, scripts/ and .github/ have no entry either, so a
# binary added there is blocked.
#
# Every pattern here must also survive `git ls-files | git check-ignore
# --stdin --no-index` printing nothing -- an allowlisted file that .gitignore
# also matches fails silently on `git add` rather than erroring.
# --- Reports and presentations (155 .pdf, 3 .docx, 1 .doc, 1 .pptx) ---------
Programming/*.pdf
NotProgramming/*.pdf
Programming/*.doc
NotProgramming/*.doc
Programming/*.docx
NotProgramming/*.docx
Programming/*.ppt
NotProgramming/*.ppt
Programming/*.pptx
NotProgramming/*.pptx
# --- Figures, screenshots, schematics (275 .png, 68 .jpg, 52 .gif, ...) -----
Programming/*.png
NotProgramming/*.png
Programming/*.jpg
NotProgramming/*.jpg
Programming/*.jpeg
NotProgramming/*.jpeg
Programming/*.gif
NotProgramming/*.gif
Programming/*.bmp
NotProgramming/*.bmp
Programming/*.svg
NotProgramming/*.svg
Programming/*.webp
NotProgramming/*.webp
Programming/*.ico
NotProgramming/*.ico
Programming/*.tif
NotProgramming/*.tif
Programming/*.tiff
NotProgramming/*.tiff
# --- Fonts shipped inside a submitted project ------------------------------
Programming/*.ttf
NotProgramming/*.ttf
# --- Datasets and course-supplied archives ---------------------------------
# The .tgz entries matter: EOPSY ships its simulators as ftp/sched.tgz and
# ftp/memory.tgz, and `make setup` extracts them. Blocking those would break
# the lab. The .zip files are submitted result bundles and course inputs.
Programming/*.zip
NotProgramming/*.zip
Programming/*.tgz
NotProgramming/*.tgz
Programming/*.bin
NotProgramming/*.bin

95
scripts/check_no_binaries.sh Executable file
View File

@ -0,0 +1,95 @@
#!/bin/bash
# Pre-commit hook: block binary and image files from being committed.
#
# Copied from ~/testsAndMisc/meta/scripts/check_no_binaries.sh, which is where
# this gate lives today (it is NOT in the shared ~/utils gate set). Keep the two
# in step, and prefer moving it to ~/utils over letting them drift -- a
# hand-copied gate is exactly what produced four forks of the 250-line cap.
#
# Only the remedy text differs: "move it to a sibling _binaries/ dir" is wrong
# advice here, because in a coursework archive the PDFs and figures ARE the
# submitted deliverable (approved decision 3: allowlist in place, no history
# rewrite).
# Allowed exceptions are listed in .binary-allowlist (one glob pattern per line).
set -euo pipefail
ALLOWLIST_FILE=".binary-allowlist"
# Binary/image extensions to block
BLOCKED_EXTENSIONS=(
# Images
png jpg jpeg gif webp svg ico bmp tiff tif psd
# Audio/Video
mp3 mp4 wav avi mkv flac ogg wma aac m4a mov wmv flv
# Archives
zip tar gz tgz bz2 xz 7z rar
# Documents
pdf doc docx xls xlsx ppt pptx
# Fonts
ttf woff woff2 eot otf
# Compiled / binary
o so a exe dll dylib pyc pyo class
# Data
apkg bin flat db sqlite sqlite3
)
# Build regex pattern from extensions
pattern=""
for ext in "${BLOCKED_EXTENSIONS[@]}"; do
if [ -z "$pattern" ]; then
pattern="\\.(${ext}"
else
pattern="${pattern}|${ext}"
fi
done
pattern="${pattern})$"
# Load allowlist
allowed_patterns=()
if [ -f "$ALLOWLIST_FILE" ]; then
while IFS= read -r line; do
# Skip comments and empty lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "${line// /}" ]] && continue
allowed_patterns+=("$line")
done < "$ALLOWLIST_FILE"
fi
is_allowed() {
local file="$1"
for pat in "${allowed_patterns[@]+"${allowed_patterns[@]}"}"; do
# Use bash pattern matching (glob)
# shellcheck disable=SC2254
case "$file" in
$pat) return 0 ;;
esac
done
return 1
}
found=0
for file in "$@"; do
# Skip files not tracked by git (deleted from index or untracked)
git ls-files --error-unmatch "$file" >/dev/null 2>&1 || continue
# Check if the file matches a blocked extension
if echo "$file" | grep -qiE "$pattern"; then
if is_allowed "$file"; then
continue
fi
echo "BLOCKED: $file"
echo " Binary/image files should not be committed to the repo."
echo " In this coursework archive the submitted reports, figures and"
echo " datasets are allowed -- but only under Programming/ or NotProgramming/."
echo " To allow this file, add a glob pattern to $ALLOWLIST_FILE"
found=1
fi
done
if [ "$found" -eq 1 ]; then
echo ""
echo "ERROR: Attempted to commit binary/image files."
echo "Options:"
echo " 1. A submitted deliverable? Add a glob to $ALLOWLIST_FILE"
echo " 2. Build output? Add it to .gitignore instead, never the allowlist"
exit 1
fi