security-review/nightly_sweep.sh

367 lines
20 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
# nightly_sweep.sh — Sea Haven nightly security sweep (RETIRED — retained for reference).
#
# The VM-based Path B sweep is retired (the sweep VM was destroyed); the automated
# sweep now runs as Claude Code web cloud routines (see README.md). This script is
# kept as the reference implementation of the two-tier sweep design.
#
# TWO-TIER, CLEAN-CLONE AUTO-DISCOVERY (no per-repo wiring):
# Discovery: enumerate ALL Sea-Haven-Industries org repos via the GitHub REST API
# (curl + a read-only fine-grained PAT in GH_TOKEN — no gh CLI dependency), then
# mirror each into ~/repo-mirrors as a shallow clean clone (git clone --depth=1,
# default branch from the API). Scanning server-side clones (not developer working
# trees) structurally avoids surfacing local gitignored .env secrets.
# TIER 1 (every repo, every night, $0 Claude): review.sh --scanners-only over every
# mirror — complete deterministic baseline coverage.
# TIER 2 (bounded agentic): the expensive run_headless.py detector+verifier pass runs
# over a deterministic ROUND-ROBIN rotation that fits TOTAL_BUDGET_USD, with a
# persistent cycle pointer so every repo gets a deep pass within MAX_CYCLE_NIGHTS.
# This bounds the draw on the SHARED Max subscription limits (see memory
# reference-claude-subscription-billing): a clean night never scans all repos
# agentically.
#
# Anti-complacency: the canary testbed is ALWAYS scanned agentically first (block +
# recall floor). Reporting is Slack ALARM-ONLY (a clean night posts NOTHING — see
# memory feedback_cloudwatch_alarms). Secret-shaped values are redacted from the Slack
# string; on-disk reports are written mode 600.
#
# Skip a repo: a .security-review-skip file committed at its root, OR an entry in the
# central skip list ($CENTRAL_SKIP_FILE). Repos skipped via their OWN committed marker
# are LOGGED in the report (auditable — a sensitive repo cannot silently self-exclude).
#
# Contract notes:
# - run_headless.py REQUIRES CLAUDE_CODE_OAUTH_TOKEN and pops ANTHROPIC_API_KEY.
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
# Source the sweep env file before invoking.
# - review.sh re-derives the block decision (exit 1 = BLOCK). This script makes NO
# block decision itself; it only reports.
#
# Config (env, all optional except auth):
# GH_TOKEN read-only fine-grained PAT (Contents: read) — REQUIRED for discovery
# GH_ORG org to enumerate (default: Sea-Haven-Industries)
# MIRROR_DIR clean-clone mirror root (default: ~/repo-mirrors)
# CENTRAL_SKIP_FILE one repo name per line, # comments (default: ~/.secrev-skip.txt)
# TARGETS space-separated paths to scan INSTEAD of discovery (manual override)
# TESTBED canary corpus dir (default: ~/security-review-testbed)
# CANARY_FLOOR min confirmed crit+high the canary MUST surface (default: 10)
# TOTAL_BUDGET_USD hard agentic spend ceiling across the night (default: 120 —
# full deep-pass coverage of every repo per night; first-run
# data 2026-06-17 showed $20 covered only canary + 5 repos)
# PER_TARGET_BUDGET_USD passed to run_headless --total-budget-usd (default: 12)
# MAX_CYCLE_NIGHTS alarm if the agentic rotation hasn't covered every repo in this many nights (default: 4)
# MAX_AGENTIC_PER_NIGHT cap on repos given the deep agentic pass per night, for wall-clock bounding
# (default: 0 = unlimited, bounded only by TOTAL_BUDGET_USD)
# REPORT_ROOT base dir for logs+JSON (default: ~/sweep-reports)
# SLACK_WEBHOOK_URL incoming-webhook URL; if unset, alarms are logged only
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
# ENABLE_XMODEL_HOOK 1 to run the cross-family critical tiebreak via this repo's
# cross_review.py (default: 0)
# VENV_PY python interpreter (default: python3 on PATH)
#
# Exit: 0 = sweep completed (whether or not it alarmed); 2 = setup/usage error.
set -euo pipefail
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
log() { echo "[nightly_sweep] $*" >&2; }
die() { echo "[nightly_sweep] FATAL: $*" >&2; exit 2; }
# --- Shared substrate (discovery / mirror / budget / rotation / Slack / canary) -
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
# Factored out so the sweep and the checker coordinator reuse one implementation, WITHOUT
# changing any sweep behavior. The functions close over this script's globals by name
# (bash dynamic scoping); see lib/sweep_substrate.sh for the read/mutate contract.
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/sweep_substrate.sh
. "$HERE/lib/sweep_substrate.sh"
# --- Config + defaults --------------------------------------------------------
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
VENV_PY="${VENV_PY:-$(command -v python3)}"
CROSS_REVIEW="$HERE/cross_review.py"
RUN_HEADLESS="$HERE/run_headless.py"
REVIEW_SH="$HERE/review.sh"
GH_ORG="${GH_ORG:-Sea-Haven-Industries}"
MIRROR_DIR="${MIRROR_DIR:-$HOME/repo-mirrors}"
CENTRAL_SKIP_FILE="${CENTRAL_SKIP_FILE:-$HOME/.secrev-skip.txt}"
TESTBED="${TESTBED:-$HOME/security-review-testbed}"
CANARY_FLOOR="${CANARY_FLOOR:-14}"
TOTAL_BUDGET_USD="${TOTAL_BUDGET_USD:-120}"
PER_TARGET_BUDGET_USD="${PER_TARGET_BUDGET_USD:-12}"
MAX_CYCLE_NIGHTS="${MAX_CYCLE_NIGHTS:-6}"
MAX_AGENTIC_PER_NIGHT="${MAX_AGENTIC_PER_NIGHT:-0}"
REPORT_ROOT="${REPORT_ROOT:-$HOME/sweep-reports}"
ENABLE_XMODEL_HOOK="${ENABLE_XMODEL_HOOK:-0}"
command -v jq >/dev/null || die "jq is required"
command -v curl >/dev/null || die "curl is required for org discovery"
command -v git >/dev/null || die "git is required"
[ -x "$VENV_PY" ] || die "venv python not found/executable: $VENV_PY"
[ -f "$RUN_HEADLESS" ] || die "run_headless.py not found: $RUN_HEADLESS"
[ -x "$REVIEW_SH" ] || die "review.sh not found/executable: $REVIEW_SH"
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
[ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || die "CLAUDE_CODE_OAUTH_TOKEN not set (source the sweep env file)"
UTC_DATE="$(date -u +%Y-%m-%d)"
UTC_STAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
REPORT_DIR="$REPORT_ROOT/$UTC_DATE"
mkdir -p "$REPORT_DIR"; chmod 700 "$REPORT_ROOT" "$REPORT_DIR" 2>/dev/null || true
ROTATION_STATE="$REPORT_ROOT/.rotation-state.json"
SWEEP_LOG="$REPORT_DIR/sweep.log"
exec > >(tee -a "$SWEEP_LOG") 2>&1
umask 077 # on-disk reports/logs are not world-readable
log "=== nightly sweep $UTC_STAMP (two-tier auto-discovery) ==="
log "org=$GH_ORG mirror=$MIRROR_DIR report=$REPORT_DIR total-budget=\$$TOTAL_BUDGET_USD canary-floor=$CANARY_FLOOR"
# --- Aggregate state ----------------------------------------------------------
TOTAL_SPEND="0"; BUDGET_HIT=0
declare -a ALARM_LINES=(); declare -a XMODEL_LINES=(); declare -a MARKER_SKIPS=()
# add_spend / over_budget (budget ledger), redact (Slack secret redaction),
# discover_repos (org enumeration), mirror_repo (clean shallow clone): provided by
# lib/sweep_substrate.sh, sourced above. They close over the globals defined here
# (TOTAL_SPEND, TOTAL_BUDGET_USD, GH_TOKEN, GH_ORG, MIRROR_DIR, REPORT_DIR).
# --- Skip resolution: "" = scan, else reason ("marker"|"central") --------------
declare -a CENTRAL_SKIP=()
if [ -f "$CENTRAL_SKIP_FILE" ]; then
while IFS= read -r line; do line="${line%%#*}"; line="$(echo "$line" | xargs || true)"
[ -n "$line" ] && CENTRAL_SKIP+=( "$line" ); done < "$CENTRAL_SKIP_FILE"
fi
skip_reason() { # name dir
local name="$1" dir="$2"
[ -f "$dir/.security-review-skip" ] && { echo "marker"; return; }
for s in ${CENTRAL_SKIP[@]+"${CENTRAL_SKIP[@]}"}; do [ "$s" = "$name" ] && { echo "central"; return; }; done
echo ""
}
# --- xmodel cross-family critical tiebreak (GUARDED, never fails the sweep) ----
xmodel_check_criticals() {
local label="$1" result_json="$2"
[ "$ENABLE_XMODEL_HOOK" = "1" ] || return 0
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
[ -f "$CROSS_REVIEW" ] || { log " xmodel hook: cross_review.py missing — skipping"; return 0; }
"$VENV_PY" -c 'import openai' >/dev/null 2>&1 || { log " xmodel hook: openai package missing — skipping"; return 0; }
local crits n; crits="$(jq -c '[.findings[]? | select(.status=="confirmed" and .severity=="critical")]' "$result_json" 2>/dev/null || echo '[]')"
n="$(echo "$crits" | jq 'length')"; [ "${n:-0}" -gt 0 ] || return 0
log " xmodel hook: re-checking $n confirmed critical(s) for $label"
local i=0
while [ "$i" -lt "$n" ]; do
local summary; summary="$(echo "$crits" | jq -r --argjson i "$i" '.[$i] | "\(.cwe // "n/a") \(.file):\(.line // 0) — \(.title // .id) :: \(.data_flow // "")"')"
local verdict
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
if verdict="$("$VENV_PY" "$CROSS_REVIEW" "Independently assess whether this is a real exploitable vulnerability (yes/no) and why: $summary" 2>>"$REPORT_DIR/xmodel.log")"; then
if echo "$verdict" | grep -qiE '(^|[^a-z])no([^a-z]|$)|not (a |an )?(real |exploitable )?vuln'; then
XMODEL_LINES+=( "DISAGREEMENT on $label critical: $summary (cross_reviewer says NOT a vuln)" )
fi
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
else log " xmodel hook: cross_review.py failed for a critical (logged) — continuing"; fi
i=$((i+1))
done
}
# --- TIER 1: deterministic scanners over a target dir -------------------------
# Sets T1_BLOCK/T1_CRIT/T1_HIGH. review.sh exit 0 pass / 1 BLOCK / 2 setup.
T1_BLOCK=0; T1_CRIT=0; T1_HIGH=0
scan_scanners() { # target slug
local target="$1" slug="$2"
local result_json="$REPORT_DIR/${slug}.scanners.json"
T1_BLOCK=0; T1_CRIT=0; T1_HIGH=0
local sup=()
[ -f "$target/.security-review/suppressions.json" ] && sup=(--suppressions "$target/.security-review/suppressions.json")
set +e
"$REVIEW_SH" --scanners-only ${sup[@]+"${sup[@]}"} --json-out "$result_json" "$target" >"$REPORT_DIR/${slug}.scanners.log" 2>&1
local rc=$?
set -e
[ "$rc" -eq 2 ] && { ALARM_LINES+=( "*$slug*: review.sh scanner setup error. See \`$REPORT_DIR/${slug}.scanners.log\`." ); return; }
T1_CRIT="$(jq -r '(.summary.confirmed_critical // 0)' "$result_json" 2>/dev/null || echo 0)"
T1_HIGH="$(jq -r '(.summary.confirmed_high // 0)' "$result_json" 2>/dev/null || echo 0)"
[ "$rc" -eq 1 ] && T1_BLOCK=1
return 0 # MUST return 0: results go via globals; a falsey last cmd would trip set -e in the caller
}
# --- TIER 2: agentic run_headless + full review.sh over a target dir ----------
# Sets LAST_BLOCK/LAST_CRIT/LAST_HIGH/LAST_REASON/LAST_RESULT_JSON/LAST_ERRORS.
LAST_BLOCK=0; LAST_CRIT=0; LAST_HIGH=0; LAST_REASON=""; LAST_RESULT_JSON=""; LAST_ERRORS=0
scan_agentic() { # target slug
local target="$1" slug="$2"
LAST_BLOCK=0; LAST_CRIT=0; LAST_HIGH=0; LAST_REASON=""; LAST_RESULT_JSON=""; LAST_ERRORS=0
[ -d "$target" ] || { ALARM_LINES+=( "Target *$slug* ($target) missing — could not scan." ); LAST_ERRORS=1; return; }
local agent_json="$REPORT_DIR/${slug}.agent.json" result_json="$REPORT_DIR/${slug}.result.json" runner_log="$REPORT_DIR/${slug}.runner.log"
LAST_RESULT_JSON="$result_json"
log " [$slug] run_headless.py (per-target budget \$$PER_TARGET_BUDGET_USD)"
if ! "$VENV_PY" "$RUN_HEADLESS" "$target" --out "$agent_json" --total-budget-usd "$PER_TARGET_BUDGET_USD" >>"$runner_log" 2>&1; then
ALARM_LINES+=( "*$slug*: run_headless.py failed (setup error). See \`$runner_log\`." ); LAST_ERRORS=1; return
fi
[ -f "$agent_json" ] || { ALARM_LINES+=( "*$slug*: run_headless produced no JSON." ); LAST_ERRORS=1; return; }
local spend errs; spend="$(jq -r '(._meta.spend_usd // 0)' "$agent_json")"; errs="$(jq -r '(._meta.errors // []) | length' "$agent_json")"
add_spend "$spend"; LAST_ERRORS="$errs"
log " [$slug] spend \$$spend, runner errors $errs, total \$$TOTAL_SPEND"
[ "${errs:-0}" -gt 0 ] && ALARM_LINES+=( "*$slug*: run_headless reported $errs error(s): $(jq -r '(._meta.errors // []) | join("; ")' "$agent_json")" )
local sup=()
[ -f "$target/.security-review/suppressions.json" ] && sup=(--suppressions "$target/.security-review/suppressions.json")
set +e
"$REVIEW_SH" --agent-findings "$agent_json" ${sup[@]+"${sup[@]}"} --json-out "$result_json" "$target" >"$REPORT_DIR/${slug}.review.log" 2>&1
local rc=$?
set -e
[ "$rc" -eq 2 ] && { ALARM_LINES+=( "*$slug*: review.sh setup error. See \`$REPORT_DIR/${slug}.review.log\`." ); LAST_ERRORS=$((LAST_ERRORS+1)); return; }
LAST_CRIT="$(jq -r '(.summary.confirmed_critical // 0)' "$result_json" 2>/dev/null || echo 0)"
LAST_HIGH="$(jq -r '(.summary.confirmed_high // 0)' "$result_json" 2>/dev/null || echo 0)"
if [ "$rc" -eq 1 ]; then LAST_BLOCK=1; LAST_REASON="confirmed crit=$LAST_CRIT high=$LAST_HIGH"; log " [$slug] BLOCK ($LAST_REASON)"
else log " [$slug] PASS (crit=$LAST_CRIT high=$LAST_HIGH)"; fi
}
# ============================== 1) CANARY ====================================
CANARY_OK=1
if [ -d "$TESTBED" ]; then
log "--- canary (anti-complacency): $TESTBED ---"
scan_agentic "$TESTBED" "canary"
CANARY_CONFIRMED="$(canary_confirmed_count "$LAST_RESULT_JSON")"
log "canary: block=$LAST_BLOCK confirmed(crit+high)=$CANARY_CONFIRMED (floor=$CANARY_FLOOR)"
if [ "$LAST_BLOCK" -ne 1 ]; then
CANARY_OK=0; ALARM_LINES+=( "*COMPLACENCY ALARM*: canary testbed did NOT block. Result: \`$LAST_RESULT_JSON\`" )
elif [ "${CANARY_CONFIRMED:-0}" -lt "$CANARY_FLOOR" ]; then
CANARY_OK=0; ALARM_LINES+=( "*COMPLACENCY ALARM*: canary recall $CANARY_CONFIRMED < floor $CANARY_FLOOR. Result: \`$LAST_RESULT_JSON\`" )
fi
xmodel_check_criticals "canary" "$LAST_RESULT_JSON"
else
CANARY_OK=0; ALARM_LINES+=( "*COMPLACENCY ALARM*: canary testbed missing at $TESTBED." )
fi
# ============================== 2) DISCOVER + MIRROR =========================
declare -a REPO_NAMES=() # scan order (discovery order)
declare -A REPO_DIR=()
if [ -n "${TARGETS:-}" ]; then
# Manual override: scan explicit paths, no discovery/cloning.
# shellcheck disable=SC2206
arr=( $TARGETS )
for p in "${arr[@]}"; do
p="${p/#\~/$HOME}"; nm="$(basename "$p")"
REPO_NAMES+=( "$nm" ); REPO_DIR["$nm"]="$p"
done
log "manual TARGETS override: ${REPO_NAMES[*]}"
else
mkdir -p "$MIRROR_DIR"
DISCOVERED="$REPORT_DIR/discovered.tsv"
if discover_repos > "$DISCOVERED" 2>>"$REPORT_DIR/discover.log" && [ -s "$DISCOVERED" ]; then
NREPO="$(wc -l < "$DISCOVERED" | tr -d ' ')"
log "discovered $NREPO non-archived repo(s) in $GH_ORG"
while IFS=$'\t' read -r name url branch; do
[ -n "$name" ] || continue
if mirror_repo "$name" "$url" "$branch"; then
REPO_NAMES+=( "$name" ); REPO_DIR["$name"]="$MIRROR_DIR/$name"
else
log " mirror FAILED: $name"; ALARM_LINES+=( "*$name*: clone/pull failed — not scanned this night. See \`$REPORT_DIR/discover.log\`." )
fi
done < "$DISCOVERED"
log "mirrored ${#REPO_NAMES[@]} repo(s) into $MIRROR_DIR"
else
ALARM_LINES+=( "*DISCOVERY ALARM*: org enumeration failed (GH_TOKEN missing/invalid or API error). Falling back to existing mirrors; coverage may be stale. See \`$REPORT_DIR/discover.log\`." )
log "discovery failed — falling back to existing mirrors in $MIRROR_DIR"
if [ -d "$MIRROR_DIR" ]; then
for d in "$MIRROR_DIR"/*/; do [ -d "$d/.git" ] || continue; nm="$(basename "$d")"; REPO_NAMES+=( "$nm" ); REPO_DIR["$nm"]="${d%/}"; done
fi
fi
fi
# Resolve skips up front (so both tiers honor them and marker-skips are auditable).
declare -a SCANNABLE=()
for nm in ${REPO_NAMES[@]+"${REPO_NAMES[@]}"}; do
reason="$(skip_reason "$nm" "${REPO_DIR[$nm]}")"
if [ "$reason" = "marker" ]; then MARKER_SKIPS+=( "$nm" ); log " skip $nm (repo-committed .security-review-skip)"
elif [ "$reason" = "central" ]; then log " skip $nm (central skip list)"
else SCANNABLE+=( "$nm" ); fi
done
if [ "${#MARKER_SKIPS[@]}" -gt 0 ]; then
ALARM_LINES+=( "*self-excluded repos* (committed .security-review-skip, FYI/audit): ${MARKER_SKIPS[*]}" )
fi
log "scannable repos: ${#SCANNABLE[@]} (skipped: $(( ${#REPO_NAMES[@]} - ${#SCANNABLE[@]} )))"
# ============================== 3) TIER 1: scanners over ALL ==================
declare -a BLOCKED_T1=()
for nm in ${SCANNABLE[@]+"${SCANNABLE[@]}"}; do
scan_scanners "${REPO_DIR[$nm]}" "scan-$nm"
if [ "$T1_BLOCK" -eq 1 ]; then
BLOCKED_T1+=( "$nm" )
ALARM_LINES+=( "*$nm* TIER1/scanners BLOCK: crit=$T1_CRIT high=$T1_HIGH. Result: \`$REPORT_DIR/scan-$nm.scanners.json\`" )
fi
done
log "tier1 complete: ${#SCANNABLE[@]} scanned, ${#BLOCKED_T1[@]} blocked"
# ============================== 4) TIER 2: agentic rotation ===================
# Persistent cycle state: {cycle_start, scanned:[names]}. Reset the cycle once every
# scannable repo has had a deep pass; alarm if a cycle runs longer than MAX_CYCLE_NIGHTS.
[ -f "$ROTATION_STATE" ] || echo "{\"cycle_start\":\"$UTC_DATE\",\"scanned\":[]}" > "$ROTATION_STATE"
SCANNED_JSON="$(jq -c '.scanned // []' "$ROTATION_STATE" 2>/dev/null || echo '[]')"
CYCLE_START="$(jq -r '.cycle_start // empty' "$ROTATION_STATE" 2>/dev/null || echo "$UTC_DATE")"
[ -n "$CYCLE_START" ] || CYCLE_START="$UTC_DATE"
if [ "${#SCANNABLE[@]}" -gt 0 ]; then
SCANNABLE_JSON="$(printf '%s\n' "${SCANNABLE[@]}" | jq -R . | jq -cs .)"
else
SCANNABLE_JSON="[]"
fi
# If every scannable repo is already in scanned[], the cycle is complete -> start fresh.
if jq -e -n --argjson sc "$SCANNED_JSON" --argjson all "$SCANNABLE_JSON" '($all - $sc) | length == 0' >/dev/null 2>&1 \
&& [ "$(echo "$SCANNABLE_JSON" | jq 'length')" -gt 0 ]; then
log "agentic rotation: cycle complete ($CYCLE_START) — starting a new cycle"
SCANNED_JSON="[]"; CYCLE_START="$UTC_DATE"
fi
# This night's agentic candidates = scannable repos not yet scanned this cycle, discovery order.
PENDING_JSON="$(jq -c -n --argjson all "$SCANNABLE_JSON" --argjson sc "$SCANNED_JSON" '$all - $sc')"
declare -a BLOCKED_T2=(); AGENTIC_DONE=0
if over_budget; then
BUDGET_HIT=1; ALARM_LINES+=( "*BUDGET ALARM*: ceiling \$$TOTAL_BUDGET_USD hit after canary (\$$TOTAL_SPEND). No agentic rotation this night." )
else
while read -r nm; do
[ -n "$nm" ] || continue
if over_budget; then BUDGET_HIT=1; log "budget ceiling hit (\$$TOTAL_SPEND) — pausing rotation"; break; fi
if [ "$MAX_AGENTIC_PER_NIGHT" -gt 0 ] && [ "$AGENTIC_DONE" -ge "$MAX_AGENTIC_PER_NIGHT" ]; then
log "per-night agentic cap ($MAX_AGENTIC_PER_NIGHT) reached — pausing rotation"; break; fi
log "--- agentic: $nm ---"
scan_agentic "${REPO_DIR[$nm]}" "scan-$nm"
SCANNED_JSON="$(echo "$SCANNED_JSON" | jq -c --arg n "$nm" '. + [$n] | unique')"
AGENTIC_DONE=$((AGENTIC_DONE+1))
if [ "$LAST_BLOCK" -eq 1 ]; then
BLOCKED_T2+=( "$nm" )
ALARM_LINES+=( "*$nm* TIER2/agentic BLOCK: $LAST_REASON. Result: \`$LAST_RESULT_JSON\`" )
xmodel_check_criticals "$nm" "$LAST_RESULT_JSON"
fi
done < <(echo "$PENDING_JSON" | jq -r '.[]')
fi
# Persist rotation state.
jq -n --arg cs "$CYCLE_START" --argjson sc "$SCANNED_JSON" '{cycle_start:$cs, scanned:$sc}' > "$ROTATION_STATE"
# Coverage accounting + lag alarm.
REMAINING="$(jq -n --argjson all "$SCANNABLE_JSON" --argjson sc "$SCANNED_JSON" '($all - $sc) | length')"
CYCLE_AGE=$(( ( $(to_epoch "$UTC_DATE") - $(to_epoch "$CYCLE_START") ) / 86400 ))
log "agentic rotation: scanned $AGENTIC_DONE this night, $REMAINING still pending in cycle (started $CYCLE_START, age ${CYCLE_AGE}d)"
if [ "$REMAINING" -gt 0 ] && [ "$CYCLE_AGE" -ge "$MAX_CYCLE_NIGHTS" ]; then
ALARM_LINES+=( "*COVERAGE ALARM*: agentic rotation behind — $REMAINING repo(s) not deep-scanned in ${CYCLE_AGE}d (cycle since $CYCLE_START, max $MAX_CYCLE_NIGHTS). Raise budget or check for failures." )
fi
# Fold xmodel disagreements into the alarm set.
for x in ${XMODEL_LINES[@]+"${XMODEL_LINES[@]}"}; do ALARM_LINES+=( "$x" ); done
# ============================== 5) ALARM-ONLY REPORT =========================
ALARM=0
[ "${#BLOCKED_T1[@]}" -gt 0 ] && ALARM=1
[ "${#BLOCKED_T2[@]}" -gt 0 ] && ALARM=1
[ "$CANARY_OK" -ne 1 ] && ALARM=1
[ "$BUDGET_HIT" -eq 1 ] && ALARM=1
[ "${#ALARM_LINES[@]}" -gt 0 ] && ALARM=1
SUMMARY_LINE="sweep $UTC_STAMP: scannable=${#SCANNABLE[@]} tier1_blocked=${#BLOCKED_T1[@]} tier2_scanned=$AGENTIC_DONE tier2_blocked=${#BLOCKED_T2[@]} canary_ok=$CANARY_OK spend=\$$TOTAL_SPEND/\$$TOTAL_BUDGET_USD alarm=$ALARM report=$REPORT_DIR"
echo "$SUMMARY_LINE"
if [ "$ALARM" -ne 1 ]; then
log "clean night — no alarm conditions. Posting NOTHING to Slack (ALARM-only policy)."
exit 0
fi
ALARM_BODY="$(printf '%s\n' ${ALARM_LINES[@]+"${ALARM_LINES[@]}"} | sed 's/^/• /')"
SLACK_TEXT=":rotating_light: *Sea Haven nightly security sweep — ALARM* ($UTC_STAMP)
$ALARM_BODY
Coverage: tier1 scanners ${#SCANNABLE[@]} repos · tier2 agentic $AGENTIC_DONE this night ($REMAINING pending) · canary_ok=$CANARY_OK
Spend: \$$TOTAL_SPEND (ceiling \$$TOTAL_BUDGET_USD)
feat: re-home cross-family reviewer CLI, retire Path B host artifacts (#5) * feat: add router-less cross-family reviewer CLI (cross_review.py) Re-homes the archived orchestrator repo's GPT-4.1 cross_reviewer as a direct OpenAI SDK CLI: verbatim system prompt, same model id, ported 3-attempt exponential-backoff retry. Reads OPENAI_API_KEY from the environment or the gitignored repo-root .env. Lazy openai import so --help works without the package. * feat: create machine-level suppressions dir on --global hook install install-hooks.sh --global now mkdir -p's ${SH_SECURITY_SUPPRESSIONS_DIR:-~/.config/sea-haven/security-review} and states the convention: machine-level <dir>/<repo-basename>/suppressions.json is preferred over repo-local .security-review/suppressions.json, with review.sh merging both when run without --suppressions. * docs: describe sh-build-review as cross-family GPT-4.1 pass via cross_review.py * chore: retire Path B VM host artifacts, repoint xmodel hook to cross_review.py - delete systemd/ units and DEPLOY-R720.md (VM destroyed; recoverable from git history) - nightly_sweep.sh: retained for reference — header notes the VM-based Path B sweep is retired; ENABLE_XMODEL_HOOK now calls this repo's cross_review.py instead of the archived orchestrator's run.py - sweep-targets.txt: drop the stale ~/orchestrator warning block - README: document the Claude Code web cloud routines (repo-scanner-nightly-sweep 08:00 ET, repo-checkers-plane1 07:30 ET, ALARM-only to #repo-scanner) and add the cross_review.py section * docs(iam): repoint cross-family review invocations to cross_review.py * fix(ci): exclude canary corpus from ruff, add import smoke test CI has been red repo-wide: the latest ruff wants to reformat the intentionally-vulnerable canary fixtures (whose line numbers are keyed in canary-meta/KEY.md), and pytest --collect-only exits 5 with zero tests. Exclude canary/ via ruff.toml and add a root-level import smoke test so collection is non-empty and imports cross_review.py.
2026-07-14 19:24:01 -04:00
Reports + JSON: \`$REPORT_DIR\`"
SLACK_TEXT="$(echo "$SLACK_TEXT" | redact)"
log "ALARM conditions present — composing Slack post"
echo "$SLACK_TEXT" >&2
post_slack_alarm "$SLACK_TEXT"
# An alarm is a reportable condition, not a script crash. Exit 0 so systemd shows success.
exit 0