From 1a1facd945359c09fce51309f78cc322c1ee7ca0 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Thu, 18 Jun 2026 14:06:31 -0400 Subject: [PATCH 1/2] refactor(secrev): factor shared sweep substrate out of nightly_sweep.sh (Plane-1 Phase 0) Extract discovery/mirror/budget-ledger/rotation/Slack-ALARM(redaction)/canary into security-review/lib/sweep_substrate.sh (sourceable, bash, stdlib only); nightly_sweep.sh (415->362) now sources it. Zero behavior change proven: shellcheck -x clean, offline two-tier dry-run byte-identical before/after, token discipline (read-only PAT, REST-only, no gh CLI, origin scrubbed) preserved, review.sh untouched. Revert point: 73e35f3. Foundation both planes' scheduled side reuses. --- security-review/lib/sweep_substrate.sh | 126 +++++++++++++++++++++++++ security-review/nightly_sweep.sh | 81 +++------------- 2 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 security-review/lib/sweep_substrate.sh diff --git a/security-review/lib/sweep_substrate.sh b/security-review/lib/sweep_substrate.sh new file mode 100644 index 0000000..f8883d8 --- /dev/null +++ b/security-review/lib/sweep_substrate.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# sweep_substrate.sh - shared, sourceable substrate for Sea Haven R720 sweeps. +# +# This module factors the reusable concerns out of nightly_sweep.sh (the LIVE sh-secrev +# Path B nightly sweep) so both secrev and the R720 agent-team (a separate track) can +# reuse one implementation. See docs/r720-agent-team-design.md section 7 Phase 0. +# +# Design contract (IMPORTANT - read before editing): +# These functions are extracted VERBATIM from nightly_sweep.sh. They preserve secrev +# behavior exactly. Bash uses dynamic scoping, so a function sourced here closes over +# the CALLER'S variables by name. Each function below documents which caller globals +# it reads or mutates. Callers MUST provide those globals (the names are part of the +# contract); this keeps the extraction zero-behavior-change versus the old inline copy. +# +# bash, stdlib/coreutils only (jq, curl, git, sed, date). No new dependencies. +# +# Usage: +# source "/lib/sweep_substrate.sh" +# ... then call the functions exactly as the inline versions were called. +# +# Functions (each small + individually testable): +# --- budget ledger --- +# add_spend AMOUNT accumulate agentic spend into TOTAL_SPEND (jq exact-add) +# over_budget true if TOTAL_SPEND >= TOTAL_BUDGET_USD (and ceiling > 0) +# --- Slack ALARM-only reporting (with secret redaction) --- +# redact stdin->stdout: mask AWS/GitHub/Slack/high-entropy secrets +# post_slack_alarm TEXT POST the alarm to SLACK_WEBHOOK_URL, or log-only if unset +# --- discovery (org enumeration via REST + GH_TOKEN, no gh CLI) --- +# discover_repos emit "nameclone_urldefault_branch" per non-archived repo +# --- mirror (clean shallow clone; token never persisted to .git/config) --- +# mirror_repo NAME URL BRANCH mirror one repo into MIRROR_DIR/NAME (0 ok / 1 fail) +# --- round-robin rotation (persistent cycle pointer) --- +# to_epoch DATE UTC date string -> epoch seconds (GNU or BSD date) +# --- canary / testbed gate --- +# canary_confirmed_count JSON count confirmed crit+high findings in a review.sh result JSON + +# --- budget ledger ------------------------------------------------------------ +# Reads/mutates caller globals: TOTAL_SPEND. Reads: TOTAL_BUDGET_USD. +add_spend() { TOTAL_SPEND="$(jq -n --argjson a "$TOTAL_SPEND" --argjson b "${1:-0}" '$a + $b')"; } +over_budget() { jq -n --argjson s "$TOTAL_SPEND" --argjson c "$TOTAL_BUDGET_USD" -e '$c > 0 and $s >= $c' >/dev/null; } + +# --- Secret redaction for the Slack string (defense-in-depth; reports stay on the VM) -- +redact() { + sed -E \ + -e 's/AKIA[0-9A-Z]{16}/AKIA****REDACTED****/g' \ + -e 's/gh[pousr]_[A-Za-z0-9]{20,}/gh*_****REDACTED****/g' \ + -e 's/(xox[baprs]-)[A-Za-z0-9-]{10,}/\1****REDACTED****/g' \ + -e 's/[A-Za-z0-9/+]{40,}/****REDACTED-HIENTROPY****/g' +} + +# --- Slack ALARM-only delivery ------------------------------------------------- +# Posts the (already-redacted, already-composed) alarm text. If SLACK_WEBHOOK_URL is +# unset or curl is missing, logs only; the alarm text remains in the sweep log. +# Reads caller globals: SLACK_WEBHOOK_URL, REPORT_DIR, SWEEP_LOG. Uses log() from caller. +post_slack_alarm() { # alarm_text + local slack_text="$1" + if [ -n "${SLACK_WEBHOOK_URL:-}" ] && command -v curl >/dev/null; then + local payload; payload="$(jq -n --arg t "$slack_text" '{text:$t}')" + if curl -fsS -X POST -H 'Content-Type: application/json' --data "$payload" "$SLACK_WEBHOOK_URL" >/dev/null 2>>"$REPORT_DIR/slack.log"; then + log "Slack alarm posted." + else + log "Slack POST FAILED — see $REPORT_DIR/slack.log. Alarm text is in $SWEEP_LOG." + fi + else + log "SLACK_WEBHOOK_URL unset (or curl missing) — alarm logged to $SWEEP_LOG only." + fi +} + +# --- Discovery: enumerate non-archived org repos via the REST API ------------- +# Emits "nameclone_urldefault_branch" per repo. Returns non-zero on failure. +# Reads caller globals: GH_TOKEN, GH_ORG, REPORT_DIR. +discover_repos() { + [ -n "${GH_TOKEN:-}" ] || { log "GH_TOKEN unset — cannot enumerate org"; return 1; } + local page=1 got body + while :; do + body="$(curl -fsS \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/orgs/$GH_ORG/repos?per_page=100&type=all&page=$page" 2>>"$REPORT_DIR/discover.log")" || return 1 + echo "$body" | jq -e 'type=="array"' >/dev/null 2>&1 || return 1 + got="$(echo "$body" | jq -r '[.[] | select(.archived==false)] | .[] | [.name, .clone_url, .default_branch] | @tsv')" + [ -n "$got" ] && echo "$got" + [ "$(echo "$body" | jq 'length')" -lt 100 ] && break + page=$((page+1)) + done + return 0 +} + +# --- Mirror one repo as a shallow clean clone ------------------------------------------- +# The token is NEVER persisted to .git/config: the fetch path passes the auth URL inline +# (transient, command-args only), and the clone path scrubs origin immediately after. So a +# failed fetch cannot leave GH_TOKEN at rest on disk. (Residual: the token is briefly visible +# in process args to a local `ps`; acceptable on this single-user unattended box.) +# Reads caller globals: MIRROR_DIR, GH_TOKEN. +mirror_repo() { # name clone_url default_branch -> 0 ok / 1 fail + local name="$1" url="$2" branch="$3" + local dir="$MIRROR_DIR/$name" + local auth_url="https://x-access-token:${GH_TOKEN}@${url#https://}" + if [ -d "$dir/.git" ]; then + git -C "$dir" fetch --depth=1 "$auth_url" "$branch" >/dev/null 2>&1 || return 1 + git -C "$dir" reset --hard FETCH_HEAD >/dev/null 2>&1 || return 1 + git -C "$dir" clean -fdq >/dev/null 2>&1 || true + else + git clone --depth=1 --branch "$branch" "$auth_url" "$dir" >/dev/null 2>&1 || return 1 + git -C "$dir" remote set-url origin "$url" >/dev/null 2>&1 || true # clone wrote auth URL → scrub it + fi + return 0 +} + +# --- Rotation helper: portable UTC date-string -> epoch ------------------------ +# Used by the round-robin rotation cycle-age accounting. GNU date (Linux/VM) and BSD +# date (macOS) both handled; unparseable -> 0. +to_epoch() { date -u -d "$1" +%s 2>/dev/null || date -u -j -f '%Y-%m-%d' "$1" +%s 2>/dev/null || echo 0; } + +# --- Canary / testbed gate helper --------------------------------------------- +# Count confirmed crit+high findings in a review.sh result JSON (the anti-complacency +# recall measure). Prints 0 if the file is missing/unreadable. +canary_confirmed_count() { # result_json + local result_json="$1" + if [ -n "$result_json" ] && [ -f "$result_json" ]; then + jq -r '[.findings[]? | select(.status=="confirmed" and (.severity|IN("critical","high")))] | length' "$result_json" 2>/dev/null || echo 0 + else + echo 0 + fi +} diff --git a/security-review/nightly_sweep.sh b/security-review/nightly_sweep.sh index 5a4634d..526efec 100755 --- a/security-review/nightly_sweep.sh +++ b/security-review/nightly_sweep.sh @@ -59,8 +59,15 @@ 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; } -# --- Config + defaults -------------------------------------------------------- +# --- Shared substrate (discovery / mirror / budget / rotation / Slack / canary) - +# Factored out so secrev and the R720 agent-team reuse one implementation, WITHOUT +# changing any secrev 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 -------------------------------------------------------- ORCHESTRATOR_DIR="${ORCHESTRATOR_DIR:-$HOME/orchestrator}" VENV_PY="${VENV_PY:-$ORCHESTRATOR_DIR/.venv/bin/python}" RUN_HEADLESS="$HERE/run_headless.py" @@ -100,57 +107,10 @@ log "org=$GH_ORG mirror=$MIRROR_DIR report=$REPORT_DIR total-budget=\$$TOTAL_BUD # --- Aggregate state ---------------------------------------------------------- TOTAL_SPEND="0"; BUDGET_HIT=0 declare -a ALARM_LINES=(); declare -a XMODEL_LINES=(); declare -a MARKER_SKIPS=() -add_spend() { TOTAL_SPEND="$(jq -n --argjson a "$TOTAL_SPEND" --argjson b "${1:-0}" '$a + $b')"; } -over_budget() { jq -n --argjson s "$TOTAL_SPEND" --argjson c "$TOTAL_BUDGET_USD" -e '$c > 0 and $s >= $c' >/dev/null; } - -# --- Secret redaction for the Slack string (defense-in-depth; reports stay on the VM) -- -redact() { - sed -E \ - -e 's/AKIA[0-9A-Z]{16}/AKIA****REDACTED****/g' \ - -e 's/gh[pousr]_[A-Za-z0-9]{20,}/gh*_****REDACTED****/g' \ - -e 's/(xox[baprs]-)[A-Za-z0-9-]{10,}/\1****REDACTED****/g' \ - -e 's/[A-Za-z0-9/+]{40,}/****REDACTED-HIENTROPY****/g' -} - -# --- Discovery: enumerate non-archived org repos via the REST API ------------- -# Emits "nameclone_urldefault_branch" per repo. Returns non-zero on failure. -discover_repos() { - [ -n "${GH_TOKEN:-}" ] || { log "GH_TOKEN unset — cannot enumerate org"; return 1; } - local page=1 got body - while :; do - body="$(curl -fsS \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/orgs/$GH_ORG/repos?per_page=100&type=all&page=$page" 2>>"$REPORT_DIR/discover.log")" || return 1 - echo "$body" | jq -e 'type=="array"' >/dev/null 2>&1 || return 1 - got="$(echo "$body" | jq -r '[.[] | select(.archived==false)] | .[] | [.name, .clone_url, .default_branch] | @tsv')" - [ -n "$got" ] && echo "$got" - [ "$(echo "$body" | jq 'length')" -lt 100 ] && break - page=$((page+1)) - done - return 0 -} - -# --- Mirror one repo as a shallow clean clone ------------------------------------------- -# The token is NEVER persisted to .git/config: the fetch path passes the auth URL inline -# (transient, command-args only), and the clone path scrubs origin immediately after. So a -# failed fetch cannot leave GH_TOKEN at rest on disk. (Residual: the token is briefly visible -# in process args to a local `ps`; acceptable on this single-user unattended box.) -mirror_repo() { # name clone_url default_branch -> 0 ok / 1 fail - local name="$1" url="$2" branch="$3" - local dir="$MIRROR_DIR/$name" - local auth_url="https://x-access-token:${GH_TOKEN}@${url#https://}" - if [ -d "$dir/.git" ]; then - git -C "$dir" fetch --depth=1 "$auth_url" "$branch" >/dev/null 2>&1 || return 1 - git -C "$dir" reset --hard FETCH_HEAD >/dev/null 2>&1 || return 1 - git -C "$dir" clean -fdq >/dev/null 2>&1 || true - else - git clone --depth=1 --branch "$branch" "$auth_url" "$dir" >/dev/null 2>&1 || return 1 - git -C "$dir" remote set-url origin "$url" >/dev/null 2>&1 || true # clone wrote auth URL → scrub it - fi - return 0 -} +# 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=() @@ -243,10 +203,7 @@ CANARY_OK=1 if [ -d "$TESTBED" ]; then log "--- canary (anti-complacency): $TESTBED ---" scan_agentic "$TESTBED" "canary" - CANARY_CONFIRMED=0 - if [ -n "$LAST_RESULT_JSON" ] && [ -f "$LAST_RESULT_JSON" ]; then - CANARY_CONFIRMED="$(jq -r '[.findings[]? | select(.status=="confirmed" and (.severity|IN("critical","high")))] | length' "$LAST_RESULT_JSON" 2>/dev/null || echo 0)" - fi + 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\`" ) @@ -362,7 +319,6 @@ fi 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')" -to_epoch() { date -u -d "$1" +%s 2>/dev/null || date -u -j -f '%Y-%m-%d' "$1" +%s 2>/dev/null || echo 0; } 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 @@ -400,16 +356,7 @@ SLACK_TEXT="$(echo "$SLACK_TEXT" | redact)" log "ALARM conditions present — composing Slack post" echo "$SLACK_TEXT" >&2 -if [ -n "${SLACK_WEBHOOK_URL:-}" ] && command -v curl >/dev/null; then - PAYLOAD="$(jq -n --arg t "$SLACK_TEXT" '{text:$t}')" - if curl -fsS -X POST -H 'Content-Type: application/json' --data "$PAYLOAD" "$SLACK_WEBHOOK_URL" >/dev/null 2>>"$REPORT_DIR/slack.log"; then - log "Slack alarm posted." - else - log "Slack POST FAILED — see $REPORT_DIR/slack.log. Alarm text is in $SWEEP_LOG." - fi -else - log "SLACK_WEBHOOK_URL unset (or curl missing) — alarm logged to $SWEEP_LOG only." -fi +post_slack_alarm "$SLACK_TEXT" # An alarm is a reportable condition, not a script crash. Exit 0 so systemd shows success. exit 0 -- 2.50.1 From 28553d86f7bccba3aa5437ea2efcaae0cf9d774f Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Thu, 18 Jun 2026 14:06:31 -0400 Subject: [PATCH 2/2] feat(secrev): compliance-drift Plane-1 Tier-1 checker (ALARM-only) Read-only org compliance checker on the shared substrate (no re-clone; scans existing mirrors). Checklist grounded in handbook/github-standards: kebab repo name, README, CI/CD, Dependabot config+alerts, tracked-.env secrets, branch protection, merge settings; handbook exceptions (docs-only, compliance-exempt) honored. Mode-600 reports, ALARM-only (clean=silent). Includes planted-drift canary (asserts 6). Review fixes folded in: branch-protection + dependabot are status-code-aware (only a real 404 is drift; transient API failure -> skip, no false alarm); secrets-committed fires only on secret-shaped values (not benign config). NOT scheduled (provisioning gated). --- security-review/checkers/compliance-drift.sh | 485 ++++++++++++++++++ .../BadName_repo/dotgit/COMMIT_EDITMSG | 1 + .../compliance-drift/BadName_repo/dotgit/HEAD | 1 + .../BadName_repo/dotgit/config | 10 + .../BadName_repo/dotgit/description | 1 + .../dotgit/hooks/applypatch-msg.sample | 15 + .../dotgit/hooks/commit-msg.sample | 24 + .../dotgit/hooks/fsmonitor-watchman.sample | 174 +++++++ .../dotgit/hooks/post-update.sample | 8 + .../dotgit/hooks/pre-applypatch.sample | 14 + .../dotgit/hooks/pre-commit.sample | 49 ++ .../dotgit/hooks/pre-merge-commit.sample | 13 + .../BadName_repo/dotgit/hooks/pre-push.sample | 53 ++ .../dotgit/hooks/pre-rebase.sample | 169 ++++++ .../dotgit/hooks/pre-receive.sample | 24 + .../dotgit/hooks/prepare-commit-msg.sample | 42 ++ .../dotgit/hooks/push-to-checkout.sample | 78 +++ .../dotgit/hooks/sendemail-validate.sample | 77 +++ .../BadName_repo/dotgit/hooks/update.sample | 128 +++++ .../BadName_repo/dotgit/index | Bin 0 -> 217 bytes .../BadName_repo/dotgit/info/exclude | 6 + .../BadName_repo/dotgit/logs/HEAD | 1 + .../BadName_repo/dotgit/logs/refs/heads/main | 1 + .../4d/56164171e7ac47e50f3c6b06091d1f1e4e36a1 | Bin 0 -> 45 bytes .../72/9065c84f82263e16b2ef5241f3d4f0fc0aca45 | 1 + .../bc/8f350556a9ba83a52c0896c69005ec5c872c71 | Bin 0 -> 103 bytes .../e4/f0ea548f86dabe65d0507443f306ac0fdeeaa5 | Bin 0 -> 29 bytes .../BadName_repo/dotgit/refs/heads/main | 1 + .../BadName_repo/package.json | 1 + .../compliance-drift/EXPECTED_DRIFT_COUNT | 1 + .../fixtures/compliance-drift/README.md | 19 + .../clean-repo/.github/dependabot.yml | 1 + .../clean-repo/.github/workflows/ci.yaml | 1 + .../compliance-drift/clean-repo/.gitignore | 2 + .../compliance-drift/clean-repo/README.md | 1 + .../clean-repo/dotgit/COMMIT_EDITMSG | 1 + .../compliance-drift/clean-repo/dotgit/HEAD | 1 + .../compliance-drift/clean-repo/dotgit/config | 10 + .../clean-repo/dotgit/description | 1 + .../dotgit/hooks/applypatch-msg.sample | 15 + .../clean-repo/dotgit/hooks/commit-msg.sample | 24 + .../dotgit/hooks/fsmonitor-watchman.sample | 174 +++++++ .../dotgit/hooks/post-update.sample | 8 + .../dotgit/hooks/pre-applypatch.sample | 14 + .../clean-repo/dotgit/hooks/pre-commit.sample | 49 ++ .../dotgit/hooks/pre-merge-commit.sample | 13 + .../clean-repo/dotgit/hooks/pre-push.sample | 53 ++ .../clean-repo/dotgit/hooks/pre-rebase.sample | 169 ++++++ .../dotgit/hooks/pre-receive.sample | 24 + .../dotgit/hooks/prepare-commit-msg.sample | 42 ++ .../dotgit/hooks/push-to-checkout.sample | 78 +++ .../dotgit/hooks/sendemail-validate.sample | 77 +++ .../clean-repo/dotgit/hooks/update.sample | 128 +++++ .../compliance-drift/clean-repo/dotgit/index | Bin 0 -> 539 bytes .../clean-repo/dotgit/info/exclude | 6 + .../clean-repo/dotgit/logs/HEAD | 1 + .../clean-repo/dotgit/logs/refs/heads/main | 1 + .../22/817d2a9c7fc1f62d5670ca1e44948446543973 | Bin 0 -> 27 bytes .../2f/2f27c44f1ffd173eb9771fab6b77d0ee075bb5 | Bin 0 -> 29 bytes .../58/439813fe88d3d045a3093999f382522a7a7327 | Bin 0 -> 24 bytes .../5d/51e08f85f54ae3c0b94e94e669fb64868538cb | Bin 0 -> 52 bytes .../71/3d5006dabfe2792c6ee1dbb4fdbdff2c44e304 | Bin 0 -> 35 bytes .../72/baacfb9265a352ce186809392c0c849c6220e4 | Bin 0 -> 103 bytes .../ac/9e4bf172ad292a8d19f7acfb7676c3a0d3bfbd | Bin 0 -> 94 bytes .../b9/0fb0f8fa06bca16e51fa38bbd44fc6735d2b6d | Bin 0 -> 38 bytes .../c4/30364d61f3b0be2614d2c76f873edd7547a54a | Bin 0 -> 156 bytes .../clean-repo/dotgit/refs/heads/main | 1 + .../compliance-drift/clean-repo/package.json | 1 + .../docs-repo/dotgit/COMMIT_EDITMSG | 1 + .../compliance-drift/docs-repo/dotgit/HEAD | 1 + .../compliance-drift/docs-repo/dotgit/config | 10 + .../docs-repo/dotgit/description | 1 + .../dotgit/hooks/applypatch-msg.sample | 15 + .../docs-repo/dotgit/hooks/commit-msg.sample | 24 + .../dotgit/hooks/fsmonitor-watchman.sample | 174 +++++++ .../docs-repo/dotgit/hooks/post-update.sample | 8 + .../dotgit/hooks/pre-applypatch.sample | 14 + .../docs-repo/dotgit/hooks/pre-commit.sample | 49 ++ .../dotgit/hooks/pre-merge-commit.sample | 13 + .../docs-repo/dotgit/hooks/pre-push.sample | 53 ++ .../docs-repo/dotgit/hooks/pre-rebase.sample | 169 ++++++ .../docs-repo/dotgit/hooks/pre-receive.sample | 24 + .../dotgit/hooks/prepare-commit-msg.sample | 42 ++ .../dotgit/hooks/push-to-checkout.sample | 78 +++ .../dotgit/hooks/sendemail-validate.sample | 77 +++ .../docs-repo/dotgit/hooks/update.sample | 128 +++++ .../compliance-drift/docs-repo/dotgit/index | Bin 0 -> 145 bytes .../docs-repo/dotgit/info/exclude | 6 + .../docs-repo/dotgit/logs/HEAD | 1 + .../docs-repo/dotgit/logs/refs/heads/main | 1 + .../48/cdce85287243a96a9e7d47855104acbcb79837 | Bin 0 -> 28 bytes .../60/03c9aac8de93dc4777594f2b0d46ee8345ccea | Bin 0 -> 55 bytes .../79/906bf4bbcd829d2a1f611b11b058dd90827217 | Bin 0 -> 101 bytes .../docs-repo/dotgit/refs/heads/main | 1 + .../compliance-drift/docs-repo/index.html | 1 + 95 files changed, 3184 insertions(+) create mode 100755 security-review/checkers/compliance-drift.sh create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/COMMIT_EDITMSG create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/HEAD create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/config create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/description create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/applypatch-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/commit-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/fsmonitor-watchman.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/post-update.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-applypatch.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-commit.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-merge-commit.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-push.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-rebase.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-receive.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/prepare-commit-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/push-to-checkout.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/sendemail-validate.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/update.sample create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/index create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/info/exclude create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/HEAD create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/refs/heads/main create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/4d/56164171e7ac47e50f3c6b06091d1f1e4e36a1 create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/72/9065c84f82263e16b2ef5241f3d4f0fc0aca45 create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/bc/8f350556a9ba83a52c0896c69005ec5c872c71 create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/e4/f0ea548f86dabe65d0507443f306ac0fdeeaa5 create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/refs/heads/main create mode 100644 security-review/checkers/fixtures/compliance-drift/BadName_repo/package.json create mode 100644 security-review/checkers/fixtures/compliance-drift/EXPECTED_DRIFT_COUNT create mode 100644 security-review/checkers/fixtures/compliance-drift/README.md create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/.github/dependabot.yml create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/.github/workflows/ci.yaml create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/.gitignore create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/README.md create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/COMMIT_EDITMSG create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/HEAD create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/config create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/description create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/applypatch-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/commit-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/fsmonitor-watchman.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/post-update.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-applypatch.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-commit.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-merge-commit.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-push.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-rebase.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-receive.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/prepare-commit-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/push-to-checkout.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/sendemail-validate.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/update.sample create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/index create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/info/exclude create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/HEAD create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/refs/heads/main create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/22/817d2a9c7fc1f62d5670ca1e44948446543973 create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/2f/2f27c44f1ffd173eb9771fab6b77d0ee075bb5 create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/58/439813fe88d3d045a3093999f382522a7a7327 create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/5d/51e08f85f54ae3c0b94e94e669fb64868538cb create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/71/3d5006dabfe2792c6ee1dbb4fdbdff2c44e304 create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/72/baacfb9265a352ce186809392c0c849c6220e4 create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/ac/9e4bf172ad292a8d19f7acfb7676c3a0d3bfbd create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/b9/0fb0f8fa06bca16e51fa38bbd44fc6735d2b6d create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/c4/30364d61f3b0be2614d2c76f873edd7547a54a create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/refs/heads/main create mode 100644 security-review/checkers/fixtures/compliance-drift/clean-repo/package.json create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/COMMIT_EDITMSG create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/HEAD create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/config create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/description create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/applypatch-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/commit-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/fsmonitor-watchman.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/post-update.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-applypatch.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-commit.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-merge-commit.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-push.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-rebase.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-receive.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/prepare-commit-msg.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/push-to-checkout.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/sendemail-validate.sample create mode 100755 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/update.sample create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/index create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/info/exclude create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/HEAD create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/refs/heads/main create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/48/cdce85287243a96a9e7d47855104acbcb79837 create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/60/03c9aac8de93dc4777594f2b0d46ee8345ccea create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/79/906bf4bbcd829d2a1f611b11b058dd90827217 create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/refs/heads/main create mode 100644 security-review/checkers/fixtures/compliance-drift/docs-repo/index.html diff --git a/security-review/checkers/compliance-drift.sh b/security-review/checkers/compliance-drift.sh new file mode 100755 index 0000000..d65175d --- /dev/null +++ b/security-review/checkers/compliance-drift.sh @@ -0,0 +1,485 @@ +#!/usr/bin/env bash +# compliance-drift.sh — Plane-1 / Tier-1 checker for the R720 agent-team. +# +# Design refs: docs/r720-agent-team-design.md §4 (Tier 1 roster: compliance-drift) and +# §7 Phase 1 ("one checker end to end"). This is the FIRST Plane-1 checker built on the +# Phase-0 shared substrate (lib/sweep_substrate.sh) — it proves the substrate generalizes +# beyond the secrev nightly sweep. +# +# WHAT IT DOES (read-only): +# Flags drift from Sea Haven engineering conventions across the org mirrors. It scans the +# SAME shallow clean clones that nightly_sweep.sh already produced in $MIRROR_DIR — it does +# NOT re-clone (mirrors-first; an optional --refresh re-runs discovery+mirror via the shared +# substrate). The checklist is GROUNDED in the engineering-handbook + this repo's README; it +# does not invent rules. See "CHECKLIST" below. +# +# REPORTING (matches secrev sweep conventions): +# - Writes a per-run JSON + text report under $REPORT_ROOT//, mode 600 (umask 077). +# - Slack ALARM-ONLY: a clean run (no confirmed drift) posts NOTHING (memory +# feedback_cloudwatch_alarms). Secret-shaped values are redacted from the Slack string. +# - Reuses the substrate's redact() + post_slack_alarm() verbatim. +# +# SUBSTRATE REUSE (lib/sweep_substrate.sh, sourced — bash dynamic scoping): +# redact, post_slack_alarm -> Slack delivery (reads SLACK_WEBHOOK_URL, REPORT_DIR, SWEEP_LOG) +# discover_repos, mirror_repo-> ONLY on --refresh (reads GH_TOKEN, GH_ORG, MIRROR_DIR, REPORT_DIR) +# Default path enumerates EXISTING $MIRROR_DIR/*/.git dirs — zero clones, zero network. +# +# CANARY / DRY-RUN (offline, no network, no token): +# --canary runs the checklist against a planted-drift fixture (checkers/fixtures/compliance-drift/) +# and asserts the known drift count. This is the anti-complacency floor (design §6.4) AND the +# routing dry-run (§7 Phase 1, F4): with --dry-run, the Slack alarm is composed + printed but +# NOT POSTed. Fully offline-smoke-testable. +# +# SCOPE / SAFETY: +# Read-only. Filesystem checks need no network. The branch-protection / Dependabot-alerts / +# repo-settings checks call the GitHub REST API read-only with the same $GH_TOKEN the sweep +# uses (Contents+Metadata read). When GH_TOKEN is unset OR --no-api is passed (the offline +# default for --canary), API-only checks are SKIPPED and noted in the report — they are never +# reported as drift on missing data (memory feedback_cloudwatch_alarms: no false alarms on no-data). +# +# This script does NOT touch agent_team/ or agent-team/, and is NOT wired into systemd — that is +# Phase-6 provisioning (gated). See the "PROVISIONING (NOT DONE HERE)" note at the bottom. +# +# Exit: 0 = ran (whether or not it alarmed); 2 = setup/usage error; 3 = canary assertion FAILED. +set -euo pipefail +export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" + +log() { echo "[compliance-drift] $*" >&2; } +die() { echo "[compliance-drift] FATAL: $*" >&2; exit 2; } + +# --- Shared substrate --------------------------------------------------------- +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SUBSTRATE="$HERE/../lib/sweep_substrate.sh" +[ -f "$SUBSTRATE" ] || die "shared substrate not found: $SUBSTRATE" +# shellcheck source=../lib/sweep_substrate.sh +. "$SUBSTRATE" + +# --- Config + defaults (env, all optional) ------------------------------------ +GH_ORG="${GH_ORG:-Sea-Haven-Industries}" +MIRROR_DIR="${MIRROR_DIR:-$HOME/repo-mirrors}" +REPORT_ROOT="${REPORT_ROOT:-$HOME/sweep-reports/compliance-drift}" +# Repos exempt from CodeQL/compliance tooling per github-standards.md ("Exceptions"). +# Comma-separated; handbook lists shoc-backend, shoc-frontend-new (SHOC-owned) + docs repos. +COMPLIANCE_EXEMPT="${COMPLIANCE_EXEMPT:-shoc-backend,shoc-frontend-new}" +# Docs-only repos skip CodeQL/CI-deploy expectations (handbook exception); they still need README. +DOCS_ONLY_REPOS="${DOCS_ONLY_REPOS:-engineering-handbook}" + +REFRESH=0 # --refresh: re-run discovery+mirror via substrate (network). Default: reuse mirrors. +DO_API=1 # --no-api: skip GitHub-API checks (branch protection / dependabot / settings). +DRY_RUN=0 # --dry-run: compose the Slack alarm but DO NOT post it (routing dry-run, F4). +CANARY=0 # --canary: run against the planted-drift fixture + assert the known count. +TARGETS_OVERRIDE="" # --targets "p1 p2": scan explicit dirs instead of the mirror set. + +usage() { + cat >&2 </dev/null || die "jq is required" +command -v git >/dev/null || die "git is required" + +# --- Report dir (mode 600 reports; matches sweep conventions) ----------------- +umask 077 +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 +# shellcheck disable=SC2034 # read by the sourced substrate (post_slack_alarm) via dynamic scope +SWEEP_LOG="$REPORT_DIR/compliance-drift.log" # name the substrate's post_slack_alarm() references +REPORT_JSON="$REPORT_DIR/compliance-drift.json" +REPORT_TXT="$REPORT_DIR/compliance-drift.txt" + +log "=== compliance-drift $UTC_STAMP (canary=$CANARY dry_run=$DRY_RUN api=$DO_API refresh=$REFRESH) ===" + +# ------------------------------------------------------------------------------ +# CHECKLIST (grounded — every item cites a handbook/README rule; nothing invented): +# +# naming-repo repo dir name is kebab-case naming-conventions.md ("kebab-case for everything") +# readme-present README.md exists at repo root github-standards.md / global CLAUDE.md ("Every repo must have a README") +# cicd-present .github/workflows/ci.yaml|ci.yml cicd.md ("Every deployable repo must have a CI/CD pipeline"; ci.yaml) +# dependabot-config .github/dependabot.yml present when github-standards.md ("Every repo with dependencies gets a .github/dependabot.yml") +# dependency manifests exist +# secrets-committed no committed .env with real-looking secrets-and-config.md ("Never commit .env files containing real values") +# values (tracked-in-git, not gitignored) +# --- API-only (need GH_TOKEN; skipped offline / --no-api / --canary) --- +# branch-protection main requires PR, no force-push, github-standards.md ("Branch Protection") +# no deletion +# dependabot-alerts Dependabot alerts + security updates github-standards.md ("Dependabot alerts and security updates enabled") +# enabled +# merge-settings allow_auto_merge + delete_branch_on_ github-standards.md ("enable auto-merge and auto-delete head branch") +# merge enabled +# +# Each emitted finding follows the spirit of finding.schema.json (id/title/severity/category/ +# proof/status) so a later phase can route it like an agentic finding. category="other" — this is +# convention drift, not the schema's security categories. status="confirmed" only for deterministic +# filesystem facts and explicit API "false" answers; API checks on missing data are NOT findings. +# ------------------------------------------------------------------------------ + +# Drift accumulator: one JSON object per finding, appended to a bash array. +declare -a FINDINGS=() +add_finding() { # repo id title severity check proof + local repo="$1" id="$2" title="$3" sev="$4" check="$5" proof="$6" + FINDINGS+=( "$(jq -n \ + --arg repo "$repo" --arg id "$id" --arg title "$title" --arg sev "$sev" \ + --arg check "$check" --arg proof "$proof" \ + '{repo:$repo, id:($repo+"-"+$id), title:$title, severity:$sev, category:"other", + check:$check, status:"confirmed", proof:{outcome:$proof}}')" ) +} +declare -a SKIPPED_CHECKS=() # (repo:check) checks skipped on missing data — reported, never alarmed +note_skip() { SKIPPED_CHECKS+=( "$1" ); } + +in_csv() { # needle csv -> 0 if present + local n="$1" csv="$2"; case ",$csv," in *",$n,"*) return 0 ;; *) return 1 ;; esac +} + +# --- kebab-case test (lowercase, digits, single hyphens; no leading/trailing hyphen) --- +is_kebab() { [[ "$1" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; } + +# --- Does the repo carry dependency manifests that warrant a dependabot.yml? ---- +has_dep_manifests() { # dir + local d="$1" + # Match handbook's ecosystem table: package.json / requirements.txt / *.csproj. + [ -f "$d/package.json" ] && return 0 + find "$d" -maxdepth 3 -name requirements.txt -not -path '*/.git/*' -print -quit 2>/dev/null | grep -q . && return 0 + find "$d" -maxdepth 3 -name '*.csproj' -not -path '*/.git/*' -print -quit 2>/dev/null | grep -q . && return 0 + return 1 +} + +# ============================================================================== +# FILESYSTEM CHECKS (offline; run on every repo dir) +# ============================================================================== +check_repo_fs() { # repo_name repo_dir + local repo="$1" dir="$2" + local docs_only=0; in_csv "$repo" "$DOCS_ONLY_REPOS" && docs_only=1 + local exempt=0; in_csv "$repo" "$COMPLIANCE_EXEMPT" && exempt=1 + + # naming-repo — repo dir name kebab-case + is_kebab "$repo" || add_finding "$repo" "naming-repo" \ + "Repo name '$repo' is not kebab-case" "medium" "naming-repo" \ + "naming-conventions.md: kebab-case for everything (repository names)" + + # readme-present — every repo, no exceptions + [ -f "$dir/README.md" ] || add_finding "$repo" "readme-missing" \ + "No README.md at repo root" "high" "readme-present" \ + "global CLAUDE.md / github-standards.md: every repo must have a README" + + # cicd-present — ci workflow expected unless docs-only or compliance-exempt + if [ "$docs_only" -eq 0 ] && [ "$exempt" -eq 0 ]; then + if [ ! -f "$dir/.github/workflows/ci.yaml" ] && [ ! -f "$dir/.github/workflows/ci.yml" ]; then + add_finding "$repo" "cicd-missing" \ + "No .github/workflows/ci.yaml" "high" "cicd-present" \ + "cicd.md: every deployable repo must have a CI/CD pipeline (ci.yaml)" + fi + else + note_skip "$repo:cicd-present(docs-only/exempt)" + fi + + # dependabot-config — required only when dependency manifests exist, and not exempt + if [ "$exempt" -eq 0 ] && has_dep_manifests "$dir"; then + [ -f "$dir/.github/dependabot.yml" ] || [ -f "$dir/.github/dependabot.yaml" ] || \ + add_finding "$repo" "dependabot-config-missing" \ + "Has dependency manifests but no .github/dependabot.yml" "medium" "dependabot-config" \ + "github-standards.md: every repo with dependencies gets a .github/dependabot.yml" + fi + + # secrets-committed — a .env TRACKED in git (gitignored .env is fine; tracked is the drift) + if [ -d "$dir/.git" ]; then + while IFS= read -r envf; do + [ -n "$envf" ] || continue + # Only flag .env / .env.* that look like they hold real values, not .env.example/.sample/.template. + case "$envf" in *.example|*.sample|*.template|*.dist) continue ;; esac + # Fire only on secret-SHAPED entries: a secret-ish key name, or a long + # (>=20 char) high-entropy value. Benign config (PORT=3000, DEBUG=true) + # is NOT drift, so a tracked config-only .env raises no ALARM + # (feedback_cloudwatch_alarms: no false alarms on non-secret config). + if grep -qiE '(secret|token|key|password|passwd|api[_-]?key|credential|private)[^=]*=[^[:space:]#]+' "$dir/$envf" 2>/dev/null \ + || grep -qE '=[^[:space:]#]{20,}' "$dir/$envf" 2>/dev/null; then + add_finding "$repo" "secrets-committed-$(echo "$envf" | tr '/.' '--')" \ + "Tracked env file with values committed: $envf" "high" "secrets-committed" \ + "secrets-and-config.md: never commit .env files containing real values" + fi + done < <(git -C "$dir" ls-files -- '*.env' '.env' '.env.*' 2>/dev/null || true) + else + note_skip "$repo:secrets-committed(not-a-git-checkout)" + fi +} + +# ============================================================================== +# API CHECKS (read-only GitHub REST; need GH_TOKEN; skipped offline/--no-api/--canary) +# ============================================================================== +gh_api() { # path -> body on stdout, non-zero on transport/HTTP error + curl -fsS \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/$1" 2>>"$REPORT_DIR/api.log" +} + +check_repo_api() { # repo_name + local repo="$1" + local exempt=0; in_csv "$repo" "$COMPLIANCE_EXEMPT" && exempt=1 + + # repo settings: merge baseline + vulnerability-alerts capability come off the repo object. + local body + if ! body="$(gh_api "repos/$GH_ORG/$repo")" || ! echo "$body" | jq -e 'type=="object" and has("name")' >/dev/null 2>&1; then + note_skip "$repo:api(repo-fetch-failed)"; return + fi + local default_branch; default_branch="$(echo "$body" | jq -r '.default_branch // "main"')" + + # merge-settings — auto-merge + delete-branch-on-merge (per-repo, no org default) + if [ "$exempt" -eq 0 ]; then + local am dbm; am="$(echo "$body" | jq -r '.allow_auto_merge')"; dbm="$(echo "$body" | jq -r '.delete_branch_on_merge')" + [ "$am" = "true" ] || add_finding "$repo" "merge-automerge-off" \ + "allow_auto_merge disabled" "low" "merge-settings" \ + "github-standards.md: enable auto-merge (allow_auto_merge)" + [ "$dbm" = "true" ] || add_finding "$repo" "merge-deletebranch-off" \ + "delete_branch_on_merge disabled" "low" "merge-settings" \ + "github-standards.md: enable auto-delete head branch on merge (delete_branch_on_merge)" + fi + + # dependabot-alerts — vulnerability alerts enabled (204 = enabled, 404 = disabled) + if [ "$exempt" -eq 0 ]; then + local code + # No -f: a 404 (alerts off) is a real HTTP response we must classify, so curl + # must exit 0 and -w must yield a clean "404" (with -f the body-fail path + # corrupts the captured code and a real 404 would be misread as a skip). + code="$(curl -sS -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GH_ORG/$repo/vulnerability-alerts" 2>>"$REPORT_DIR/api.log" || echo 000)" + case "$code" in + 204) : ;; # enabled + 404) add_finding "$repo" "dependabot-alerts-off" \ + "Dependabot vulnerability alerts disabled" "high" "dependabot-alerts" \ + "github-standards.md: Dependabot alerts and security updates enabled on all active repos" ;; + *) note_skip "$repo:dependabot-alerts(http-$code)" ;; # missing data -> no alarm + esac + fi + + # branch-protection — main: require PR, no force-push, no deletion. + # Status-code-aware (mirrors dependabot-alerts): 200 -> parse the rules, + # 404 -> no protection rule = real drift, anything else (403/5xx/000 transient + # or transport failure) -> skip with NO alarm (feedback_cloudwatch_alarms: a + # flaky API call must never raise a high-severity false alarm). + local prot_tmp prot_code prot + prot_tmp="$(mktemp)" + prot_code="$(curl -sS -o "$prot_tmp" -w '%{http_code}' \ + -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GH_ORG/$repo/branches/$default_branch/protection" \ + 2>>"$REPORT_DIR/api.log" || echo 000)" + prot="$(cat "$prot_tmp" 2>/dev/null)"; rm -f "$prot_tmp" + case "$prot_code" in + 200) + echo "$prot" | jq -e '.required_pull_request_reviews != null' >/dev/null 2>&1 || \ + add_finding "$repo" "branchprot-no-pr" \ + "main does not require a PR for merge" "high" "branch-protection" \ + "github-standards.md: require a PR for merges to main (no direct push)" + echo "$prot" | jq -e '.allow_force_pushes.enabled == false' >/dev/null 2>&1 || \ + add_finding "$repo" "branchprot-force-push" \ + "main allows force-push" "high" "branch-protection" \ + "github-standards.md: no force push to main" + echo "$prot" | jq -e '.allow_deletions.enabled == false' >/dev/null 2>&1 || \ + add_finding "$repo" "branchprot-deletion" \ + "main allows branch deletion" "high" "branch-protection" \ + "github-standards.md: no branch deletion for main" + ;; + 404) + # 404 from this endpoint = no protection rule at all on the default branch -> that IS drift. + add_finding "$repo" "branchprot-absent" \ + "No branch protection on '$default_branch'" "high" "branch-protection" \ + "github-standards.md: require a PR for merges to main, no force push, no deletion" + ;; + *) note_skip "$repo:branch-protection(http-$prot_code)" ;; # transient/forbidden -> no alarm + esac +} + +# ============================================================================== +# TARGET RESOLUTION +# ============================================================================== +declare -a REPO_NAMES=(); declare -A REPO_DIR=() + +if [ "$CANARY" -eq 1 ]; then + FIXTURE_ROOT="$HERE/fixtures/compliance-drift" + [ -d "$FIXTURE_ROOT" ] || die "canary fixture missing: $FIXTURE_ROOT" + # Pin the exception lists the fixtures were authored against, so the canary is + # self-contained and deterministic regardless of the operator's env. + DOCS_ONLY_REPOS="docs-repo" + COMPLIANCE_EXEMPT="" + # Fixtures ship their git metadata as `dotgit/` (not `.git/`) so they are committable + # into THIS repo without becoming nested submodules. Materialize them into a temp work + # area — copy each fixture and rename dotgit -> .git — so the tracked-`.env`/ls-files + # checks run against a real git checkout. The temp area is mode 700 and removed on exit. + FIXTURE_WORK="$(mktemp -d "${TMPDIR:-/tmp}/compliance-drift-canary.XXXXXX")" + trap 'rm -rf "$FIXTURE_WORK"' EXIT + log "canary: materializing planted-drift fixtures from $FIXTURE_ROOT into $FIXTURE_WORK" + for d in "$FIXTURE_ROOT"/*/; do + [ -d "$d/dotgit" ] || continue # only fixture repos (skip README.md etc.) + nm="$(basename "$d")" + cp -R "$d" "$FIXTURE_WORK/$nm" + mv "$FIXTURE_WORK/$nm/dotgit" "$FIXTURE_WORK/$nm/.git" + REPO_NAMES+=( "$nm" ); REPO_DIR["$nm"]="$FIXTURE_WORK/$nm" + done +elif [ -n "$TARGETS_OVERRIDE" ]; then + # shellcheck disable=SC2206 + arr=( $TARGETS_OVERRIDE ) + for p in "${arr[@]}"; do p="${p/#\~/$HOME}"; nm="$(basename "$p")"; REPO_NAMES+=( "$nm" ); REPO_DIR["$nm"]="$p"; done + log "explicit targets: ${REPO_NAMES[*]}" +else + if [ "$REFRESH" -eq 1 ]; then + [ -n "${GH_TOKEN:-}" ] || die "--refresh needs GH_TOKEN" + command -v curl >/dev/null || die "--refresh needs curl" + mkdir -p "$MIRROR_DIR" + log "refresh: re-discovering + mirroring via shared substrate (no separate clone path)" + DISCOVERED="$REPORT_DIR/discovered.tsv" + if discover_repos > "$DISCOVERED" 2>>"$REPORT_DIR/discover.log" && [ -s "$DISCOVERED" ]; then + while IFS=$'\t' read -r name url branch; do + [ -n "$name" ] || continue + mirror_repo "$name" "$url" "$branch" || log " mirror FAILED: $name (will use stale mirror if present)" + done < "$DISCOVERED" + else + log "discovery failed — falling back to existing mirrors (coverage may be stale)" + fi + fi + # Default + post-refresh: enumerate EXISTING mirrors. No clone here — reuse the sweep's clones. + [ -d "$MIRROR_DIR" ] || die "mirror dir not found: $MIRROR_DIR (run nightly_sweep.sh first, or use --refresh/--targets)" + for d in "$MIRROR_DIR"/*/; do + [ -d "$d/.git" ] || continue + nm="$(basename "$d")"; REPO_NAMES+=( "$nm" ); REPO_DIR["$nm"]="${d%/}" + done + log "reusing ${#REPO_NAMES[@]} existing mirror(s) in $MIRROR_DIR (no re-clone)" +fi + +[ "${#REPO_NAMES[@]}" -gt 0 ] || die "no repos to scan" + +# Decide whether API checks run: need a token, the API enabled, and not the offline canary. +RUN_API=0 +if [ "$DO_API" -eq 1 ] && [ -n "${GH_TOKEN:-}" ] && command -v curl >/dev/null; then RUN_API=1 +elif [ "$DO_API" -eq 1 ]; then log "API checks requested but GH_TOKEN/curl unavailable — skipping (no false alarms on missing data)"; fi + +# ============================================================================== +# RUN CHECKS +# ============================================================================== +for nm in "${REPO_NAMES[@]}"; do + check_repo_fs "$nm" "${REPO_DIR[$nm]}" + [ "$RUN_API" -eq 1 ] && check_repo_api "$nm" +done + +# ============================================================================== +# ASSEMBLE REPORT (JSON + text), mode 600 +# ============================================================================== +if [ "${#FINDINGS[@]}" -gt 0 ]; then + FINDINGS_JSON="$(printf '%s\n' "${FINDINGS[@]}" | jq -cs .)" +else + FINDINGS_JSON="[]" +fi +if [ "${#SKIPPED_CHECKS[@]}" -gt 0 ]; then + SKIPPED_JSON="$(printf '%s\n' "${SKIPPED_CHECKS[@]}" | jq -R . | jq -cs .)" +else + SKIPPED_JSON="[]" +fi + +N_DRIFT="$(echo "$FINDINGS_JSON" | jq 'length')" +N_HIGH="$(echo "$FINDINGS_JSON" | jq '[.[]|select(.severity=="high")] | length')" +N_REPOS_DRIFTED="$(echo "$FINDINGS_JSON" | jq '[.[].repo] | unique | length')" + +jq -n \ + --arg checker "compliance-drift" --arg ts "$UTC_STAMP" --arg org "$GH_ORG" \ + --argjson api "$RUN_API" --argjson scanned "${#REPO_NAMES[@]}" \ + --argjson findings "$FINDINGS_JSON" --argjson skipped "$SKIPPED_JSON" \ + '{checker:$checker, generated:$ts, org:$org, api_checks_ran:($api==1), + repos_scanned:$scanned, drift_count:($findings|length), + repos_with_drift:([$findings[].repo]|unique|length), + findings:$findings, skipped_checks:$skipped}' > "$REPORT_JSON" + +{ + echo "compliance-drift report — $UTC_STAMP" + echo "org=$GH_ORG repos_scanned=${#REPO_NAMES[@]} api_checks=$([ "$RUN_API" -eq 1 ] && echo on || echo off)" + echo "drift findings: $N_DRIFT ($N_HIGH high) across $N_REPOS_DRIFTED repo(s)" + echo + echo "$FINDINGS_JSON" | jq -r '.[] | "• [\(.severity)] \(.repo): \(.title)\n rule: \(.proof.outcome)"' + if [ "$(echo "$SKIPPED_JSON" | jq 'length')" -gt 0 ]; then + echo; echo "skipped checks (missing data — NOT counted as drift):" + echo "$SKIPPED_JSON" | jq -r '.[] | " - \(.)"' + fi +} > "$REPORT_TXT" +chmod 600 "$REPORT_JSON" "$REPORT_TXT" 2>/dev/null || true + +log "report: $REPORT_JSON ($N_DRIFT drift finding(s), $N_REPOS_DRIFTED repo(s))" + +# ============================================================================== +# CANARY ASSERTION (anti-complacency floor, design §6.4) +# ============================================================================== +if [ "$CANARY" -eq 1 ]; then + EXPECT_FILE="$HERE/fixtures/compliance-drift/EXPECTED_DRIFT_COUNT" + [ -f "$EXPECT_FILE" ] || die "canary expected-count file missing: $EXPECT_FILE" + EXPECTED="$(tr -dc '0-9' < "$EXPECT_FILE")" + log "canary assertion: expected drift=$EXPECTED, got=$N_DRIFT" + if [ "$N_DRIFT" -ne "$EXPECTED" ]; then + echo "[compliance-drift] CANARY FAIL: planted-drift count mismatch (expected $EXPECTED, got $N_DRIFT)" >&2 + echo " -> the checklist regressed (a check stopped firing) or the fixture changed. See $REPORT_TXT." >&2 + exit 3 + fi + log "canary PASS: all $EXPECTED planted drifts detected." +fi + +# ============================================================================== +# ALARM-ONLY ROUTING (clean = silent; memory feedback_cloudwatch_alarms) +# ============================================================================== +if [ "$N_DRIFT" -eq 0 ]; then + log "no confirmed drift — posting NOTHING to Slack (ALARM-only policy)." + exit 0 +fi + +ALARM_BODY="$(echo "$FINDINGS_JSON" | jq -r ' + group_by(.repo)[] | "*\(.[0].repo)*: " + ([.[] | "[\(.severity)] \(.title)"] | join("; "))' | sed 's/^/• /')" +SLACK_TEXT=":triangular_flag_on_post: *Sea Haven compliance-drift — ALARM* ($UTC_STAMP) +$N_DRIFT drift finding(s) across $N_REPOS_DRIFTED repo(s) ($N_HIGH high): +$ALARM_BODY + +Checks: naming · README · CI/CD · Dependabot · secrets-placement · branch-protection (api=$([ "$RUN_API" -eq 1 ] && echo on || echo off)) +Report (mode 600): \`$REPORT_JSON\` (on R720)" +SLACK_TEXT="$(echo "$SLACK_TEXT" | redact)" + +echo "$SLACK_TEXT" >&2 + +if [ "$DRY_RUN" -eq 1 ]; then + log "DRY-RUN: alarm composed but NOT posted (routing dry-run, design §7 Phase 1 / F4)." + exit 0 +fi +post_slack_alarm "$SLACK_TEXT" +exit 0 + +# ============================================================================== +# PROVISIONING (NOT DONE HERE — gated, Phase 6): +# - No systemd unit / timer is installed by this script. Wiring it into the live +# sea-haven-secrev schedule (or a sibling timer) is provisioning and is gated. +# - The coordinator (design §5) that runs this alongside other Tier-1 checkers under +# one shared budget + versioned rotation state is Phase 2, not built here. +# - Confluence + project_r720_agent_team memory updates are docs-as-you-go obligations +# for the build session, tracked outside this script. +# ============================================================================== diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/COMMIT_EDITMSG b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/COMMIT_EDITMSG new file mode 100644 index 0000000..b1b7161 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +init diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/HEAD b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/config b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/config new file mode 100644 index 0000000..8bb2ccd --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[user] + email = t@t + name = t diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/description b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/applypatch-msg.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000..a5d7b84 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/commit-msg.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/commit-msg.sample new file mode 100755 index 0000000..b58d118 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/fsmonitor-watchman.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000..23e856f --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/post-update.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/post-update.sample new file mode 100755 index 0000000..ec17ec1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-applypatch.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000..4142082 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-commit.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-commit.sample new file mode 100755 index 0000000..29ed5ee --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff-index --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-merge-commit.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-merge-commit.sample new file mode 100755 index 0000000..399eab1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-push.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-push.sample new file mode 100755 index 0000000..4ce688d --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-rebase.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-rebase.sample new file mode 100755 index 0000000..6cbef5c --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-receive.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-receive.sample new file mode 100755 index 0000000..a1fd29e --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/prepare-commit-msg.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000..10fa14c --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/push-to-checkout.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/push-to-checkout.sample new file mode 100755 index 0000000..af5a0c0 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + exit 1 +} + +unset GIT_DIR GIT_WORK_TREE +cd "$worktree" && + +if grep -q "^diff --git " "$1" +then + validate_patch "$1" +else + validate_cover_letter "$1" +fi && + +if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" +then + git config --unset-all sendemail.validateWorktree && + trap 'git worktree remove -ff "$worktree"' EXIT && + validate_series +fi diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/update.sample b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/update.sample new file mode 100755 index 0000000..c4d426b --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/index b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/index new file mode 100644 index 0000000000000000000000000000000000000000..64af49e36af5e888a02762ae8090f5f189ad738a GIT binary patch literal 217 zcmZ?q402{*U|<5_EE9vza(WAEVKgHH13#~3KoJ8&;}Ql2#;-s%B0wzb8z$yh_f?~wp;sBF9ei0e`Z_5 zfA7^&2A+b%IvIexzK;Sbj- aSIar;7_JL&oY54LJR7k3+svL7mv{hFKSD16 literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/info/exclude b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/HEAD b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/HEAD new file mode 100644 index 0000000..c0ca3c9 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 bc8f350556a9ba83a52c0896c69005ec5c872c71 t 1781805299 -0400 commit (initial): init diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/refs/heads/main b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/refs/heads/main new file mode 100644 index 0000000..c0ca3c9 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/logs/refs/heads/main @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 bc8f350556a9ba83a52c0896c69005ec5c872c71 t 1781805299 -0400 commit (initial): init diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/4d/56164171e7ac47e50f3c6b06091d1f1e4e36a1 b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/4d/56164171e7ac47e50f3c6b06091d1f1e4e36a1 new file mode 100644 index 0000000000000000000000000000000000000000..27a53791096685b21eac176753d6623f7a2562b5 GIT binary patch literal 45 zcmV+|0Mh?>0ZYosPf{?lWN-}djQ4hpv~~3MboBHOcJp`f^D}piaP$rEapeL4$n*+L DEO{7q literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/72/9065c84f82263e16b2ef5241f3d4f0fc0aca45 b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/72/9065c84f82263e16b2ef5241f3d4f0fc0aca45 new file mode 100644 index 0000000..184767e --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/72/9065c84f82263e16b2ef5241f3d4f0fc0aca45 @@ -0,0 +1 @@ +x+)JMU07b040031QÐKÍ+cð s,|¾Æý)¿M6§¬¼œŸÙB¨|Abrvbzª^Vq~Ó¯BúÛníK½Pâü™m ÿ½WKç� \ No newline at end of file diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/bc/8f350556a9ba83a52c0896c69005ec5c872c71 b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/bc/8f350556a9ba83a52c0896c69005ec5c872c71 new file mode 100644 index 0000000000000000000000000000000000000000..04249ea63a24e78c3741a6ad5742b738453d8c77 GIT binary patch literal 103 zcmV-t0GR)H0e#HD3Bxc90KmRIg%&7CitN}>LRWEBiTaTQg0g=g1N48K?&~@Nh}t6# zGnlzy$y&uLbD50Fhs`q!`ScY>SM1s)r+&t7KOo>0?^uB5N)\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/fsmonitor-watchman.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000..23e856f --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/post-update.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/post-update.sample new file mode 100755 index 0000000..ec17ec1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-applypatch.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000..4142082 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-commit.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-commit.sample new file mode 100755 index 0000000..29ed5ee --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff-index --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-merge-commit.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-merge-commit.sample new file mode 100755 index 0000000..399eab1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-push.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-push.sample new file mode 100755 index 0000000..4ce688d --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-rebase.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-rebase.sample new file mode 100755 index 0000000..6cbef5c --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-receive.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-receive.sample new file mode 100755 index 0000000..a1fd29e --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/prepare-commit-msg.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000..10fa14c --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/push-to-checkout.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/push-to-checkout.sample new file mode 100755 index 0000000..af5a0c0 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + exit 1 +} + +unset GIT_DIR GIT_WORK_TREE +cd "$worktree" && + +if grep -q "^diff --git " "$1" +then + validate_patch "$1" +else + validate_cover_letter "$1" +fi && + +if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" +then + git config --unset-all sendemail.validateWorktree && + trap 'git worktree remove -ff "$worktree"' EXIT && + validate_series +fi diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/update.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/update.sample new file mode 100755 index 0000000..c4d426b --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/index b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/index new file mode 100644 index 0000000000000000000000000000000000000000..93f0dab66cea2c8fc64f15daf750b5d5f4a9bd97 GIT binary patch literal 539 zcmZ?q402{*U|<4b)+`f)&(gCb8(=ge0|P&=W`Gz2L*o(#2F9;IH6lRFt<+enHK+dI zH{Gy;Q*tg-THHb`iy6f9(lbjkN|W?cQVUY^QWBH$OY|ypbAaj@z~;?(@e@Wv%@sv6 zmovh7hVZ|R%NJZ1b6U>)+!Ul$Rjke+2{X4mzbHE`C%?Q{KRHvcG7)Gr*t|K)-(WP< zTnRLDg$r#1*lz8ARH>8q@b;F!d;jaWJZ53w0-2kco|j*g3UVU|fX$g+8w;bM<^lbT z;(uO!ef1;$@_)tcc9zSp&Mv?3jy-xS180z{ql>SrUTzA+oY|U{U~_;p)Esd%bHsM? zZ}{B@!R7sX;X%h>s9?Yq u8~C8V^{dz813UeuJj?u@($;EmIw|3sr0uQUcJpRG{hjpFqxJNokH-O^cDQl? literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/info/exclude b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/HEAD b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/HEAD new file mode 100644 index 0000000..b6e12ed --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 72baacfb9265a352ce186809392c0c849c6220e4 t 1781805299 -0400 commit (initial): init diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/refs/heads/main b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/refs/heads/main new file mode 100644 index 0000000..b6e12ed --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/logs/refs/heads/main @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 72baacfb9265a352ce186809392c0c849c6220e4 t 1781805299 -0400 commit (initial): init diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/22/817d2a9c7fc1f62d5670ca1e44948446543973 b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/22/817d2a9c7fc1f62d5670ca1e44948446543973 new file mode 100644 index 0000000000000000000000000000000000000000..10bb808375844c931ab267ec903099b3b058e7e8 GIT binary patch literal 27 jcmb)e_1n}ZA9WiT`_Ff%bxNY2!&Ow7$;h;W`E{IBEk1=q!#mNP#$1!+|k Ks{;TmoDXaV=M+@{ literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/71/3d5006dabfe2792c6ee1dbb4fdbdff2c44e304 b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/71/3d5006dabfe2792c6ee1dbb4fdbdff2c44e304 new file mode 100644 index 0000000000000000000000000000000000000000..c0f1465f80720df334c1939bb11c4789a09a9a27 GIT binary patch literal 35 tcmV+;0Nnq00ZYosPf{?nWXQ`;NsZ6VPbtkwE!OAKOU*0e0swxT2;wl;5eonS literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/72/baacfb9265a352ce186809392c0c849c6220e4 b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/72/baacfb9265a352ce186809392c0c849c6220e4 new file mode 100644 index 0000000000000000000000000000000000000000..c7569df26fd43717206e856f3b342902188b25e9 GIT binary patch literal 103 zcmV-t0GR)H0e#Ft3WP8W06@=uMLr-TZQ8UT;#X=Lu?JU>?Ee?`0p5pc_jL^eqB7Z| z3XKDX#68gwcHGq`;_jQIBTLZUC9>4W)zz<^_X7rtZMFhQri{^-0y8qAYya5vg}Ct< J=6+)=BT{Q!F_HiP literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/ac/9e4bf172ad292a8d19f7acfb7676c3a0d3bfbd b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/ac/9e4bf172ad292a8d19f7acfb7676c3a0d3bfbd new file mode 100644 index 0000000000000000000000000000000000000000..be64983b4071594f46aac0ba5a7859488881ff3d GIT binary patch literal 94 zcmV-k0HObQ0V^p=O;s?rU@$Z=Ff%bxNJ%Y7%}Ys4$}iEY%*|m?YOK|oQ-AQAZdk!7 zIhQFdZXuS%CI&#DP@Z3uotBedUd#|1_@KY_tJmWLJN>3S%lw_v)@pGY03i$_n;*0( At^fc4 literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/b9/0fb0f8fa06bca16e51fa38bbd44fc6735d2b6d b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/b9/0fb0f8fa06bca16e51fa38bbd44fc6735d2b6d new file mode 100644 index 0000000000000000000000000000000000000000..c3c50ad48278668c21625e04a50a35a96d224639 GIT binary patch literal 38 ucmb7v1BkY00ITQ^vsfs(j\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/fsmonitor-watchman.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000..23e856f --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/post-update.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/post-update.sample new file mode 100755 index 0000000..ec17ec1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-applypatch.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-applypatch.sample new file mode 100755 index 0000000..4142082 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-commit.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-commit.sample new file mode 100755 index 0000000..29ed5ee --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff-index --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-merge-commit.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-merge-commit.sample new file mode 100755 index 0000000..399eab1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-push.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-push.sample new file mode 100755 index 0000000..4ce688d --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-rebase.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-rebase.sample new file mode 100755 index 0000000..6cbef5c --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-receive.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-receive.sample new file mode 100755 index 0000000..a1fd29e --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/prepare-commit-msg.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000..10fa14c --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/push-to-checkout.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/push-to-checkout.sample new file mode 100755 index 0000000..af5a0c0 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + exit 1 +} + +unset GIT_DIR GIT_WORK_TREE +cd "$worktree" && + +if grep -q "^diff --git " "$1" +then + validate_patch "$1" +else + validate_cover_letter "$1" +fi && + +if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" +then + git config --unset-all sendemail.validateWorktree && + trap 'git worktree remove -ff "$worktree"' EXIT && + validate_series +fi diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/update.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/update.sample new file mode 100755 index 0000000..c4d426b --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/index b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/index new file mode 100644 index 0000000000000000000000000000000000000000..9d83913d88e3041f5e3f319af72a69d5aa93e7db GIT binary patch literal 145 zcmZ?q402{*U|<4b#w-(q&+^}O(_l0s0|P&=X23QEhQ=j8>90UFB0$XJarRuRMv?Q% zta-KWt${3S_H3VF&cKzKmy%kcmr;_N15^VBAwjOLK!zlPp@IQd0`tjLC+tNiCF;<4MZzaL4la132`{oGQ`$%O!x*)MPa literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/info/exclude b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/HEAD b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/HEAD new file mode 100644 index 0000000..a1496c0 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 79906bf4bbcd829d2a1f611b11b058dd90827217 t 1781805299 -0400 commit (initial): init diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/refs/heads/main b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/refs/heads/main new file mode 100644 index 0000000..a1496c0 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/logs/refs/heads/main @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 79906bf4bbcd829d2a1f611b11b058dd90827217 t 1781805299 -0400 commit (initial): init diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/48/cdce85287243a96a9e7d47855104acbcb79837 b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/48/cdce85287243a96a9e7d47855104acbcb79837 new file mode 100644 index 0000000000000000000000000000000000000000..5d54a9e17aec6c9521934f8a7c8d9e24ff7b9694 GIT binary patch literal 28 kcmb*1THx)F}ru2R#8eW)ODf58F#A1BvwGz0Isvs)EL z#2{RXZNrL{M%5fBifD1=vHiNiV6w$#@G1L@abSiX3DL) literal 0 HcmV?d00001 diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/refs/heads/main b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/refs/heads/main new file mode 100644 index 0000000..ab3a75a --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/refs/heads/main @@ -0,0 +1 @@ +79906bf4bbcd829d2a1f611b11b058dd90827217 diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/index.html b/security-review/checkers/fixtures/compliance-drift/docs-repo/index.html new file mode 100644 index 0000000..48cdce8 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/index.html @@ -0,0 +1 @@ +placeholder -- 2.50.1