From dd238e52f4941faca2c8e58699a8d5a57dd044d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 11 May 2026 00:57:03 +0300 Subject: [PATCH] =?UTF-8?q?feat(scripts):=20verify.sh=20=E2=80=94=20.brain?= =?UTF-8?q?-version=20+=20sha256=20+=20strict-secrets=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/tests/verify-test.sh | 30 ++++++++++++++ scripts/verify.sh | 79 ++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 scripts/tests/verify-test.sh create mode 100644 scripts/verify.sh diff --git a/scripts/tests/verify-test.sh b/scripts/tests/verify-test.sh new file mode 100644 index 0000000..cc84c83 --- /dev/null +++ b/scripts/tests/verify-test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -u +SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +source "$SCRIPT_DIR/lib/common.sh" + +FAILURES=0 +assert_eq() { + if [ "$1" = "$2" ]; then echo "PASS: $3"; else echo "FAIL: $3 (expected '$1', got '$2')"; FAILURES=$((FAILURES + 1)); fi +} + +# Test 1: missing .brain-version → exit 1 +tmpdir=$(mktemp -d) +bash "$SCRIPT_DIR/verify.sh" --target="$tmpdir" >/dev/null 2>&1 +assert_eq "1" "$?" "missing .brain-version returns exit 1" +rm -rf "$tmpdir" + +# Test 2: valid .brain-version → exit 0 (минимальный manifest пустой) +tmpdir=$(mktemp -d) +echo "brain-v1.0" > "$tmpdir/.brain-version" +echo "sha: 0000000000000000000000000000000000000000" >> "$tmpdir/.brain-version" +# Создаём минимальный manifest в brain +brain_dir=$(mktemp -d) +echo '{"version": "brain-v1.0", "files": {}}' > "$brain_dir/manifest.json" +BRAIN_ROOT="$brain_dir" bash "$SCRIPT_DIR/verify.sh" --target="$tmpdir" >/dev/null 2>&1 +assert_eq "0" "$?" "valid .brain-version returns exit 0" +rm -rf "$tmpdir" "$brain_dir" + +echo "---" +echo "Failures: $FAILURES" +exit $FAILURES diff --git a/scripts/verify.sh b/scripts/verify.sh new file mode 100644 index 0000000..007591a --- /dev/null +++ b/scripts/verify.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Verify brain installation integrity +# Checks: .brain-version, sha256 from manifest.json, settings.json validity, hooks syntax +# +# Usage: verify.sh --target= [--strict-secrets] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/lib/common.sh" + +# Determine brain root: env BRAIN_ROOT or script's parent +BRAIN_ROOT="${BRAIN_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}" + +target="" +strict_secrets=0 + +while [ $# -gt 0 ]; do + case "$1" in + --target=*) target="${1#--target=}" ;; + --strict-secrets) strict_secrets=1 ;; + *) log_error "Unknown: $1"; exit 1 ;; + esac + shift +done + +[ -n "$target" ] || { log_error "--target required"; exit 1; } + +# Check 1: .brain-version exists +bv="$target/.brain-version" +[ -f "$bv" ] || { log_error ".brain-version not found in $target"; exit 1; } + +# Parse version + sha +version=$(head -1 "$bv") +sha_line=$(grep '^sha:' "$bv" || echo "") +[ -n "$version" ] || { log_error ".brain-version: missing version line"; exit 1; } + +log_info "Found .brain-version: $version" + +# Check 2: manifest.json exists in brain root +manifest="$BRAIN_ROOT/manifest.json" +[ -f "$manifest" ] || { log_error "manifest.json missing in BRAIN_ROOT=$BRAIN_ROOT"; exit 1; } + +# Check 3: sha256 per file in manifest (если manifest содержит files) +files_count=$(jq -r '(.files // {}) | length' "$manifest") +if [ "$files_count" -gt 0 ]; then + log_info "Verifying $files_count file hashes..." + while IFS= read -r entry; do + rel_path=$(echo "$entry" | jq -r '.key') + expected_sha=$(echo "$entry" | jq -r '.value') + actual_path="$target/$rel_path" + + if [ ! -f "$actual_path" ]; then + log_error "Missing: $rel_path" + exit 2 + fi + + if command -v sha256sum >/dev/null 2>&1; then + actual_sha=$(sha256sum "$actual_path" | cut -d' ' -f1) + else + actual_sha=$(certutil -hashfile "$actual_path" SHA256 | sed -n '2p' | tr -d ' \r') + fi + + if [ "$actual_sha" != "$expected_sha" ]; then + log_error "SHA mismatch: $rel_path (expected $expected_sha, got $actual_sha)" + exit 2 + fi + done < <(jq -c '(.files // {}) | to_entries[]' "$manifest") +fi + +# Check 4: strict secrets — no unresolved <<*>> placeholders +if [ "$strict_secrets" -eq 1 ]; then + if grep -rE '<<[A-Z_]+>>' "$target" 2>/dev/null | grep -v '.brain-backup' | head -1; then + log_error "Unresolved placeholders found" + exit 7 + fi +fi + +log_info "Verification passed" +exit 0