Push Gate Formalised pre-push safety check. Runs before every where the remote is not a local file path. Refuses on secret hits; warns on size/forbidden-file; confirms intent before pushing. Use this skill whenever the user asks to push, or before Claude runs to any remote. Complements (which handles the push itself) — this is the gate that runs immediately before. Hard rules 1. Gitleaks is a required dependency. If not installed, emit the install instructions and refuse. Do not silently fall back to regex-only. 2. Any secret-scanner hit ⇒ refuse. No bypass flag. Force the user to rewrite his…

\n\nif git rev-parse --verify \"${REMOTE}/${BRANCH}\" >/dev/null 2>&1; then\n ADDED_FILES=\"$(git diff --name-only --diff-filter=A \"${REMOTE}/${BRANCH}..${BRANCH}\")\"\nelse\n ADDED_FILES=\"$(git ls-tree -r --name-only \"$BRANCH\")\"\nfi\n\nFORBIDDEN_HITS=\"$(printf '%s\\n' \"$ADDED_FILES\" | grep -iE \"$FORBIDDEN_REGEX\" || true)\"\nif [ -n \"$FORBIDDEN_HITS\" ]; then\n echo \"STEP 7 FAIL forbidden files in push:\"\n printf '%s\\n' \"$FORBIDDEN_HITS\" | sed 's/^/ /'\n echo \" if any are genuinely needed on the remote, remove them from\"\n echo \" the push (git rm --cached) or relax the FORBIDDEN_REGEX in\"\n echo \" scripts/preflight.sh — the default is intentionally strict.\"\n exit 2\nfi\necho \"STEP 7 OK no forbidden file paths\"\n\n# ── Step 8: size advisory ─────────────────────────────────────────────────────\nDIFF_BYTES=0\nif git rev-parse --verify \"${REMOTE}/${BRANCH}\" >/dev/null 2>&1; then\n DIFF_BYTES=\"$(git diff --stat=\"10000,10000,10000\" \"${REMOTE}/${BRANCH}..${BRANCH}\" \\\n | tail -1 | awk '{print $4 + $6}' 2>/dev/null || echo 0)\"\nfi\n\nif [ \"$COMMIT_COUNT\" -gt 50 ]; then\n echo \"STEP 8 WARN ${COMMIT_COUNT} commits in one push (>50). Consider whether\"\n echo \" this should be split into logical pushes for reviewability.\"\nelif [ \"$COMMIT_COUNT\" -gt 10 ]; then\n echo \"STEP 8 INFO ${COMMIT_COUNT} commits (moderate batch)\"\nelse\n echo \"STEP 8 OK ${COMMIT_COUNT} commits\"\nfi\n\ndivider\necho \"push-gate: ALL GATES PASSED\"\necho \"\"\necho \"Ready to push:\"\necho \" git push ${REMOTE} ${BRANCH}\"\necho \"\"\necho \"push-gate does not execute the push itself. Run it explicitly to\"\necho \"preserve 'two-human-steps' separation between gate and action.\"\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6784,"content_sha256":"428d36af5b49f2de9afdaf78e0525d6802041712aa57ebd9de957ca4561436f3"},{"filename":"scripts/scan-secrets.sh","content":"#!/usr/bin/env bash\n# scan-secrets.sh — Secret-scan a pending push diff via gitleaks + regex layer.\n#\n# Usage: scan-secrets.sh \u003cremote> \u003cbranch>\n# Exit: 0 clean, 1 secret hit, 5 missing dep\n\nset -euo pipefail\n\nREMOTE=\"${1:?usage: scan-secrets.sh \u003cremote> \u003cbranch>}\"\nBRANCH=\"${2:?usage: scan-secrets.sh \u003cremote> \u003cbranch>}\"\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPATTERNS_FILE=\"$SCRIPT_DIR/../references/secret-patterns.txt\"\n\n# ── Dep check ─────────────────────────────────────────────────────────────────\nif ! command -v gitleaks >/dev/null 2>&1; then\n cat >&2 \u003c\u003c'EOF'\npush-gate: gitleaks not installed.\n\nInstall:\n Windows (scoop): scoop install gitleaks\n Windows (winget): winget install gitleaks.gitleaks\n macOS: brew install gitleaks\n Linux (apt): apt install gitleaks\n Any platform: https://github.com/gitleaks/gitleaks/releases\nEOF\n exit 5\nfi\n\nif ! command -v rg >/dev/null 2>&1; then\n echo \"push-gate: ripgrep (rg) not installed. See https://github.com/BurntSushi/ripgrep\" >&2\n exit 5\nfi\n\n# ── Range to scan ─────────────────────────────────────────────────────────────\n# Two cases:\n# (a) origin/\u003cbranch> exists → diff range scan (incremental push)\n# (b) origin/\u003cbranch> missing → full branch scan (first push to new remote)\n# The well-known empty-tree SHA lets us express \"everything as added\" for the\n# regex layer's diff-based extraction without special-casing its plumbing.\nEMPTY_TREE=\"4b825dc642cb6eb9a060e54bf8d69288fbee4904\"\n\nif git rev-parse --verify \"${REMOTE}/${BRANCH}\" >/dev/null 2>&1; then\n RANGE=\"${REMOTE}/${BRANCH}..${BRANCH}\"\n GITLEAKS_LOG_OPTS=\"$RANGE\"\n DIFF_RANGE=\"$RANGE\"\n COMMIT_COUNT=\"$(git rev-list --count \"$RANGE\")\"\n if [ \"$COMMIT_COUNT\" -eq 0 ]; then\n echo \"push-gate: nothing to push (${RANGE} is empty).\"\n exit 0\n fi\n SCAN_LABEL=\"${COMMIT_COUNT} commits via gitleaks (${RANGE})\"\nelse\n COMMIT_COUNT=\"$(git rev-list --count \"$BRANCH\")\"\n if [ \"$COMMIT_COUNT\" -eq 0 ]; then\n echo \"push-gate: branch ${BRANCH} has no commits.\"\n exit 0\n fi\n GITLEAKS_LOG_OPTS=\"$BRANCH\"\n DIFF_RANGE=\"${EMPTY_TREE}..${BRANCH}\"\n SCAN_LABEL=\"full branch — ${COMMIT_COUNT} commits via gitleaks (first push to new remote)\"\nfi\n\n# ── Layer 1: gitleaks on the commit range ─────────────────────────────────────\necho \"push-gate: scanning ${SCAN_LABEL}\"\nGITLEAKS_REPORT=\"$(mktemp -t gitleaks.XXXXXX.json)\"\ntrap 'rm -f \"$GITLEAKS_REPORT\" \"$DIFF_FILE\" 2>/dev/null || true' EXIT\n\nGITLEAKS_EXIT=0\ngitleaks detect \\\n --source . \\\n --log-opts=\"$GITLEAKS_LOG_OPTS\" \\\n --report-format=json \\\n --report-path=\"$GITLEAKS_REPORT\" \\\n --redact \\\n --no-banner \\\n --exit-code=1 \\\n 2>&1 || GITLEAKS_EXIT=$?\n\nif [ \"$GITLEAKS_EXIT\" -ne 0 ]; then\n echo \"\"\n echo \"═══════════════════════════════════════════════════════════════\"\n echo \" SECRET DETECTED (gitleaks)\"\n echo \"═══════════════════════════════════════════════════════════════\"\n if command -v jq >/dev/null 2>&1 && [ -s \"$GITLEAKS_REPORT\" ]; then\n jq -r '.[] | \" \\(.RuleID) in \\(.File):\\(.StartLine) — \\(.Description)\"' \"$GITLEAKS_REPORT\" 2>/dev/null \\\n || cat \"$GITLEAKS_REPORT\"\n else\n cat \"$GITLEAKS_REPORT\"\n fi\n echo \"\"\n echo \"Refusing push. Remediate via one of:\"\n echo \" 1. If the secret is real: rotate it NOW, then rewrite history\"\n echo \" (git filter-repo, BFG, or reset + re-commit).\"\n echo \" 2. If it is a false positive: add to .gitleaksignore at repo root\"\n echo \" and commit, then re-run push-gate.\"\n exit 1\nfi\n\n# ── Layer 2: regex corpus on the diff ─────────────────────────────────────────\necho \"push-gate: regex layer on added lines\"\nDIFF_FILE=\"$(mktemp -t push-gate-diff.XXXXXX)\"\n# Exclude push-gate's own pattern corpus — it contains examples of every\n# secret shape it's trying to detect, so scanning it matches everything.\n# (Classic snake-eating-tail when push-gate is part of the pushed content.)\ngit diff \"$DIFF_RANGE\" -- . \\\n ':(exclude,glob)**/push-gate/references/secret-patterns.txt' \\\n > \"$DIFF_FILE\"\n\n# Extract added lines only (strip the leading '+'), ignore file-header lines\nADDED_FILE=\"$(mktemp -t push-gate-added.XXXXXX)\"\ngrep -E '^\\+' \"$DIFF_FILE\" | grep -vE '^\\+\\+\\+ ' | sed 's/^+//' > \"$ADDED_FILE\" || true\n\n# Load patterns (skip blanks/comments)\nPATTERN_ARGS=()\nwhile IFS= read -r line; do\n case \"$line\" in\n ''|\\#*) continue ;;\n *) PATTERN_ARGS+=(-e \"$line\") ;;\n esac\ndone \u003c \"$PATTERNS_FILE\"\n\n# Run ripgrep with all patterns; capture matches\nRAW_HITS=\"$(rg --no-filename --line-number --no-heading \"${PATTERN_ARGS[@]}\" \"$ADDED_FILE\" 2>/dev/null || true)\"\n\n# Filter common false positives.\n# Note: the `\\.\\.\\.'` ellipsis-apostrophe patterns were removed because they\n# required an embedded `'` inside a bash single-quoted string, which closes\n# the string early and breaks the regex (\"Unmatched ( or \\(\"). The remaining\n# patterns (placeholder/example/getenv/etc) cover the bulk of false positives.\nFILTERED_HITS=\"$(\n printf '%s\\n' \"$RAW_HITS\" \\\n | grep -viE '(example|placeholder|\\\u003cdummy\\>|\\\u003cfake\\>|\\\u003cTODO\\>|\u003cunset>|os\\.environ|process\\.env|getenv|\\$\\{[A-Z_]+:-|\\$\\{[A-Z_]+\\}|\\$\\([A-Z_]+\\)|\\$env:[A-Z_]+|\\.\\.\\.\u003c)' \\\n || true\n)\"\n\n# Drop blank lines\nFILTERED_HITS=\"$(printf '%s\\n' \"$FILTERED_HITS\" | grep -v '^

