#!/usr/bin/env bash # Deterministic security checklist seed scanner (paths + diff hunks). # Hits here are hard-fail regardless of model agreement (see policy.security_agreement). set -euo pipefail FILES_LIST="${1:?changed-files list}" OUT="${2:?out json}" ROOT="${GATEKEEP_ROOT:-.}" REPO="${GITHUB_REPOSITORY:-}" EVENT="${GITHUB_EVENT_PATH:-}" hits=() while IFS= read -r f; do [[ -z "$f" ]] && continue case "$f" in .github/workflows/*|.github/actions/*) hits+=("CI workflow tampering surface: $f") ;; *secret*|*credential*|.env*|.env) hits+=("Secrets/credential path touched: $f") ;; esac done < "$FILES_LIST" # Diff scan for high-signal patterns (best-effort; forge-agnostic) if [[ -n "$EVENT" && -f "$EVENT" && -n "$REPO" ]]; then PR=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["pull_request"]["number"])' "$EVENT") ACTION_DIR="${GATEKEEP_ACTION:-$(cd "$(dirname "$0")/.." && pwd)}" TMP=$(mktemp) # Prefer .diff endpoint (works on Gitea + GitHub) SERVER="${GITHUB_SERVER_URL:-}" TOKEN="${GITHUB_TOKEN:-${GITEA_TOKEN:-}}" if [[ -n "$SERVER" ]]; then curl -sS -H "Authorization: token ${TOKEN}" \ "${SERVER%/}/${REPO}/pulls/${PR}.diff" -o "$TMP" 2>/dev/null \ || curl -sS -H "Authorization: token ${TOKEN}" \ "${GITHUB_API_URL%/}/repos/${REPO}/pulls/${PR}.diff" -o "$TMP" 2>/dev/null \ || true fi if [[ ! -s "$TMP" ]]; then python3 "$ACTION_DIR/scripts/forge_api.py" GET "/repos/${REPO}/pulls/${PR}.diff" >"$TMP" 2>/dev/null || true fi DIFF=$(cat "$TMP" 2>/dev/null || true) # Only look at added lines ADDED=$(grep -E '^\+' "$TMP" | grep -vE '^\+\+\+' || true) check() { local name="$1" pat="$2" if printf '%s\n' "$ADDED" | grep -Eiq -- "$pat"; then hits+=("$name") fi } check "Hardcoded secrets/tokens pattern" '(api[_-]?key|secret|token|password|private[_-]?key)\s*[:=]\s*['\''\"]?[A-Za-z0-9_\-]{16,}' check "Prompt injection via repo instruction files" '(ignore (all )?(previous|above) instructions|you are now|system prompt)' check "Possible SQL injection concat" '(execute\(|query\(|raw\().*\+|(SELECT|DROP|UNION).*\+' check "Possible command injection" '(exec\(|spawn\(|os\.system\(|subprocess\.[a-z]+\([^)]*shell\s*=\s*True)' check "Unsafe deserialization" '(pickle\.loads|yaml\.load\(|unserialize\(|ObjectInputStream)' rm -f "$TMP" fi HARD=false if ((${#hits[@]} > 0)); then HARD=true fi python3 - "$OUT" "$HARD" "${hits[@]}" <<'PY' import json,sys out=sys.argv[1] hard=sys.argv[2]=="true" hits=sys.argv[3:] json.dump({"hard_fail": hard, "hits": hits, "checklist": [ "hardcoded secrets/tokens", "prompt injection via repo files", "auth bypass", "unauthorized API access", "billing logic bypass", "CI workflow tampering", "unsafe deserialization", "SQL/command injection" ]}, open(out,"w"), indent=2) print(f"security-scan hard_fail={hard} hits={len(hits)}", file=sys.stderr) PY