mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-06 19:35:13 +02:00
104 lines
3.3 KiB
Bash
Executable file
104 lines
3.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Phase 30 — Track C: determinism audit gate.
|
|
#
|
|
# Greps `src/dynamic/` for non-deterministic RNG APIs. Anything inside
|
|
# the dynamic verifier must route through `crate::dynamic::rand::SpecRng`
|
|
# so identical inputs produce identical sandbox runs; the Phase 27
|
|
# `events.jsonl` replay invariant and the Phase 28 repro bundle
|
|
# hermeticity contract both depend on it.
|
|
#
|
|
# Exits 0 on a clean tree, 1 when any banned API surfaces. CI wires
|
|
# this into the dynamic workflow so a regression fails the build before
|
|
# it ships.
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
DYN_DIR="$ROOT/src/dynamic"
|
|
FUZZ_DIR="$ROOT/fuzz/dynamic_corpus/src"
|
|
|
|
if [[ ! -d "$DYN_DIR" ]]; then
|
|
echo "audit: src/dynamic/ missing at $DYN_DIR" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# The dynamic-corpus mutation fuzzer is also audited: it routes every
|
|
# randomness draw through `SpecRng::seeded(&spec.spec_hash)` so two
|
|
# runs against the same fixture produce identical candidate streams,
|
|
# matching the determinism contract of the verifier it feeds.
|
|
if [[ ! -d "$FUZZ_DIR" ]]; then
|
|
# Soft warn — the fuzzer is optional during early bootstrap.
|
|
echo "audit: fuzz/dynamic_corpus/src/ missing at $FUZZ_DIR (skipping)" >&2
|
|
fi
|
|
|
|
# Banned patterns: any real call site of a non-deterministic RNG API.
|
|
#
|
|
# Each pattern is a Rust-token shape we expect to never appear inside
|
|
# src/dynamic/ once Phase 30 lands. The seccomp policy file (which
|
|
# names the "getrandom" syscall as a string literal) is excluded
|
|
# because its mention is a syscall name, not a Rust API call — the
|
|
# string-literal regex below matches the bare token, and the seccomp
|
|
# files spell it inside quotes that look identical, so we exclude the
|
|
# seccomp subtree explicitly.
|
|
PATTERNS=(
|
|
'rand::thread_rng'
|
|
'thread_rng\s*\('
|
|
'rand::random'
|
|
'OsRng'
|
|
'from_entropy'
|
|
'getrandom::getrandom'
|
|
'Uuid::new_v4'
|
|
'uuid::Uuid::new_v4'
|
|
'fastrand'
|
|
'nanoid'
|
|
)
|
|
|
|
EXCLUDE_PATHS=(
|
|
"$DYN_DIR/sandbox/seccomp"
|
|
"$DYN_DIR/rand.rs"
|
|
)
|
|
|
|
# Use `git grep` when inside a git repo (respects .gitignore), fall
|
|
# back to `grep -r` otherwise. Either way the exclusion list is
|
|
# applied via a post-filter so the audit catches new files even
|
|
# before they are tracked.
|
|
if git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
HITS="$(git -C "$ROOT" grep -nE "$(IFS='|'; echo "${PATTERNS[*]}")" \
|
|
-- 'src/dynamic/**/*.rs' 'src/dynamic/*.rs' \
|
|
'fuzz/dynamic_corpus/src/**/*.rs' 'fuzz/dynamic_corpus/src/*.rs' \
|
|
|| true)"
|
|
else
|
|
HITS="$(grep -rnE "$(IFS='|'; echo "${PATTERNS[*]}")" --include='*.rs' \
|
|
"$DYN_DIR" ${FUZZ_DIR:+"$FUZZ_DIR"} || true)"
|
|
fi
|
|
|
|
if [[ -z "$HITS" ]]; then
|
|
echo "audit: src/dynamic/ is free of unseeded RNG APIs"
|
|
exit 0
|
|
fi
|
|
|
|
FILTERED=""
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
path="${line%%:*}"
|
|
skip=0
|
|
for ex in "${EXCLUDE_PATHS[@]}"; do
|
|
case "$path" in
|
|
"$ex"*|"${ex#$ROOT/}"*) skip=1; break ;;
|
|
esac
|
|
done
|
|
if [[ $skip -eq 0 ]]; then
|
|
FILTERED+="$line"$'\n'
|
|
fi
|
|
done <<< "$HITS"
|
|
|
|
if [[ -z "${FILTERED//[$' \t\n\r']/}" ]]; then
|
|
echo "audit: src/dynamic/ is free of unseeded RNG APIs"
|
|
exit 0
|
|
fi
|
|
|
|
echo "audit: banned RNG APIs surfaced inside src/dynamic/" >&2
|
|
echo "$FILTERED" >&2
|
|
echo >&2
|
|
echo "Replace with crate::dynamic::rand::SpecRng::seeded(&spec.spec_hash)." >&2
|
|
exit 1
|