Push Gate Formalised pre-push safety check. Runs before every where the remote is not a local file path. Refuses on secret hits; warns on size/forbidden-file; confirms intent before pushing. Use this skill whenever the user asks to push, or before Claude runs to any remote. Complements (which handles the push itself) — this is the gate that runs immediately before. Hard rules 1. Gitleaks is a required dependency. If not installed, emit the install instructions and refuse. Do not silently fall back to regex-only. 2. Any secret-scanner hit ⇒ refuse. No bypass flag. Force the user to rewrite his…

|| true)\"\n\nrm -f \"$ADDED_FILE\" \"$DIFF_FILE\"\n\nif [ -n \"$FILTERED_HITS\" ]; then\n echo \"\"\n echo \"═══════════════════════════════════════════════════════════════\"\n echo \" SECRET-PATTERN MATCH (regex layer)\"\n echo \"═══════════════════════════════════════════════════════════════\"\n printf '%s\\n' \"$FILTERED_HITS\" | head -40\n echo \"\"\n echo \"Refusing push. These are added lines matching secret-shape patterns.\"\n echo \"Each match must be confirmed safe (placeholder/reference) or redacted\"\n echo \"via history rewrite. See SKILL.md §False-positive handling.\"\n exit 1\nfi\n\necho \"push-gate: secret scan CLEAN (gitleaks + regex layer)\"\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6828,"content_sha256":"b019d59160444bb39207f8d763fdc5894f74c90f75f380de2bb60070a17b952b"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Push Gate","type":"text"}]},{"type":"paragraph","content":[{"text":"Formalised pre-push safety check. Runs before ","type":"text"},{"text":"every","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"git push \u003cremote>","type":"text","marks":[{"type":"code_inline"}]},{"text":" where the remote is not a local file path. Refuses on secret hits; warns on size/forbidden-file; confirms intent before pushing.","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill whenever the user asks to push, or before Claude runs ","type":"text"},{"text":"git push","type":"text","marks":[{"type":"code_inline"}]},{"text":" to any remote. Complements ","type":"text"},{"text":"git-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" (which handles the push itself) — this is the gate that runs immediately before.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Hard rules","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Gitleaks is a required dependency.","type":"text","marks":[{"type":"strong"}]},{"text":" If not installed, emit the install instructions and refuse. Do not silently fall back to regex-only.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any secret-scanner hit ⇒ refuse.","type":"text","marks":[{"type":"strong"}]},{"text":" No bypass flag. Force the user to rewrite history and re-invoke the gate.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never ","type":"text","marks":[{"type":"strong"}]},{"text":"--force","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" push.","type":"text","marks":[{"type":"strong"}]},{"text":" The gate never passes a force flag. If the user needs to force-push, that's a separate conversation with explicit authorization.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never ","type":"text","marks":[{"type":"strong"}]},{"text":"--no-verify","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":".","type":"text","marks":[{"type":"strong"}]},{"text":" Don't skip hooks.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Working tree must be clean.","type":"text","marks":[{"type":"strong"}]},{"text":" Refuse on dirty tree (uncommitted work could be accidentally stashed into the push flow).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remote must be named.","type":"text","marks":[{"type":"strong"}]},{"text":" Refuse if ","type":"text"},{"text":"git push","type":"text","marks":[{"type":"code_inline"}]},{"text":" is called without an explicit remote and branch.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Step 1 → Identify remote + branch\nStep 2 → git fetch \u003cremote>\nStep 3 → Verify working tree clean\nStep 4 → Compute pending commits (count + list)\nStep 5 → Check divergence (non-ff ⇒ require user to rebase first)\nStep 6 → Secret scan ────────┐\nStep 7 → Forbidden-file scan │ refuse on any hit\nStep 8 → Size advisory │\nStep 9 → Explicit confirm │\nStep 10 → git push \u003cremote> \u003cbranch>\nStep 11 → Post-push verify (ls-remote matches pushed SHA)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Invocation","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# From the repo root (most common)\nbash .claude/skills/push-gate/scripts/preflight.sh \u003cremote> \u003cbranch>\n\n# When calling from another skill with a different cwd (e.g. github-ops)\nbash $HOME/.claude/skills/push-gate/scripts/preflight.sh --cwd \u003crepo-root> \u003cremote> \u003cbranch>","type":"text"}]},{"type":"paragraph","content":[{"text":"--cwd","type":"text","marks":[{"type":"code_inline"}]},{"text":" must precede the positional arguments. When omitted, the script operates against ","type":"text"},{"text":"$PWD","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"The script prints a structured report and exits with:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exit code","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Meaning","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"What Claude does","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All gates passed; ready for push","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Ask user to confirm, then ","type":"text"},{"text":"git push \u003cremote> \u003cbranch>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Secret-scanner hit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Report to user; refuse; suggest ","type":"text"},{"text":"git filter-repo","type":"text","marks":[{"type":"code_inline"}]},{"text":" / BFG","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"2","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Forbidden file added (.env, key files, ","type":"text"},{"text":".claude/settings.local.json","type":"text","marks":[{"type":"code_inline"}]},{"text":", worktree paths, etc.)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Report; refuse","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dirty working tree","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Report; ask user to commit or stash first","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"4","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Non-ff divergence","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Report; ask user to rebase or merge first","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing dependency (gitleaks)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Report install instructions; refuse","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"6","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No remote specified / unknown remote","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Report; ask for clarification","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Dependencies","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tool","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"gitleaks","type":"text","marks":[{"type":"strong"}]},{"text":" (required)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Secret detection with maintained rule corpus","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows: ","type":"text"},{"text":"scoop install gitleaks","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"winget install gitleaks.gitleaks","type":"text","marks":[{"type":"code_inline"}]},{"text":" / macOS: ","type":"text"},{"text":"brew install gitleaks","type":"text","marks":[{"type":"code_inline"}]},{"text":" / Linux: ","type":"text"},{"text":"apt install gitleaks","type":"text","marks":[{"type":"code_inline"}]},{"text":" or binary from https://github.com/gitleaks/gitleaks/releases","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ripgrep","type":"text","marks":[{"type":"strong"}]},{"text":" (required)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Regex fallback layer + forbidden-file scan","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Usually pre-installed; ","type":"text"},{"text":"winget install BurntSushi.ripgrep.MSVC","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"brew install ripgrep","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"git","type":"text","marks":[{"type":"strong"}]},{"text":" ≥ 2.30","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Core operations","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Standard","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Both secret layers must pass: gitleaks detects known token formats with a maintained corpus; the regex layer catches generic ","type":"text"},{"text":"password = \"...\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" / DSN / connection-string patterns that gitleaks may miss. See ","type":"text"},{"text":"references/secret-patterns.txt","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the regex corpus.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Trigger phrases","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User intent","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Triggers","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Direct","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"push to origin\", \"push to github\", \"push to remote\", \"git push\"","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Question","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"can we push?\", \"safe to push?\", \"ready to push?\"","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Explicit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/push-gate","type":"text","marks":[{"type":"code_inline"}]},{"text":", \"run push-gate\"","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Claude should invoke ","type":"text"},{"text":"scripts/preflight.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" on any of these. Do not invoke on local pushes (","type":"text"},{"text":"git push \u003cpath>","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"git push .","type":"text","marks":[{"type":"code_inline"}]},{"text":") — those are the ","type":"text"},{"text":"updateInstead","type":"text","marks":[{"type":"code_inline"}]},{"text":" pattern for cross-worktree landings and don't leave the host.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"False-positive handling","type":"text"}]},{"type":"paragraph","content":[{"text":"The regex layer filters common false positives automatically (env-var references, shell fallbacks, placeholders with ","type":"text"},{"text":"...","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Gitleaks has its own ","type":"text"},{"text":".gitleaksignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" file mechanism — add entries there for confirmed-safe findings, committed at repo root. The skill ","type":"text"},{"text":"will not","type":"text","marks":[{"type":"strong"}]},{"text":" offer an inline bypass.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Not in scope","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Release automation (changelog, tagging, version bumps) — that's ","type":"text"},{"text":"ci-cd-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"git-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" territory.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Full security audit — that's ","type":"text"},{"text":"security-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" (broader SAST + dep scanning).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Force-push / history rewriting — intentionally excluded; requires explicit out-of-band authorization.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Signed-commit verification — add later if needed.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Files","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Role","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SKILL.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"This file — workflow + rules","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/preflight.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Main orchestration (Steps 1–8)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/scan-secrets.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Gitleaks + regex layer (Step 6)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/secret-patterns.txt","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Regex corpus + false-positive filter words","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"(empty; reserved for future report templates)","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"push-gate","author":"@skillopedia","source":{"stars":21,"repo_name":"claude-mods","origin_url":"https://github.com/0xdarkmatter/claude-mods/blob/HEAD/skills/push-gate/SKILL.md","repo_owner":"0xdarkmatter","body_sha256":"2fe12d5bbb50badc62476da45f12622025e0b2bd9c0ad10435102071cb657f4d","cluster_key":"a2fe9e201c7a8f9410e7e2a357970b5f50ef0722cae4365440f39a5c7151568a","clean_bundle":{"format":"clean-skill-bundle-v1","source":"0xdarkmatter/claude-mods/skills/push-gate/SKILL.md","attachments":[{"id":"fb9534c0-a377-5177-a22b-b05777aa93c0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fb9534c0-a377-5177-a22b-b05777aa93c0/attachment.txt","path":"references/secret-patterns.txt","size":1701,"sha256":"e66fecc59936ca8c7c4eb19f8025828b61e5c8c786daf5c0742c21966292d654","contentType":"text/plain; charset=utf-8"},{"id":"1ab2e729-4cf0-5163-8bea-136c01f75806","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1ab2e729-4cf0-5163-8bea-136c01f75806/attachment.sh","path":"scripts/preflight.sh","size":6784,"sha256":"428d36af5b49f2de9afdaf78e0525d6802041712aa57ebd9de957ca4561436f3","contentType":"application/x-sh; charset=utf-8"},{"id":"397a0c2e-511f-5095-8f53-d033a392ab8d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/397a0c2e-511f-5095-8f53-d033a392ab8d/attachment.sh","path":"scripts/scan-secrets.sh","size":6828,"sha256":"b019d59160444bb39207f8d763fdc5894f74c90f75f380de2bb60070a17b952b","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"0477fe581bb2bf0f7cce3007de58a53571762f40faf98437a611d476cd0e2f68","attachment_count":3,"text_attachments":3,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/push-gate/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"security","metadata":{"author":"claude-mods","related-skills":"git-ops, security-ops"},"import_tag":"clean-skills-v1","description":"Pre-push safety gate for any git push to a remote (GitHub, GitLab, Bitbucket, self-hosted). Runs gitleaks + regex-layer secret scan, forbidden-file check, divergence check, size warning, and requires explicit confirm before pushing. Refuses on any secret hit. Triggers on: push to origin, push to github, push to remote, git push, can we push, safe to push, ready to push, pre-push check, push-gate.","allowed-tools":"Read Bash Glob Grep"}},"renderedAt":1782979600047}

Push Gate Formalised pre-push safety check. Runs before every where the remote is not a local file path. Refuses on secret hits; warns on size/forbidden-file; confirms intent before pushing. Use this skill whenever the user asks to push, or before Claude runs to any remote. Complements (which handles the push itself) — this is the gate that runs immediately before. Hard rules 1. Gitleaks is a required dependency. If not installed, emit the install instructions and refuse. Do not silently fall back to regex-only. 2. Any secret-scanner hit ⇒ refuse. No bypass flag. Force the user to rewrite his…