From c62ff092b77e3e035ce05f6f7151df6006ea7286 Mon Sep 17 00:00:00 2001 From: Vault Sovereign Date: Sat, 27 Dec 2025 01:06:04 +0000 Subject: [PATCH] feat: pin constitution hash and manifest evidence --- 20-collectors/collect_ledger_verify.sh | 12 +- 40-rules/governance_constitution_pinned.sh | 6 + README.md | 7 +- config/pins.yaml | 2 + config/sources.yaml | 3 +- scripts/vmcc | 129 +++++++++++++++------ 6 files changed, 117 insertions(+), 42 deletions(-) create mode 100644 config/pins.yaml diff --git a/20-collectors/collect_ledger_verify.sh b/20-collectors/collect_ledger_verify.sh index 51947bf..82ca0be 100755 --- a/20-collectors/collect_ledger_verify.sh +++ b/20-collectors/collect_ledger_verify.sh @@ -5,9 +5,17 @@ OUT_DIR="${1:?usage: collect_ledger_verify.sh }" mkdir -p "$OUT_DIR" if command -v ledger >/dev/null 2>&1; then - ledger verify --format json > "$OUT_DIR/ledger_verify.json" + if ! ledger verify --format json > "$OUT_DIR/ledger_verify.json"; then + cat > "$OUT_DIR/ledger_verify.json" <<'JSON' +{"collected": false, "reason": "ledger verify failed"} +JSON + fi elif command -v ledger-cli >/dev/null 2>&1; then - ledger-cli verify --format json > "$OUT_DIR/ledger_verify.json" + if ! ledger-cli verify --format json > "$OUT_DIR/ledger_verify.json"; then + cat > "$OUT_DIR/ledger_verify.json" <<'JSON' +{"collected": false, "reason": "ledger-cli verify failed"} +JSON + fi else cat > "$OUT_DIR/ledger_verify.json" <<'JSON' {"collected": false, "reason": "ledger CLI not found"} diff --git a/40-rules/governance_constitution_pinned.sh b/40-rules/governance_constitution_pinned.sh index 6cee97d..86e0463 100755 --- a/40-rules/governance_constitution_pinned.sh +++ b/40-rules/governance_constitution_pinned.sh @@ -7,8 +7,14 @@ EVID_DIR="${1:?usage: governance_constitution_pinned.sh }" TS="$(iso_utc_now)" FILE="$EVID_DIR/constitution_hash.json" +ROOT="$(vmcc_root)" +PIN_FILE="${VMCC_PINS_FILE:-$ROOT/config/pins.yaml}" PINNED_SHA256="${VMCC_PINNED_CONSTITUTION_SHA256:-}" +if [[ -z "$PINNED_SHA256" && -f "$PIN_FILE" ]]; then + PINNED_SHA256="$(awk -F': *' '/^constitution_sha256:/ {print $2}' "$PIN_FILE" | tr -d '"' | tr -d "'" | head -n 1)" +fi + if [[ ! -f "$FILE" ]]; then json_emit "$(jq -n --arg ts "$TS" '{ version:"1.0.0", diff --git a/README.md b/README.md index 2b65f6d..2249557 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ vm-cc is the continuous compliance and evidence orchestration layer. It ingests ## Layout - config/: source/rule/redaction/schedule configs - schemas/: JSON/YAML schemas for evidence, rules, reports -- 00-frameworks/: frameworks and mappings (e.g., CIS→rules) +- 00-frameworks/: frameworks and mappings (e.g., CIS->rules) - 10-controls/: control definitions - 20-collectors/: collectors to pull evidence from vm-skills, ops, ledger, mcp - 30-evidence/: raw evidence drops (per-run folders) @@ -19,7 +19,7 @@ vm-cc is the continuous compliance and evidence orchestration layer. It ingests - 70-violations/: findings and escalations - 80-remediation/: playbooks/automation for fixes - 90-automation/: pipelines/glue for end-to-end runs -- scripts/: thin CLI wrappers to orchestrate collect → evaluate → report → sign +- scripts/: thin CLI wrappers to orchestrate collect -> evaluate -> report -> sign ## Run directories Each execution writes to a per-run folder set: @@ -45,6 +45,7 @@ RUN_ID format: `YYYYMMDDThhmmssZ_`. "sha256": "..." } ], - "details": { "entries_checked": 18231 } + "details": { "entries_checked": 18231 }, + "remediation": null } ``` diff --git a/config/pins.yaml b/config/pins.yaml new file mode 100644 index 0000000..87a1fab --- /dev/null +++ b/config/pins.yaml @@ -0,0 +1,2 @@ +version: "1.0.0" +constitution_sha256: "" diff --git a/config/sources.yaml b/config/sources.yaml index 21f7229..9bb6a6d 100644 --- a/config/sources.yaml +++ b/config/sources.yaml @@ -20,14 +20,13 @@ vm_ledger: path: "../vm-ledger" evidence: - "log/entries.cborseq" - - "ledger-cli verify --format json" # command invocation placeholder vm_mcp: path: "../vm-mcp" evidence: - "governance/constitution.lock" -vm_contracts: +contracts: path: "../vm-contracts" evidence: - "receipt_v1.schema.json" diff --git a/scripts/vmcc b/scripts/vmcc index ecc25e8..2d68fd2 100755 --- a/scripts/vmcc +++ b/scripts/vmcc @@ -14,51 +14,110 @@ REP_DIR="$ROOT/50-reports/$DAY/$RUN_ID" mkdir -p "$EVID_DIR" "$RULE_DIR" "$REP_DIR" +hash_file() { + local file="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file" | awk '{print $1}' + else + shasum -a 256 "$file" | awk '{print $1}' + fi +} + +write_manifest() { + local manifest="$EVID_DIR/manifest.json" + local ts + ts="$(iso_utc_now)" + { + echo "{" + echo " \"version\": \"1.0.0\"," + echo " \"collected_at\": \"${ts}\"," + echo " \"run_id\": \"${RUN_ID}\"," + echo " \"files\": [" + local first=1 + while IFS= read -r file; do + local rel + rel="${file#$ROOT/}" + local sha + sha="$(hash_file "$file")" + if [[ $first -eq 0 ]]; then + echo " ," + fi + first=0 + echo " {\"path\": \"${rel}\", \"sha256\": \"${sha}\"}" + done < <(find "$EVID_DIR" -type f ! -name "manifest.json" | sort) + echo " ]" + echo "}" + } > "$manifest" +} + +run_collect() { + echo "[vmcc] run_id=$RUN_ID" + echo "[vmcc] collecting evidence -> $EVID_DIR" + "$ROOT/20-collectors/collect_ledger_verify.sh" "$EVID_DIR" + "$ROOT/20-collectors/collect_constitution_hash.sh" "$EVID_DIR" + "$ROOT/20-collectors/collect_backup_restore_drill.sh" "$EVID_DIR" + write_manifest +} + +run_evaluate() { + echo "[vmcc] evaluating rules -> $RULE_DIR" + "$ROOT/40-rules/ledger_hash_chain_intact.sh" "$EVID_DIR" > "$RULE_DIR/ledger.hash_chain_intact.json" + "$ROOT/40-rules/governance_constitution_pinned.sh" "$EVID_DIR" > "$RULE_DIR/governance.constitution_pinned.json" + "$ROOT/40-rules/backup_restore_drill_recent.sh" "$EVID_DIR" > "$RULE_DIR/backup.restore_drill_recent.json" +} + +run_report() { + require_cmd jq + echo "[vmcc] assembling report -> $REP_DIR/report.json" + TS="$(iso_utc_now)" + PASSED_COUNT="$(jq -s '[.[] | select(.passed==true)] | length' "$RULE_DIR"/*.json)" + FAILED_COUNT="$(jq -s '[.[] | select(.passed==false)] | length' "$RULE_DIR"/*.json)" + + jq -n \ + --arg version "1.0.0" \ + --arg timestamp "$TS" \ + --arg run_id "$RUN_ID" \ + --arg day "$DAY" \ + --slurpfile rules <(cat "$RULE_DIR"/*.json) \ + --argjson passed "$PASSED_COUNT" \ + --argjson failed "$FAILED_COUNT" \ + '{ + version: $version, + timestamp: $timestamp, + period: "run", + run_id: $run_id, + day: $day, + summary: { + rules_passed: $passed, + rules_failed: $failed, + status: (if $failed == 0 then "COMPLIANT" else "NONCOMPLIANT" end) + }, + rules: $rules + }' > "$REP_DIR/report.json" +} + case "$CMD" in collect) - echo "[vmcc] run_id=$RUN_ID" - echo "[vmcc] collecting evidence -> $EVID_DIR" - "$ROOT/20-collectors/collect_ledger_verify.sh" "$EVID_DIR" - "$ROOT/20-collectors/collect_constitution_hash.sh" "$EVID_DIR" - "$ROOT/20-collectors/collect_backup_restore_drill.sh" "$EVID_DIR" + run_collect ;; evaluate) - echo "[vmcc] evaluating rules -> $RULE_DIR" - "$ROOT/40-rules/ledger_hash_chain_intact.sh" "$EVID_DIR" > "$RULE_DIR/ledger.hash_chain_intact.json" - "$ROOT/40-rules/governance_constitution_pinned.sh" "$EVID_DIR" > "$RULE_DIR/governance.constitution_pinned.json" - "$ROOT/40-rules/backup_restore_drill_recent.sh" "$EVID_DIR" > "$RULE_DIR/backup.restore_drill_recent.json" + run_evaluate ;; report) + run_report + ;; + all) + run_collect + run_evaluate + run_report require_cmd jq - echo "[vmcc] assembling report -> $REP_DIR/report.json" - TS="$(iso_utc_now)" - PASSED_COUNT="$(jq -s '[.[] | select(.passed==true)] | length' "$RULE_DIR"/*.json)" FAILED_COUNT="$(jq -s '[.[] | select(.passed==false)] | length' "$RULE_DIR"/*.json)" - - jq -n \ - --arg version "1.0.0" \ - --arg timestamp "$TS" \ - --arg run_id "$RUN_ID" \ - --arg day "$DAY" \ - --slurpfile rules <(cat "$RULE_DIR"/*.json) \ - --argjson passed "$PASSED_COUNT" \ - --argjson failed "$FAILED_COUNT" \ - '{ - version: $version, - timestamp: $timestamp, - period: "run", - run_id: $run_id, - day: $day, - summary: { - rules_passed: $passed, - rules_failed: $failed, - status: (if $failed == 0 then "COMPLIANT" else "NONCOMPLIANT" end) - }, - rules: $rules - }' > "$REP_DIR/report.json" + if [[ "$FAILED_COUNT" -ne 0 ]]; then + exit 3 + fi ;; *) - echo "Usage: $0 {collect|evaluate|report}" >&2 + echo "Usage: $0 {collect|evaluate|report|all}" >&2 exit 1 ;; esac