Files

95 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build-time offline verification of pinned rule snapshots.
# Ordinary builds never fetch or convert; they only verify the local snapshots
# are present, intact, and match their manifests. Missing, corrupt, or
# version-mismatched snapshots fail the build.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
fail() {
echo "error: $*" >&2
echo " (run the matching Scripts/update-*.sh after reviewing upstream changes)" >&2
exit 1
}
verify_one() {
local name="$1"
local min_count="$2"
local max_count="$3"
local rules_dir="${ROOT}/Vendor/rules/${name}"
local snapshot="${rules_dir}/snapshot.json"
local manifest="${rules_dir}/manifest.json"
local notice="${rules_dir}/NOTICE"
[[ -f "${snapshot}" ]] || fail "missing ${snapshot}"
[[ -f "${manifest}" ]] || fail "missing ${manifest}"
[[ -f "${notice}" ]] || fail "missing ${notice} (license/attribution must ship with the snapshot)"
python3 - "${snapshot}" "${manifest}" "${min_count}" "${max_count}" "${name}" <<'PY' || fail "${name} rule snapshot verification failed"
import hashlib, json, sys
from pathlib import Path
snapshot_path, manifest_path, min_count, max_count, name = sys.argv[1:6]
min_count, max_count = int(min_count), int(max_count)
snapshot_bytes = Path(snapshot_path).read_bytes()
try:
snapshot = json.loads(snapshot_bytes)
except Exception as exc: # noqa: BLE001
raise SystemExit(f"snapshot is not valid JSON: {exc}")
if snapshot.get("schemaVersion") != 2:
raise SystemExit(f"schemaVersion mismatch: {snapshot.get('schemaVersion')!r} != 2")
meta = snapshot.get("metadata") or {}
for key in ("source", "upstreamReference", "inputDigest", "fetchedAt", "converterVersion", "license", "attribution"):
if key not in meta:
raise SystemExit(f"snapshot metadata missing {key!r}")
if meta.get("converterVersion") != "2.0.0":
raise SystemExit(
f"converterVersion mismatch: {meta.get('converterVersion')!r} != '2.0.0' "
"(re-run the explicit update or bump both sides together)"
)
rules = snapshot.get("rules")
if not isinstance(rules, list) or not rules:
raise SystemExit("snapshot has no rules")
if len(rules) < min_count or len(rules) > max_count:
raise SystemExit(f"abnormal rule count {len(rules)} (allowed {min_count}..{max_count})")
try:
manifest = json.loads(Path(manifest_path).read_text(encoding="utf-8"))
except Exception as exc: # noqa: BLE001
raise SystemExit(f"manifest is not valid JSON: {exc}")
for key in ("upstreamVersion", "snapshotSHA256", "converterVersion"):
if key not in manifest:
raise SystemExit(f"manifest missing {key!r}")
if manifest.get("converterVersion") != meta.get("converterVersion"):
raise SystemExit("manifest converterVersion does not match snapshot metadata")
digest = hashlib.sha256(snapshot_bytes).hexdigest()
if digest != manifest.get("snapshotSHA256"):
raise SystemExit(
"snapshot SHA-256 does not match manifest "
f"({digest} != {manifest.get('snapshotSHA256')})"
)
if manifest.get("upstreamVersion") != meta.get("source", {}).get("upstreamVersion"):
raise SystemExit("manifest upstreamVersion does not match snapshot metadata")
print(f"{name} snapshot ok: {len(rules)} rules, upstream {meta.get('source', {}).get('upstreamVersion')}")
PY
}
# geolocation-cn: domain rules.
verify_one "geolocation-cn" 100 200000
# china-ipv4: China mainland IPv4 CIDR.
verify_one "china-ipv4" 100 50000
# gfwlist: GFWList AutoProxy proxy candidates.
verify_one "gfwlist" 100 50000
echo "verify-rule-snapshots: ok"