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 0000000..64af49e Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/index differ 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 0000000..27a5379 Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/4d/56164171e7ac47e50f3c6b06091d1f1e4e36a1 differ 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 0000000..04249ea Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/bc/8f350556a9ba83a52c0896c69005ec5c872c71 differ diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/e4/f0ea548f86dabe65d0507443f306ac0fdeeaa5 b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/e4/f0ea548f86dabe65d0507443f306ac0fdeeaa5 new file mode 100644 index 0000000..6f0e32e Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/objects/e4/f0ea548f86dabe65d0507443f306ac0fdeeaa5 differ diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/refs/heads/main b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/refs/heads/main new file mode 100644 index 0000000..4d44489 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/dotgit/refs/heads/main @@ -0,0 +1 @@ +bc8f350556a9ba83a52c0896c69005ec5c872c71 diff --git a/security-review/checkers/fixtures/compliance-drift/BadName_repo/package.json b/security-review/checkers/fixtures/compliance-drift/BadName_repo/package.json new file mode 100644 index 0000000..e4f0ea5 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/BadName_repo/package.json @@ -0,0 +1 @@ +{"name":"x"} diff --git a/security-review/checkers/fixtures/compliance-drift/EXPECTED_DRIFT_COUNT b/security-review/checkers/fixtures/compliance-drift/EXPECTED_DRIFT_COUNT new file mode 100644 index 0000000..1e8b314 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/EXPECTED_DRIFT_COUNT @@ -0,0 +1 @@ +6 diff --git a/security-review/checkers/fixtures/compliance-drift/README.md b/security-review/checkers/fixtures/compliance-drift/README.md new file mode 100644 index 0000000..ed22725 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/README.md @@ -0,0 +1,19 @@ +# compliance-drift canary fixtures + +Planted-drift corpus for `checkers/compliance-drift.sh --canary` (offline, no network/token). +The checker asserts the total drift count equals `EXPECTED_DRIFT_COUNT` (anti-complacency floor, +design §6.4). If a check regresses (stops firing), the count drops and the canary FAILS (exit 3). + +Fixtures (each a real git checkout so the tracked-`.env` / `ls-files` checks work): + +| Fixture | Planted drift | Count | +|---|---|---| +| `clean-repo` | none — kebab name, README, ci.yaml, dependabot.yml, `.env` is **gitignored** (must NOT fire) | 0 | +| `BadName_repo` | non-kebab name; no README; no ci.yaml; has `package.json` but no `dependabot.yml`; tracked `.env` with values | 5 | +| `docs-repo` | docs-only (CI skipped via DOCS_ONLY_REPOS), kebab name, no README | 1 | + +Total = **6** (`EXPECTED_DRIFT_COUNT`). The canary pins `DOCS_ONLY_REPOS=docs-repo` and +`COMPLIANCE_EXEMPT=""` internally so it is deterministic regardless of the operator's env. + +When you add/remove a check or fixture, update both the fixture and `EXPECTED_DRIFT_COUNT` +in the same commit (the canary edit is itself caught on the next run — design §6.4). diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/.github/dependabot.yml b/security-review/checkers/fixtures/compliance-drift/clean-repo/.github/dependabot.yml new file mode 100644 index 0000000..22817d2 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/.github/dependabot.yml @@ -0,0 +1 @@ +version: 2 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/.github/workflows/ci.yaml b/security-review/checkers/fixtures/compliance-drift/clean-repo/.github/workflows/ci.yaml new file mode 100644 index 0000000..5843981 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/.github/workflows/ci.yaml @@ -0,0 +1 @@ +name: CI diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/.gitignore b/security-review/checkers/fixtures/compliance-drift/clean-repo/.gitignore new file mode 100644 index 0000000..713d500 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/README.md b/security-review/checkers/fixtures/compliance-drift/clean-repo/README.md new file mode 100644 index 0000000..2f2f27c --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/README.md @@ -0,0 +1 @@ +# clean-repo diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/COMMIT_EDITMSG b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/COMMIT_EDITMSG new file mode 100644 index 0000000..b1b7161 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +init diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/HEAD b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/config b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/config new file mode 100644 index 0000000..8bb2ccd --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-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/clean-repo/dotgit/description b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-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/clean-repo/dotgit/hooks/applypatch-msg.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000..a5d7b84 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-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/clean-repo/dotgit/hooks/commit-msg.sample b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/hooks/commit-msg.sample new file mode 100755 index 0000000..b58d118 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-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/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 0000000..93f0dab Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/index differ 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 0000000..10bb808 Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/22/817d2a9c7fc1f62d5670ca1e44948446543973 differ diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/2f/2f27c44f1ffd173eb9771fab6b77d0ee075bb5 b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/2f/2f27c44f1ffd173eb9771fab6b77d0ee075bb5 new file mode 100644 index 0000000..003dbbf Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/2f/2f27c44f1ffd173eb9771fab6b77d0ee075bb5 differ diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/58/439813fe88d3d045a3093999f382522a7a7327 b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/58/439813fe88d3d045a3093999f382522a7a7327 new file mode 100644 index 0000000..1c07e1b Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/58/439813fe88d3d045a3093999f382522a7a7327 differ diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/5d/51e08f85f54ae3c0b94e94e669fb64868538cb b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/5d/51e08f85f54ae3c0b94e94e669fb64868538cb new file mode 100644 index 0000000..426c77b Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/5d/51e08f85f54ae3c0b94e94e669fb64868538cb differ 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 0000000..c0f1465 Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/71/3d5006dabfe2792c6ee1dbb4fdbdff2c44e304 differ 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 0000000..c7569df Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/72/baacfb9265a352ce186809392c0c849c6220e4 differ 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 0000000..be64983 Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/ac/9e4bf172ad292a8d19f7acfb7676c3a0d3bfbd differ 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 0000000..c3c50ad Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/b9/0fb0f8fa06bca16e51fa38bbd44fc6735d2b6d differ diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/c4/30364d61f3b0be2614d2c76f873edd7547a54a b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/c4/30364d61f3b0be2614d2c76f873edd7547a54a new file mode 100644 index 0000000..b0a7a24 Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/objects/c4/30364d61f3b0be2614d2c76f873edd7547a54a differ diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/refs/heads/main b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/refs/heads/main new file mode 100644 index 0000000..8084ef9 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/dotgit/refs/heads/main @@ -0,0 +1 @@ +72baacfb9265a352ce186809392c0c849c6220e4 diff --git a/security-review/checkers/fixtures/compliance-drift/clean-repo/package.json b/security-review/checkers/fixtures/compliance-drift/clean-repo/package.json new file mode 100644 index 0000000..b90fb0f --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/clean-repo/package.json @@ -0,0 +1 @@ +{"name":"clean-repo"} diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/COMMIT_EDITMSG b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/COMMIT_EDITMSG new file mode 100644 index 0000000..b1b7161 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/COMMIT_EDITMSG @@ -0,0 +1 @@ +init diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/HEAD b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/config b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/config new file mode 100644 index 0000000..8bb2ccd --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-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/docs-repo/dotgit/description b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-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/docs-repo/dotgit/hooks/applypatch-msg.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/applypatch-msg.sample new file mode 100755 index 0000000..a5d7b84 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-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/docs-repo/dotgit/hooks/commit-msg.sample b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/hooks/commit-msg.sample new file mode 100755 index 0000000..b58d118 --- /dev/null +++ b/security-review/checkers/fixtures/compliance-drift/docs-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/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 0000000..9d83913 Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/index differ 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 0000000..5d54a9e Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/48/cdce85287243a96a9e7d47855104acbcb79837 differ diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/60/03c9aac8de93dc4777594f2b0d46ee8345ccea b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/60/03c9aac8de93dc4777594f2b0d46ee8345ccea new file mode 100644 index 0000000..c500c91 Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/60/03c9aac8de93dc4777594f2b0d46ee8345ccea differ diff --git a/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/79/906bf4bbcd829d2a1f611b11b058dd90827217 b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/79/906bf4bbcd829d2a1f611b11b058dd90827217 new file mode 100644 index 0000000..19c91fb Binary files /dev/null and b/security-review/checkers/fixtures/compliance-drift/docs-repo/dotgit/objects/79/906bf4bbcd829d2a1f611b11b058dd90827217 differ 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