From 54b71acbd75ddf116fc6b6d0dc132f526060ed53 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 7 Jul 2026 21:56:36 +0900 Subject: [PATCH 1/2] perf(strix): skip scans on doc/image-only diffs and cap job at 60m Runner-queue starvation across the org: every doc-only, image-only, or empty re-trigger PR/push spawned a fresh ~120-min Strix scan, blocking all PR merges behind runner contention. Two security-preserving throughput fixes: 1. paths-ignore on push and pull_request_target for changes whose ENTIRE diff is non-executable documentation/image assets (*.md, *.rst, *.markdown, raster images, LICENSE, .github/ISSUE_TEMPLATE). A code security scanner has nothing to analyze in such a diff. Conservative: no source, no *.txt, no *.svg (can embed script), no CODEOWNERS, no build/workflow files. GitHub requires EVERY changed file to match, so any code/config/build/workflow change still scans. The weekly full-tree schedule (no path filter) backstops protected branches, and the merge scheduler still forces same-head evidence via workflow_dispatch (which paths-ignore does not affect) before merging managed PRs. 2. Cap the strix job at 60m (was 120m). The scan step is already hard-bounded to 30 min (timeout-minutes: 30 + STRIX_TOTAL_TIMEOUT=1800) and all other steps are quick/self-bounded; 120 only ever bit hung runs. 60m clears the realistic worst case with margin and is fail-closed. The head.sha concurrency / cancel-in-progress security design (which deliberately does NOT cancel in-progress scans on new commits) is unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RjGVapDZ3k7V7zKYk16P4C --- .github/workflows/strix.yml | 56 ++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 9214c44f..071927b7 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -3,8 +3,55 @@ name: Strix Security Scan on: push: branches: [main, develop, master] + # Skip scans for changes that touch ONLY non-executable documentation and + # image assets. A change whose entire diff is these paths has no source, + # build, config, or workflow logic for a code security scanner to analyze, + # so skipping it loses no coverage while freeing shared runner capacity. + # Conservative by design: only file EXTENSIONS/paths that can never contain + # executable logic are listed (no source, no *.txt, no *.svg, no CODEOWNERS, + # no build scripts). A diff touching even one non-listed file still scans. + # The weekly full-tree schedule below re-scans protected branches with no + # path filter, backstopping every path. + paths-ignore: + - '**/*.md' + - '**/*.markdown' + - '**/*.rst' + - '**/*.png' + - '**/*.jpg' + - '**/*.jpeg' + - '**/*.gif' + - '**/*.webp' + - '**/*.bmp' + - '**/*.ico' + - 'LICENSE' + - 'LICENSE.*' + - 'COPYING' + - '.github/ISSUE_TEMPLATE/**' pull_request_target: types: [opened, synchronize, reopened, ready_for_review, closed] + # Same conservative doc/image-only skip for PR scans. GitHub evaluates these + # path filters against the PR's full base..head diff, so a PR is skipped only + # when EVERY changed file is a non-executable doc/image asset; any code, + # config, build, or workflow change still triggers the scan. The head.sha + # concurrency design (below) is unchanged. For PRs the merge scheduler + # manages, same-head Strix evidence is still forced at merge time via + # workflow_dispatch (which paths-ignore does not affect), so merged code + # never loses evidence. + paths-ignore: + - '**/*.md' + - '**/*.markdown' + - '**/*.rst' + - '**/*.png' + - '**/*.jpg' + - '**/*.jpeg' + - '**/*.gif' + - '**/*.webp' + - '**/*.bmp' + - '**/*.ico' + - 'LICENSE' + - 'LICENSE.*' + - 'COPYING' + - '.github/ISSUE_TEMPLATE/**' schedule: # Weekly scan on protected branches (Mondays at 03:00 UTC). - cron: '0 3 * * 1' @@ -61,7 +108,14 @@ jobs: strix: if: github.event_name != 'pull_request_target' || github.event.action != 'closed' - timeout-minutes: 120 + # The scan itself is hard-bounded to 30 min (the "Run Strix (quick)" step + # has timeout-minutes: 30 and exports STRIX_TOTAL_TIMEOUT_SECONDS=1800), and + # every other step is quick or self-bounded (self-test is 2 min). A healthy + # run finishes well under 50 min. The 120 cap only ever bit HUNG runs (e.g. + # a network stall in pip/git with no per-step timeout); 60 min still clears + # the realistic worst case with margin while freeing a stuck runner in half + # the time. Fail-closed: hitting the cap fails the run, never passes it. + timeout-minutes: 60 runs-on: ubuntu-latest env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true From 86cd4621cff244d934d1e8a2320fa7f4814e4cd2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 7 Jul 2026 22:05:18 +0900 Subject: [PATCH 2/2] fix(security): route osv-scanner Maven resolution through Google Central mirror to end 429 flakes The required `osv-scan / osv-scan` gate intermittently failed APPROVED Maven PRs (clearfolio #76/#82/#83) with "No issues found" while osv-scanner resolved transitive parent POMs (spring-boot-starter-parent:3.5.0). It was a reliability failure, not a real vulnerability: repo.maven.apache.org returns HTTP 429 during transitive parent-POM resolution. osv-scanner resolves parent POMs over its own HTTP client (osv-scalibr pomxmlnet -> MavenRegistryAPIClient.defaultRegistry.URL), NOT via the `mvn` CLI or ~/.m2. It never reads ~/.m2/repository and parses settings.xml only for auth, not -- so caching ~/.m2 or a settings.xml mirror would be inert. The only effective lever is the `--maven-registry` flag, which sets that default registry URL. Fix: pass `--maven-registry=https://maven-central.storage-download.googleapis.com/maven2` (Google's byte-identical Maven Central mirror) via `scan-args`, keeping `-r ./` so recursion and transitive scanning stay fully enabled. No `--no-resolve`, no coverage reduction. Applied to both osv-scan callers (security-scan.yml HARD gate and osv-scanner-pr.yml analysis upload). Verified locally with osv-scanner 2.4.0 against a spring-boot-starter-parent:3.5.0 pom.xml: the default-central run reproduced the exact 429 on parent-POM resolution, the mirror run had zero 429s, and both resolved an identical set of 8 transitive packages (byte-identical POMs) -- confirming root cause and zero coverage loss. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RjGVapDZ3k7V7zKYk16P4C --- .github/workflows/osv-scanner-pr.yml | 10 ++++++++++ .github/workflows/security-scan.yml | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml index 4d5c4475..c7238a97 100644 --- a/.github/workflows/osv-scanner-pr.yml +++ b/.github/workflows/osv-scanner-pr.yml @@ -39,3 +39,13 @@ jobs: # (medium_or_higher), not by failing this check. Keep the check green # so it only supplies the analysis; the ruleset decides blocking. fail-on-vuln: false + # RELIABILITY: resolve Maven parent POMs through Google's byte-identical + # Maven Central mirror instead of repo.maven.apache.org, which + # intermittently 429s during transitive resolution (e.g. + # spring-boot-starter-parent). Same maven2 layout, same bytes -> no + # coverage loss; transitive scanning stays fully enabled. See + # security-scan.yml for the full rationale. + scan-args: |- + --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 + -r + ./ diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 7a077b05..05c96a33 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -57,6 +57,23 @@ jobs: security-events: write with: fail-on-vuln: true + # RELIABILITY: point Maven transitive (parent-POM) resolution at Google's + # byte-identical Maven Central mirror instead of repo.maven.apache.org. + # osv-scanner resolves parent POMs (e.g. spring-boot-starter-parent) over + # its own HTTP client (osv-scalibr pomxmlnet -> defaultRegistry.URL); the + # canonical Central host intermittently returns HTTP 429, which failed this + # required gate on APPROVED Maven PRs with "No issues found" (a network + # flake, not a real vuln). The mirror serves the same maven2 layout and the + # same bytes, so coverage is unchanged -- this ONLY swaps the default + # registry host. Repos' own pom.xml are still added on top + # (scalibr AddRegistry), and transitive scanning stays fully enabled (no + # --no-resolve). Caching ~/.m2 or a settings.xml would NOT help: + # osv-scanner never reads ~/.m2/repository and parses settings.xml only for + # auth, not . --maven-registry is the only effective lever. + scan-args: |- + --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 + -r + ./ dependency-review: if: github.event.action != 'closed'