Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ indent_size = 2
indent_style = tab

# Vendored/generated trees and never-edit upstream files: leave bytes alone.
# This list is duplicated across .pre-commit-config.yaml, .typos.toml,
# .gitleaks.toml, .editorconfig, and .github/scripts/check-hashes.sh —
# update all together.
[{fonts,stagit,migration,pubkeys}/**]
insert_final_newline = false
trim_trailing_whitespace = false

# Binary and hashed image/icon assets anywhere in the tree: an editor save
# Binary and hashed media assets anywhere in the tree: an editor save
# must never alter their bytes — content-hashed filenames break otherwise
# (favicon.<hash>.svg legitimately lacks a final newline).
[*.{png,webp,svg,ico,woff2}]
# (favicon.<hash>.svg legitimately lacks a final newline). Covers types
# not present yet so a future asset starts protected; extend on new types.
[*.{png,webp,svg,ico,woff2,jpg,jpeg,gif,avif,mp4,webm}]
insert_final_newline = false
trim_trailing_whitespace = false
4 changes: 0 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
version: 2

# The pre-commit ecosystem GA'd in March 2026, but some dependabot-core
# paths still gate it behind this flag; harmless if unneeded.
enable-beta-ecosystems: true

updates:
- package-ecosystem: github-actions
directory: /
Expand Down
69 changes: 54 additions & 15 deletions .github/scripts/check-hashes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@
# discovered, not hardcoded, so renames cannot leave stale references
# behind; vendored trees are skipped (FONTLOG.txt holds historical
# commit-pinned permalinks that are valid but not present on disk);
# (c) _header.html and errdocs/err.html agree on the hash tokens they
# reference (compared by basename, since the two files may legitimately
# use different path prefixes for the same asset).
# (c) every tracked HTML template that references hashed assets agrees
# with the others on the tokens it references — templates are
# discovered, not hardcoded, so a new template cannot escape the
# check (compared by basename, since templates may legitimately use
# different path prefixes for the same asset);
# (d) every tracked hashed asset is referenced by at least one tracked
# text file — otherwise it is orphaned: published with the site but
# reachable by nothing.
#
# Word-splitting over `git ls-files` output is safe here: tracked names in
# this repo contain no whitespace.
Expand All @@ -25,6 +30,13 @@ fail() {
status=1
}

# Vendored trees never count as reference sources: FONTLOG.txt holds
# historical commit-pinned permalinks whose tokens are valid but stale.
# This list is duplicated across .pre-commit-config.yaml, .typos.toml,
# .gitleaks.toml, .editorconfig, and this script — update all together.
# Word-split deliberately when passed to git.
vendored_pathspecs=':!fonts/ :!stagit/ :!migration/ :!pubkeys/'

# --- (a) filename hash matches content hash --------------------------------
for f in $(git ls-files | grep -E '\.[0-9a-f]{64}\.' || true); do
# Extract the hash by pattern, not positional dot-splitting: several
Expand All @@ -40,28 +52,55 @@ refs() {
grep -oE '[A-Za-z0-9_/.-]*\.[0-9a-f]{64}\.[A-Za-z0-9.]+' "$1" | sort -u
}

for src in $(git grep -I -l -E '\.[0-9a-f]{64}\.' -- \
':!fonts/' ':!stagit/' ':!migration/' ':!pubkeys/'); do
# shellcheck disable=SC2086 # vendored_pathspecs must word-split
for src in $(git grep -I -l -E '\.[0-9a-f]{64}\.' -- $vendored_pathspecs); do
for ref in $(refs "$src"); do
[ -f "${ref#/}" ] || fail "$src references missing asset: $ref"
done
done

# --- (c) header and errdocs reference identical hash tokens ----------------
for src in _header.html errdocs/err.html; do
[ -f "$src" ] || fail "expected template missing: $src"
# --- (d) every tracked hashed asset is referenced somewhere ----------------
# One grep collects every .<token>. occurrence in tracked text files; an
# asset whose token never appears is orphaned. Reference chains satisfy
# this naturally (fonts are reachable via the hashed stylesheets).
# shellcheck disable=SC2086 # vendored_pathspecs must word-split
referenced=$(git grep -I -h -oE '\.[0-9a-f]{64}\.' -- $vendored_pathspecs | sort -u)
for f in $(git ls-files | grep -E '\.[0-9a-f]{64}\.' || true); do
tok=$(expr "$f" : '.*\.\([0-9a-f]\{64\}\)\.')
case "$referenced" in
*".$tok."*) ;;
*) fail "orphaned hashed asset (referenced by nothing): $f" ;;
esac
done

# --- (c) HTML templates reference identical hash tokens --------------------
tokens() {
refs "$1" | sed 's|.*/||' | sort -u
}

