fix(deps): patch socket.io-parser advisory; add local CI preflight gate
Security Audit failed on a newly-published advisory, GHSA-2m8v-j782-fhvr (socket.io-parser "Zero-attachment Memory Exhaustion", high). It affected both projects transitively via socket.io / socket.io-client. Patched to 4.2.7 in both lockfiles; no direct dependency versions changed. Also adds the gate that should have caught the previous two CI failures before they reached GitHub: - scripts/ci-preflight.sh runs every gate from .github/workflows/ci.yml (frontend and backend lint/type-check/build, plus both prod-dependency audits with the same audit-ci flags and allowlist) and additionally the backend Jest suite, which CI does not currently run. It runs all gates rather than failing fast, so one invocation surfaces every problem. ~35s. - scripts/claude-push-gate.sh wires that into a Claude Code PreToolUse hook so a `git push` is refused while any gate fails, with the failure detail returned inline. Non-push commands and --dry-run pass straight through. Verified: with the fix reverted, the gate correctly denied the push and reported "backend: lint FAIL"; with it restored, all 9 gates pass and both audit-ci invocations exit 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
23a45703a5
commit
d44a0ea8a7
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run the same gates GitHub Actions runs, locally, before pushing.
|
||||
#
|
||||
# Mirrors .github/workflows/ci.yml exactly (frontend lint/type-check/build,
|
||||
# backend lint/type-check/build, prod-dependency audits) and additionally runs
|
||||
# the backend Jest suite, which CI does not currently cover.
|
||||
#
|
||||
# Usage: scripts/ci-preflight.sh [--skip-audit]
|
||||
# Exit: 0 = everything CI checks would pass, 1 = at least one gate failed.
|
||||
#
|
||||
# Runs every gate rather than failing fast, so one invocation surfaces all
|
||||
# problems instead of dribbling them out one push at a time.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SKIP_AUDIT=0
|
||||
[[ "${1:-}" == "--skip-audit" ]] && SKIP_AUDIT=1
|
||||
|
||||
FAILED=()
|
||||
run() {
|
||||
local label="$1" dir="$2"; shift 2
|
||||
printf ' %-34s' "$label"
|
||||
if (cd "$ROOT/$dir" && "$@") >/tmp/ci-preflight-out 2>&1; then
|
||||
echo "ok"
|
||||
else
|
||||
echo "FAIL"
|
||||
FAILED+=("$label")
|
||||
# Keep a trimmed excerpt so the caller can see why without re-running.
|
||||
{
|
||||
echo "----- $label -----"
|
||||
tail -25 /tmp/ci-preflight-out
|
||||
echo
|
||||
} >>/tmp/ci-preflight-failures
|
||||
fi
|
||||
}
|
||||
|
||||
: >/tmp/ci-preflight-failures
|
||||
echo "Running CI gates locally (mirrors .github/workflows/ci.yml)"
|
||||
|
||||
run "backend: lint" backend npm run lint
|
||||
run "backend: type-check" backend npx tsc --noEmit
|
||||
run "backend: build" backend npm run build
|
||||
run "backend: tests" backend npx jest --silent
|
||||
run "frontend: lint" frontend npm run lint
|
||||
run "frontend: type-check" frontend npx tsc --noEmit
|
||||
run "frontend: build" frontend npm run build
|
||||
|
||||
if [[ $SKIP_AUDIT -eq 0 ]]; then
|
||||
run "frontend: audit (prod)" frontend \
|
||||
npx --yes audit-ci@^7 --high --skip-dev --allowlist GHSA-qwww-vcr4-c8h2
|
||||
run "backend: audit (prod)" backend \
|
||||
npx --yes audit-ci@^7 --high --skip-dev
|
||||
fi
|
||||
|
||||
if [[ ${#FAILED[@]} -eq 0 ]]; then
|
||||
echo "All CI gates passed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "FAILED: ${FAILED[*]}"
|
||||
echo "Details in /tmp/ci-preflight-failures"
|
||||
exit 1
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
# Claude Code PreToolUse(Bash) hook: refuse `git push` until the local CI gates pass.
|
||||
#
|
||||
# Reads the hook payload on stdin. Non-push commands exit immediately, so the
|
||||
# overhead on ordinary Bash calls is one short script invocation.
|
||||
#
|
||||
# On failure it emits a PreToolUse deny decision, which blocks the push and hands
|
||||
# the reason back to the model so it can fix the problem instead of retrying.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
PAYLOAD="$(cat)"
|
||||
CMD="$(printf '%s' "$PAYLOAD" | jq -r '.tool_input.command // ""' 2>/dev/null)"
|
||||
|
||||
# Only gate actual pushes (covers `git push`, `git -C <dir> push`, flags, etc.).
|
||||
# Deliberately ignores --dry-run.
|
||||
if ! printf '%s' "$CMD" | grep -Eq '(^|[;&|]|\s)git(\s+-[^;&|]*)?\s+push(\s|$)'; then
|
||||
exit 0
|
||||
fi
|
||||
if printf '%s' "$CMD" | grep -q -- '--dry-run'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REPO="/Users/rteslow/mikrotik-manager"
|
||||
[[ -x "$REPO/scripts/ci-preflight.sh" ]] || exit 0 # nothing to enforce
|
||||
|
||||
if OUTPUT="$("$REPO/scripts/ci-preflight.sh" 2>&1)"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
DETAIL="$(tail -60 /tmp/ci-preflight-failures 2>/dev/null)"
|
||||
python3 - "$OUTPUT" "$DETAIL" <<'PY'
|
||||
import json, sys
|
||||
summary, detail = sys.argv[1], sys.argv[2]
|
||||
reason = (
|
||||
"Push blocked: the local CI gates failed, so this would fail on GitHub too.\n\n"
|
||||
f"{summary.strip()}\n\n{detail.strip()}\n\n"
|
||||
"Fix these, re-run scripts/ci-preflight.sh until it passes, then push."
|
||||
)
|
||||
print(json.dumps({
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": reason,
|
||||
}
|
||||
}))
|
||||
PY
|
||||
exit 0
|
||||
Reference in New Issue
Block a user