Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/label-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ permissions:
jobs:
triage:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Classify and label
env:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ permissions:
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Apply canonical labels
env:
Expand Down
4 changes: 2 additions & 2 deletions .hypatia-exemptions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ They are accepted as a category, not enumerated row-by-row:

* `+.audittraining/**+` — training corpus.
* `+scripts/fix-scripts/**+` — remediation scripts.
* `+test/**+` and `+**/tests/**+` — test fixtures
(e.g. `+password: "test123"+`).
* `+test/**+` and `+**/tests/**+` — test fixtures that contain deliberately
invalid credential-shaped examples.

Findings against these paths under `+security_errors/secret_detected+`
are kept in `+.hypatia-baseline.json+` and should remain there.
8 changes: 8 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Security Policy

The canonical security policy for this repository is
[SECURITY.adoc](SECURITY.adoc).

Please do not disclose suspected vulnerabilities in a public issue. Use
[GitHub private vulnerability reporting](https://github.com/hyperpolymath/hypatia/security/advisories/new)
so the maintainers can investigate and coordinate a fix privately.
204 changes: 52 additions & 152 deletions scripts/auto-fix-formulaic.sh
Original file line number Diff line number Diff line change
@@ -1,125 +1,38 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# auto-fix-formulaic.sh — Direct auto-fix for predictable, formulaic issues
# auto-fix-formulaic.sh — Legacy formulaic diagnostics compatibility command
#
# This bypasses the full Hypatia pipeline and directly fixes issues that
# are so predictable they should never appear in any scan. These are the
# issues that the sophisticated pipeline SHOULD catch but currently doesn't
# because the operational deployment is missing.
# Mutation is intentionally disabled. The former implementation bypassed the
# Hypatia policy pipeline, carried a stale SHA database alongside actions.lock,
# inserted permissions: read-all contrary to current least-privilege policy,
# and could stage unrelated work with git add -A.
#
# Usage: ./scripts/auto-fix-formulaic.sh [repo-path|all]
# GitHub Action resolution belongs to the authoritative `gh actions-lock`
# recipe. Workflow permissions require workflow-specific policy evaluation.
# Until those transactional recipes are wired, this command is read-only.
#
# Usage: ./scripts/auto-fix-formulaic.sh <repo-path>
# Examples:
# ./scripts/auto-fix-formulaic.sh ~/Documents/hyperpolymath-repos/aerie
# ./scripts/auto-fix-formulaic.sh all # scan all repos
# ./scripts/auto-fix-formulaic.sh /home/hyperpolymath/developer/hyper-repos/aerie

set -euo pipefail

REPOS_DIR="${HOME}/Documents/hyperpolymath-repos"
FIXES_APPLIED=0
REPOS_SCANNED=0

# Canonical SHA pins — from Hypatia.Rules.SecurityErrors
declare -A SHA_PINS=(
["actions/checkout@v4"]="34e114876b0b11c390a56381ad16ebd13914f8d5"
["actions/checkout@v5"]="93cb6efe18208431cddfb8368fd83d5badbf9bfd"
["github/codeql-action@v3"]="6624720a57d4c312633c7b953db2f2da5bcb4c3a"
["ossf/scorecard-action@v2.4.0"]="62b2cac7ed8198b15735ed49ab1e5cf35480ba46"
["dtolnay/rust-toolchain@stable"]="4be9e76fd7c4901c61fb841f559994984270fce7"
["Swatinem/rust-cache@v2"]="779680da715d629ac1d338a641029a2f4372abb5"
["codecov/codecov-action@v5"]="671740ac38dd9b0130fbe1cec585b89eea48d3de"
["trufflesecurity/trufflehog@main"]="7ee2e0fdffec27d19ccbb8fb3dcf8a83b9d7f9e8"
["webfactory/ssh-agent@v0.9.0"]="dc588b651fe13675774614f8e6a936a468676387"
["ocaml/setup-ocaml@v3"]="dec6499fef64fc5d7ed43d43a87251b7b1c306f5"
["softprops/action-gh-release@v2"]="a06a81a03ee405af7f2048a818ed3f03bbf83c7b"
["actions/configure-pages@v5"]="983d7736d9b0ae728b81ab479565c72886d7745b"
["actions/jekyll-build-pages@v1"]="44a6e6beabd48582f863aeeb6cb2151cc1716697"
["actions/upload-pages-artifact@v3"]="56afc609e74202658d3ffba0e8f6dda462b719fa"
["actions/deploy-pages@v4"]="d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e"
["ruby/setup-ruby@v1"]="09a7688d3b55cf0e976497ff046b70949eeaccfd"
["editorconfig-checker/action-editorconfig-checker@main"]="4054fa83a075fdf090bd098bdb1c09aaf64a4169"
["slsa-framework/slsa-github-generator@v2.1.0"]="f7dd8c54c2067bafc12ca7a55595d5ee9b75204a"
)

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
WARNINGS=0

log() { echo "[hypatia-autofix] $*"; }
warn() { echo "[hypatia-autofix] WARNING: $*" >&2; }
warn() {
echo "[hypatia-autofix] WARNING: $*" >&2
WARNINGS=$((WARNINGS + 1))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
fix() {
echo "[hypatia-autofix] FIX: $*"
FIXES_APPLIED=$((FIXES_APPLIED + 1))
}

# Record activity in the repo's .hypatia/ log
record() {
local repo="$1" action="$2" details="${3:-}"
bash "${SCRIPT_DIR}/bot-accountability.sh" record "$repo" "hypatia-autofix" "$action" "$details" 2>/dev/null || true
}

# ---------------------------------------------------------------------------
# Fix 1: Pin unpinned GitHub Actions to SHA
# ---------------------------------------------------------------------------
fix_unpinned_actions() {
local repo="$1"
local workflows_dir="${repo}/.github/workflows"
[ -d "$workflows_dir" ] || return 0

for wf in "${workflows_dir}"/*.yml; do
[ -f "$wf" ] || continue

# Find lines with uses: action@vN (not already SHA-pinned)
while IFS= read -r line; do
# Extract action@version
action_ref=$(echo "$line" | grep -oP 'uses:\s*\K[^@]+@v[0-9][^\s]*' || true)
[ -z "$action_ref" ] && continue

# Check if already SHA-pinned (40-char hex after @)
if echo "$line" | grep -qP '@[0-9a-f]{40}'; then
continue
fi

# Look up canonical SHA
local sha="${SHA_PINS[$action_ref]:-}"
if [ -n "$sha" ]; then
local action_name="${action_ref%%@*}"
local version="${action_ref##*@}"
local old_pattern="${action_ref}"
local new_pattern="${action_name}@${sha} # ${version}"

sed -i "s|${old_pattern}|${new_pattern}|g" "$wf"
fix "Pinned ${action_ref} → ${sha} in $(basename "$wf")"
else
warn "Unknown action ${action_ref} in $(basename "$wf") — add to SHA_PINS"
fi
done < <(grep -n 'uses:.*@v[0-9]' "$wf" 2>/dev/null || true)
done
}

# ---------------------------------------------------------------------------
# Fix 2: Add missing permissions: read-all to workflows
# ---------------------------------------------------------------------------
fix_missing_permissions() {
local repo="$1"
local workflows_dir="${repo}/.github/workflows"
[ -d "$workflows_dir" ] || return 0

for wf in "${workflows_dir}"/*.yml; do
[ -f "$wf" ] || continue

if ! grep -q '^permissions:' "$wf" 2>/dev/null; then
# Insert permissions after the 'on:' block (after first blank line after on:)
# Simple approach: insert after SPDX header line or at line 2
if grep -q 'SPDX-License-Identifier' "$wf"; then
sed -i '/^name:/i permissions: read-all\n' "$wf"
else
sed -i '1a\permissions: read-all' "$wf"
fi
fix "Added permissions: read-all to $(basename "$wf")"
fi
done
}

# ---------------------------------------------------------------------------
# Fix 3: Detect binary artifacts tracked by git
# Diagnostic 1: Detect binary artifacts tracked by git
# ---------------------------------------------------------------------------
fix_tracked_binaries() {
local repo="$1"
Expand All @@ -142,20 +55,33 @@ fix_tracked_binaries() {
fi
done

# Materialise the listing before consuming it. Bash process substitution does
# not propagate the producer's exit status to the while loop, so reading
# directly from < <(git ls-files -z) could turn a failed listing into a
# successful empty scan.
local tracked_list
tracked_list=$(mktemp)
if ! git ls-files -z >"$tracked_list"; then
rm -f "$tracked_list"
echo "ERROR: git ls-files failed in $repo; tracked-file diagnostics are incomplete." >&2
return 1
fi

# Check for large tracked files (>1MB)
git ls-files -z 2>/dev/null | while IFS= read -r -d '' f; do
while IFS= read -r -d '' f; do
if [ -f "$f" ]; then
local size
size=$(stat -c%s "$f" 2>/dev/null || echo 0)
if [ "$size" -gt 1048576 ]; then
warn "Large file tracked ($(( size / 1024 ))KB): $f (in $(basename "$repo"))"
fi
fi
done
done <"$tracked_list"
rm -f "$tracked_list"
}

# ---------------------------------------------------------------------------
# Fix 4: Check for AGPL references (should be PMPL)
# Diagnostic 2: Check for AGPL references (should be PMPL)
# ---------------------------------------------------------------------------
fix_agpl_references() {
local repo="$1"
Expand All @@ -182,7 +108,7 @@ fix_agpl_references() {
}

# ---------------------------------------------------------------------------
# Fix 5: Check for missing SECURITY.md in public repos
# Diagnostic 3: Check for missing SECURITY.md in public repos
# ---------------------------------------------------------------------------
check_security_md() {
local repo="$1"
Expand All @@ -192,7 +118,7 @@ check_security_md() {
}

# ---------------------------------------------------------------------------
# Fix 6: Check for missing .editorconfig
# Diagnostic 4: Check for missing .editorconfig
# ---------------------------------------------------------------------------
check_editorconfig() {
local repo="$1"
Expand All @@ -211,63 +137,36 @@ scan_repo() {
log "Scanning $(basename "$repo")..."
REPOS_SCANNED=$((REPOS_SCANNED + 1))

local before=$FIXES_APPLIED
fix_unpinned_actions "$repo"
fix_missing_permissions "$repo"
fix_tracked_binaries "$repo"
fix_agpl_references "$repo"
check_security_md "$repo"
check_editorconfig "$repo"
local after=$FIXES_APPLIED

local fix_count=$((after - before))

# Auto-commit and push if fixes were applied
if [ "$fix_count" -gt 0 ] && [ "${AUTO_PUSH:-false}" = "true" ]; then
cd "$repo"
git add -A
git commit -m "$(cat <<EOF
chore: hypatia auto-fix (${fix_count} formulaic fixes)

Applied by auto-fix-formulaic.sh:
- SHA-pinned unpinned GitHub Actions
- Added missing workflow permissions
- Fixed license headers

Co-Authored-By: hypatia-autofix <noreply@hyperpolymath.github.io>
EOF
)" 2>/dev/null && {
git push origin HEAD 2>/dev/null && log "Pushed fixes for $(basename "$repo")" || warn "Push failed for $(basename "$repo")"
} || log "Nothing to commit in $(basename "$repo")"
fi

# Record visit with fix count
record "$repo" "scan" "fixes=${fix_count}"
}

# Parse flags
# Reject legacy mutation and incomplete-estate modes explicitly.
for arg in "$@"; do
case "$arg" in
--push) export AUTO_PUSH=true; shift ;;
--push)
echo "ERROR: --push is disabled; this compatibility command is diagnostic-only." >&2
exit 2
Comment thread
coderabbitai[bot] marked this conversation as resolved.
;;
esac
done

if [ "${1:-}" = "all" ]; then
log "Scanning all repos in ${REPOS_DIR}..."
for repo_dir in "${REPOS_DIR}"/*/; do
[ -d "${repo_dir}/.git" ] || continue
scan_repo "$repo_dir"
done
echo "ERROR: incomplete 'all' traversal is disabled; use the authoritative estate manifest." >&2
exit 2
elif [ -n "${1:-}" ]; then
scan_repo "$1"
else
echo "Usage: $0 [--push] [repo-path|all]"
echo " $0 ~/Documents/hyperpolymath-repos/aerie"
echo " $0 --push all # scan, fix, commit, and push"
echo "Usage: $0 <repo-path>"
echo " $0 /home/hyperpolymath/developer/hyper-repos/aerie"
echo ""
echo "Mutation, --push, and incomplete 'all' traversal are disabled."
exit 1
fi

log "Done. Scanned ${REPOS_SCANNED} repos, applied ${FIXES_APPLIED} fixes."
log "Done. Diagnostic-only scan: repos=${REPOS_SCANNED}, fixes=${FIXES_APPLIED}, warnings=${WARNINGS}."

# --- Kin Protocol: write heartbeat ---
KIN_DIR="${HOME}/.hypatia/kin"
Expand All @@ -277,14 +176,15 @@ cat > "${KIN_DIR}/auto-fix.heartbeat.json" <<HEARTBEAT
"kin_id": "auto-fix",
"role": "fixer",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"status": "healthy",
"version": "1.0.0",
"status": "diagnostic_only",
"version": "1.1.0",
"last_run": {
"repos_scanned": ${REPOS_SCANNED},
"fixes_applied": ${FIXES_APPLIED},
"auto_push": "${AUTO_PUSH:-false}"
"warnings": ${WARNINGS},
"mutation_enabled": false
},
"errors": [],
"capabilities": ["formulaic_fix", "spdx_fix", "shell_quote_fix", "sha_pin_fix"]
"capabilities": ["tracked_binary_audit", "license_reference_audit", "security_file_check", "editorconfig_check"]
}
HEARTBEAT
2 changes: 1 addition & 1 deletion scripts/hypatia-autofix.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ Description=Hypatia formulaic auto-fix scan

[Service]
Type=oneshot
ExecStart=%h/Documents/hyperpolymath-repos/hypatia/scripts/auto-fix-formulaic.sh --push all
ExecStart=%h/Documents/hyperpolymath-repos/hypatia/scripts/auto-fix-formulaic.sh %h/Documents/hyperpolymath-repos/hypatia
Environment=HOME=%h
Loading
Loading