header_tokens=$(tokens _header.html)
err_tokens=$(tokens errdocs/err.html)
if [ "$header_tokens" != "$err_tokens" ]; then
fail '_header.html and errdocs/err.html disagree on hashed assets:'
printf '%s\n' '--- _header.html:' "$header_tokens" \
'--- errdocs/err.html:' "$err_tokens" >&2
fi
templates=$(git grep -I -l -E '\.[0-9a-f]{64}\.' -- '*.html' || true)

# Guard against the check silently degrading: these two must always be in
# the discovered set.
for required in _header.html errdocs/err.html; do
printf '%s\n' "$templates" | grep -qx "$required" ||
fail "expected template missing from agreement check: $required"
done

base_file=''
base_tokens=''
for t in $templates; do
if [ -z "$base_file" ]; then
base_file=$t
base_tokens=$(tokens "$t")
continue
fi
t_tokens=$(tokens "$t")
if [ "$t_tokens" != "$base_tokens" ]; then
fail "$base_file and $t disagree on hashed assets:"
printf '%s\n' "--- $base_file:" "$base_tokens" \
"--- $t:" "$t_tokens" >&2
fi
done

exit "$status"
24 changes: 24 additions & 0 deletions .github/scripts/gitleaks-push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
# gitleaks-push.sh — secret scan for the pre-push hook.
#
# When pre-commit provides the pushed range (PRE_COMMIT_FROM_REF/TO_REF,
# set at the pre-push stage), scan just those commits: anything older is
# already public, so re-scanning ~1,100 commits on every push adds
# latency, not protection. Without a range — CI's
# `pre-commit run --all-files --hook-stage pre-push`, or a manual run —
# fall back to full history, which keeps the exhaustive scan in CI.
#
# gitleaks comes from the hook's golang environment, which pre-commit
# puts on PATH.

set -eu

from=${PRE_COMMIT_FROM_REF:-}
to=${PRE_COMMIT_TO_REF:-}

if [ -n "$from" ] && [ -n "$to" ] &&
git rev-parse --verify --quiet "$from^{commit}" > /dev/null; then
exec gitleaks git --redact --verbose --log-opts="$from..$to"
fi

exec gitleaks git --redact --verbose
7 changes: 5 additions & 2 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ jobs:
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- run: pipx install pre-commit==4.6.0
# uv installs in ~1-3s where pipx takes 5-15s building a venv.
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- run: uv tool install pre-commit==4.6.0
- run: pre-commit run --all-files --show-diff-on-failure
- run: pre-commit run --all-files --hook-stage pre-push

Expand All @@ -40,7 +42,8 @@ jobs:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- run: pipx install zizmor==1.25.2
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- run: uv tool install zizmor==1.25.2
- run: zizmor --format=github .
env:
# Without a token zizmor silently skips its online audits
Expand Down
4 changes: 4 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
[extend]
useDefault = true

# The vendored-tree list is duplicated across .pre-commit-config.yaml,
# .typos.toml, .gitleaks.toml, .editorconfig, and
# .github/scripts/check-hashes.sh — update all together. fonts/ is
# deliberately absent here: font binaries hold nothing secret-shaped.
[[allowlists]]
description = "Public keys, archived migration content, and stagit output are published on purpose"
paths = [
Expand Down
21 changes: 16 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Quality gate for anthes.is. Hygiene fixers must never touch vendored
# trees or content-hashed assets (editing one silently breaks its hash) —
# the &generated anchor below excludes both.
#
# The vendored-tree list {fonts, stagit, migration, pubkeys} is duplicated
# in each tool's own syntax across .pre-commit-config.yaml, .typos.toml,
# .gitleaks.toml, .editorconfig, and .github/scripts/check-hashes.sh —
# update all together.
default_stages: [pre-commit]

repos:
Expand Down Expand Up @@ -60,13 +65,19 @@ repos:
entry: .github/scripts/check-hashes.sh
language: script
pass_filenames: false
always_run: true
# The check always covers the whole tree; this filter only decides
# when it runs: any change to a hashed asset or to a file type that
# references hashed assets (articles, templates, stylesheets,
# webmanifest). CI's --all-files run always triggers it.
files: '\.(md|html|css|webmanifest)$|\.[0-9a-f]{64}\.'
# Local hook rather than the official one: upstream hardcodes
# --staged in its entry, which consumers cannot override, and this
# hook should scan full history at push time instead.
# --staged in its entry, which consumers cannot override. The wrapper
# scans only the pushed range locally and full history when no range
# is provided (CI's --hook-stage pre-push run, on a fetch-depth: 0
# checkout).
- id: gitleaks
name: gitleaks (full history)
entry: gitleaks git --redact --verbose
name: gitleaks
entry: .github/scripts/gitleaks-push.sh
language: golang
additional_dependencies:
# gitleaks still declares its Go module path under the old
Expand Down
3 changes: 3 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[files]
# The vendored-tree list is duplicated across .pre-commit-config.yaml,
# .typos.toml, .gitleaks.toml, .editorconfig, and
# .github/scripts/check-hashes.sh — update all together.
extend-exclude = [
"stagit/",
"migration/",
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading