From fd04df936a83fe019ba78ccf6736bc9016d0218f Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 19 May 2026 22:15:26 +0100 Subject: [PATCH] fix(security): enforce SSH-only git remotes estate-wide (standards#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire token-in-URL detection into the estate CI gate (governance-reusable.yml security-policy job) so any `.git/config` containing an x-access-token/gho_/ ghp_/ghs_/github_pat_ HTTPS credential fails the PR check immediately. Update REMOTE-URL-POLICY.adoc (→ v1.1.0) to: - record the 2026-05-19 recursive audit result (1 offender: file-soup, now fixed) - upgrade the detection one-liner to recurse all .git trees (not just ~/dev/*/) - expand the Enforcement section: CI gate reference, recommended pre-push hook, provisioning guidance The local remediation (file-soup origin → SSH) was applied directly to the .git/config; it is a local change, not a commit. Outstanding owner action: rotate token gho_**** (found in file-soup/.git/config) at https://github.com/settings/tokens — see REMOTE-URL-POLICY.adoc §4. Refs hyperpolymath/standards#69 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/governance-reusable.yml | 23 +++++++ REMOTE-URL-POLICY.adoc | 76 ++++++++++++++++++----- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 56842d18..d04a067b 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -270,6 +270,29 @@ jobs: exit 1 fi echo "✅ Security policy check passed" + - name: SSH-remote policy (token-in-URL detection) + # Estate Remote-URL policy (standards#69 / REMOTE-URL-POLICY.adoc). + # Scans every .git/config present in the checkout tree for token-in-URL + # remotes. Fails hard so a compromised credential cannot silently reach + # a PR or main-branch push. + run: | + found=0 + while IFS= read -r cfg; do + if grep -qE "url[[:space:]]*=[[:space:]]*https://[^[:space:]]*(x-access-token:|:gho_|:ghp_|:ghs_|:github_pat_)" "$cfg" 2>/dev/null; then + echo "❌ token-in-URL remote detected in: $cfg" + grep -E "url[[:space:]]*=" "$cfg" \ + | sed 's/\(x-access-token:\|gho_\|ghp_\|ghs_\|github_pat_\)[^@]*/\1****/g' + found=$((found + 1)) + fi + done < <(find . -name "config" -path "*/.git/config" 2>/dev/null) + if [ "$found" -gt 0 ]; then + echo "" + echo "Remediation: git remote set-url git@github.com:/.git" + echo "Policy: REMOTE-URL-POLICY.adoc — SSH-only remotes; no PAT/token in URL ever." + exit 1 + fi + echo "✅ SSH-remote policy: no token-in-URL remotes detected" + - name: Tooling version integrity # Estate Tooling Version Integrity policy (root cause: burble#39). # Inline + dependency-free so it runs in any caller repo. diff --git a/REMOTE-URL-POLICY.adoc b/REMOTE-URL-POLICY.adoc index 0b0cf938..6989d3a3 100644 --- a/REMOTE-URL-POLICY.adoc +++ b/REMOTE-URL-POLICY.adoc @@ -2,8 +2,8 @@ // SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) = Hyperpolymath Git Remote URL Policy (SSH-only) Jonathan D.A. Jewell -:revnumber: 1.0.0 -:revdate: 2026-05-17 +:revnumber: 1.1.0 +:revdate: 2026-05-19 :toc: :toc-placement: preamble @@ -55,24 +55,43 @@ written into a remote URL, a tracked file, or a commit. [discrete] === 3. Audit -Run estate-wide before any sync: +Run estate-wide before any sync (recurses all `.git` trees): [source,bash] ---- -for d in ~/dev/*/; do - [ -d "$d/.git" ] || continue - u=$(git -C "$d" remote get-url origin 2>/dev/null) - case "$u" in - *x-access-token*|*ghp_*|*gho_*|*ghs_*|*github_pat_*|https://*:*@*) - echo "TOKEN-IN-URL: $d" ;; - esac -done +find ~/dev -name "config" -path "*/.git/config" \ + | xargs grep -l \ + "x-access-token\|:gho_\|:ghp_\|:ghs_\|:github_pat_\|://[^@]*:[^@]*@" \ + 2>/dev/null \ + | while IFS= read -r cfg; do + dir="${cfg%/.git/config}" + echo "TOKEN-IN-URL: $dir" + grep -E "url\s*=" "$cfg" | sed 's/\(x-access-token:\|gho_\|ghp_\|ghs_\|github_pat_\)[^@]*/\1****/g' + done ---- Audit of `2026-05-17`: 20 local `~/dev` clones scanned, *0* token-in-URL remotes (the exposed `repos/ci` clone was scrubbed to SSH in the originating session). +Audit of `2026-05-19` (standards#69): full recursive sweep of ~130 +`.git/config` files across `/home/hyperpolymath/dev` (repos, scratch, +tools, worktrees, audit clones). *1* offender found and remediated: + +[cols="1,1,1,1"] +|=== +|Repo path |Remote |Offending URL (masked) |Remediated to + +|`dev/repos/file-soup` +|`origin` +|`https://x-access-token:gho_****@github.com/hyperpolymath/file-soup` +|`git@github.com:hyperpolymath/file-soup.git` +|=== + +The token `gho_****` (prefix `gho_1q9dB2…`) that appeared in +`file-soup/.git/config` is considered compromised and MUST be rotated +— see <<_remediation_of_an_exposed_token>>. + [discrete] === 4. Remediation of an exposed token @@ -85,6 +104,35 @@ one remaining open item under standards#69. == Enforcement -`gitbot` rejects any push whose `.git/config` (when present in tree) or -remediation script reintroduces a token-in-URL remote. New clones in -provisioning scripts use the SSH form by default. +=== CI gate (estate-wide, automated) + +The `security-policy` job in +`.github/workflows/governance-reusable.yml` now includes a +`SSH-remote policy` step. It scans every `.git/config` that is +checked-in or present in the working tree for the token-in-URL +patterns and fails the PR gate if any are found. Downstream repos +inherit this check automatically via the single `uses:` delegation. + +=== Local pre-push hook (recommended) + +Add the following to `.git/hooks/pre-push` (or the estate hook +installer) to catch tokens before they reach CI: + +[source,bash] +---- +#!/usr/bin/env bash +# hypatia:ignore cicd_rules/banned_language_file +# pre-push hook: reject token-in-URL remotes +if git -C . config --get-all remote.origin.url \ + | grep -qE "x-access-token:|:gho_|:ghp_|:ghs_|:github_pat_|://[^@]+:[^@]+@"; then + echo "ERROR: token-in-URL remote detected — see REMOTE-URL-POLICY.adoc" + exit 1 +fi +---- + +=== Provisioning + +New clones created by provisioning scripts MUST use the SSH form. The +`gitbot` fleet creates remotes with `git@github.com:hyperpolymath/…` +by default; HTTPS-with-credentials is explicitly blocked in +`gitbot-fleet` clone templates.