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
19 changes: 19 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{css,js,json,jsonc,yml,yaml,sh}]
indent_style = space
indent_size = 2

[Makefile]
indent_style = tab

# Vendored/generated trees and never-edit upstream files: leave bytes alone.
[{fonts,stagit,migration,pubkeys}/**]
insert_final_newline = false
trim_trailing_whitespace = false
26 changes: 26 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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: /
schedule:
interval: weekly
# Let new releases age before adopting them (supply-chain hygiene;
# zizmor's dependabot-cooldown audit enforces this).
cooldown:
default-days: 7
commit-message:
prefix: "chore(deps)"

- package-ecosystem: pre-commit
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
commit-message:
prefix: "chore(deps)"
60 changes: 60 additions & 0 deletions .github/scripts/check-hashes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/sh
# check-hashes.sh — integrity checks for content-hashed assets.
#
# (a) every tracked file with a .<sha256>. token in its name hashes to
# exactly that token;
# (b) every hashed asset referenced by _header.html, errdocs/err.html and
# site.webmanifest exists 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).
#
# Word-splitting over `git ls-files` output is safe here: tracked names in
# this repo contain no whitespace.

set -eu

cd "$(git rev-parse --show-toplevel)"

status=0
fail() {
printf 'check-hashes: %s\n' "$1" >&2
status=1
}

# --- (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
# images carry a double suffix (name.<hash>.2.png) that naive ${f%.*}
# parsing mishandles.
want=$(expr "$f" : '.*\.\([0-9a-f]\{64\}\)\.')
got=$(sha256sum "$f" | cut -d' ' -f1)
[ "$want" = "$got" ] || fail "content hash mismatch: $f (actual: $got)"
done

# --- (b) referenced hashed assets exist on disk ----------------------------
refs() {
grep -oE '[A-Za-z0-9_/.-]*\.[0-9a-f]{64}\.[A-Za-z0-9.]+' "$1" | sort -u
}

for src in _header.html errdocs/err.html site.webmanifest; do
[ -f "$src" ] || { fail "expected source file missing: $src"; continue; }
for ref in $(refs "$src"); do
[ -f "${ref#/}" ] || fail "$src references missing asset: $ref"
done
done

# --- (c) header and errdocs 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

exit "$status"
48 changes: 48 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: quality

on:
pull_request:
push:
branches: [master]

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
pre-commit:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# gitleaks scans full history
fetch-depth: 0
persist-credentials: false
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- run: pipx 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

workflow-security:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- run: pipx install zizmor==1.25.2
- run: zizmor --format=github .
env:
# Without a token zizmor silently skips its online audits
# (impostor-commit, known-vulnerable/unpinned-uses).
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20 changes: 20 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# gitleaks config for anthes.is. Extends the default ruleset; allowlists
# cover material that is public by design.
[extend]
useDefault = true

[[allowlists]]
description = "Public keys, archived migration content, and stagit output are published on purpose"
paths = [
'''^pubkeys/''',
'''^migration/''',
'''^stagit/''',
'''^gpg-transition-20250406\.txt$''',
]

[[allowlists]]
description = "PGP public key blocks and the Monero donation address are public by design"
regexes = [
'''-----BEGIN PGP (PUBLIC KEY BLOCK|SIGNATURE)-----''',
'''8B1s[1-9A-HJ-NP-Za-km-z]{90,}''',
]
31 changes: 31 additions & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
// Auto-exclude gitignored paths (specs/, test/, CLAUDE.md, ...).
"gitignore": true,
"config": {
"default": true,
// No hard line-length limit; prose uses soft wrapping.
"MD013": false,
"MD010": { "code_blocks": false },
// Shell examples deliberately show "$ " / "# " prompts.
"MD014": false,
"MD024": { "siblings_only": true },
// Published articles split ordered lists around code blocks and mix
// all-ones with sequential numbering.
"MD029": false,
// Ordered lists legitimately use both 1- and 2-space markers across
// published articles.
"MD030": false,
// Articles use no raw HTML outside code blocks.
"MD033": { "allowed_elements": [] },
// Emphasis lines are deliberate captions (e.g. "Tested on ..."),
// not headings.
"MD036": false,
// Fenced blocks without a language are fine; the site renderer does
// no syntax highlighting.
"MD040": false,
// Every article starts with an H1 title.
"MD041": true
// MD046 stays at its default (per-file consistent code-block style):
// some articles are all-fenced, others all-indented.
}
}
77 changes: 77 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 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.
default_stages: [pre-commit]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
exclude: &generated '^(fonts/|stagit/|migration/|pubkeys/)|\.[0-9a-f]{64}\.'
- id: end-of-file-fixer
exclude: *generated
- id: mixed-line-ending
args: [--fix=lf]
exclude: *generated
- id: check-yaml
- id: check-json
exclude: '\.jsonc$'
- id: check-merge-conflict
- id: check-case-conflict
- id: check-added-large-files
args: [--maxkb=600]

- repo: https://github.com/crate-ci/typos
rev: v1.47.2
hooks:
- id: typos
# Upstream defaults to --write-changes --force-exclude (autofix).
# Keep --force-exclude (excludes must apply to explicitly passed
# filenames) but drop --write-changes: report-only.
args: [--force-exclude]

- repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.22.1
hooks:
- id: markdownlint-cli2

- repo: https://github.com/rhysd/actionlint
rev: v1.7.12
hooks:
- id: actionlint

- repo: local
hooks:
- id: stylelint
name: stylelint
# --config-basedir points at pre-commit's isolated node env (located
# via the stylelint binary) so `extends` in .stylelintrc.json can
# resolve stylelint-config-standard.
entry: sh -c 'exec stylelint --config-basedir "$(dirname "$(dirname "$(command -v stylelint)")")/lib" "$@"' --
language: node
additional_dependencies:
- stylelint@17.13.0
- stylelint-config-standard@40.0.0
files: '\.css$'
exclude: '^stagit/'
- id: check-asset-hashes
name: check asset hashes
entry: .github/scripts/check-hashes.sh
language: script
pass_filenames: false
always_run: true
# 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.
- id: gitleaks
name: gitleaks (full history)
entry: gitleaks git --redact --verbose
language: golang
additional_dependencies:
# gitleaks still declares its Go module path under the old
# zricethezav account name.
- github.com/zricethezav/gitleaks/v8@v8.30.1
pass_filenames: false
always_run: true
stages: [pre-push]
18 changes: 18 additions & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": "stylelint-config-standard",
"ignoreFiles": ["stagit/**"],
"rules": {
"selector-class-pattern": null,
"custom-property-pattern": null,
"no-descending-specificity": null,
"no-duplicate-selectors": null,
"property-no-deprecated": [
true,
{ "ignoreProperties": ["/^page-break-/"] }
],
"property-no-vendor-prefix": [
true,
{ "ignoreProperties": ["/^(-webkit-|-moz-)text-size-adjust$/"] }
]
}
}
23 changes: 23 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[files]
extend-exclude = [
"stagit/",
"migration/",
"pubkeys/",
"fonts/",
"gpg-transition-20250406.txt",
"roAJVp7qzs.txt",
]

[default]
extend-ignore-re = [
# hex digests, commit hashes, content-hash filename tokens
"[0-9a-fA-F]{7,}",
# Monero donation address
"8B1s[1-9A-HJ-NP-Za-km-z]{90,}",
# markdown heading-anchor slugs (apostrophes dropped, e.g. "familys")
"\\(#[a-z0-9-]+\\)",
]

[default.extend-words]
# Grow only from real false positives. Identity mappings accept the word.
uncatalogued = "uncatalogued"
4 changes: 2 additions & 2 deletions _header.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<link
rel="stylesheet"
href="/styles.84d2a477bc0506677c24e4690e87812e05036b199b9632616b072558435db30b.css"
href="/styles.2f14b79bea0ae2281afa0cf80069ee9ac80ebdbda38b900173835d0db6014dda.css"
type="text/css"
/>
<link
Expand All @@ -33,7 +33,7 @@
<noscript>
<link
rel="stylesheet"
href="/styles-noscript.db6a2714b9e1b1c6beb69fd489ca559c50af8c5232c0ad7b2667633a711440e9.css"
href="/styles-noscript.2e04ce2510f9d98db0a2a153b215a39c91d1528dce02c29b6804632223237099.css"
type="text/css"
/>
</noscript>
Expand Down
Loading