diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index 70d1f0e63..2b2eae4c3 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -6,8 +6,17 @@ # package (block-on-any-finding policy). OSV-Scanner exits non-zero when it finds # anything, which fails this job. # -# Runs on every PR (incl. Dependabot bumps), on pushes to main, and weekly to -# catch advisories disclosed after a dependency was already merged. +# Runs on every PR (incl. Dependabot bumps), on pushes to main, and daily to +# catch advisories disclosed after a dependency was already merged — a PR is +# the only thing that surfaced a scheduled failure before this, so a scan that +# broke on `main` between PRs went unnoticed until the next one happened to +# touch the lockfile. +# +# A daily scheduled failure also posts to Slack (see the notify step below, +# gated on github.event_name == 'schedule') — PR and push runs don't, since +# those failures are already visible to whoever opened or merged the PR +# without needing a second channel. Posting is itself optional: it silently +# no-ops when the SLACK_WEBHOOK_URL repository secret isn't set. # # Triage / allow-listing unfixable advisories: see SECURITY.md and osv-scanner.toml # (auto-loaded from the repo root by OSV-Scanner). @@ -22,7 +31,7 @@ on: push: branches: [main] schedule: - - cron: "17 4 * * 1" # Mondays 04:17 UTC + - cron: "17 4 * * *" # daily, 04:17 UTC workflow_dispatch: # Least privilege: the scan only needs to read the checked-out source. @@ -46,8 +55,39 @@ jobs: # not Dependabot, which had no `cargo` ecosystem — for a TLS stack that # compiles into a root-installed system service. - name: Scan bun.lock and Cargo.lock for known-vulnerable / malicious dependencies + id: scan uses: google/osv-scanner-action/osv-scanner-action@9fd1bcce27f67e3bd819a0a7620e332803dc43bc # v2.3.8 with: scan-args: |- --lockfile=bun.lock --lockfile=Cargo.lock + # Only the schedule run notifies — nothing on main touched the lockfile, + # so nobody is watching it the way a PR author watches their own checks + # or a push failure shows up against the commit they just merged. Same + # webhook + `node -e … | curl` idiom integration-suite/run.sh uses. + # `failure()` is required, not decorative: a bare `if:` expression is + # implicitly ANDed with success(), so without it this step would be + # skipped every time — the one time it's meant to run is exactly when + # the job already failed. + - name: Notify Slack on failure + if: failure() && steps.scan.outcome == 'failure' && github.event_name == 'schedule' + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + run: | + if [ -z "$SLACK_WEBHOOK_URL" ]; then + echo "(no SLACK_WEBHOOK_URL set — not posted)" >&2 + exit 0 + fi + run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + text="🔴 *Supply Chain scan failed* on \`${GITHUB_REF_NAME}\` (${GITHUB_EVENT_NAME}) — known-vulnerable or known-malicious dependency detected. <${run_url}|View run>" + payload="$(node -e 'const t=require("fs").readFileSync(0,"utf8");process.stdout.write(JSON.stringify({text:t}))' <<<"$text")" + for attempt in 1 2 3; do + code="$(curl -sS --connect-timeout 10 --max-time 30 \ + -o /dev/null -w '%{http_code}' -X POST -H 'Content-type: application/json' \ + --data "$payload" "$SLACK_WEBHOOK_URL" 2>/dev/null || echo 000)" + if [ "$code" = 200 ]; then echo "✓ posted to Slack webhook"; exit 0; fi + echo "⚠️ Slack webhook POST attempt $attempt returned HTTP $code" >&2 + [ "$attempt" -lt 3 ] && sleep 5 + done + echo "✗ Slack webhook POST failed after 3 attempts" >&2 + exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0108aee13..8c3011fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.0.0-beta.13 — 2026-08-10 + +### Fixes +- Bump the `postcss` override from `8.5.23` to `8.5.26` to pull in `nanoid@^3.3.17`, clearing a high-severity OSV advisory (GHSA-2v37-7h3g-55p8) against the transitively-locked `nanoid@3.3.16`. (#670) +- Move the `Supply Chain` workflow's scheduled OSV-Scanner run from weekly to daily, and optionally post the daily run's failures to Slack when `SLACK_WEBHOOK_URL` is configured — a scheduled failure on `main` previously surfaced only when the next PR happened to touch the lockfile, since a red scheduled run has no PR author to notice it. PR and push runs don't post; those failures are already visible to whoever opened or merged the PR. The notify step needed its own `failure()` clause — a bare `if:` without one is implicitly ANDed with `success()`, so without it the step would have been silently skipped every single time (the one time it's meant to run is exactly when the job already failed) — and now retries the webhook POST 3 times, exiting nonzero on exhaustion so a broken webhook fails visibly instead of a green step that posted nothing. (#670) + ## 1.0.0-beta.13 — 2026-08-07 ### Fixes diff --git a/SECURITY.md b/SECURITY.md index 2b1cd813e..24ba3dc7b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -35,8 +35,10 @@ advisories **and** the [OpenSSF malicious-packages feed](https://github.com/ossf **Policy: block on any finding.** The gate fails on *any* known-vulnerable or malicious package in the tree — not just newly introduced ones. It runs on every -PR, on pushes to `main`, and weekly (to catch advisories disclosed after a -dependency was already merged). +PR, on pushes to `main`, and daily (to catch advisories disclosed after a +dependency was already merged). A daily run that fails on `main` — where there's +no PR author already looking at the check — also posts to Slack, via the +`SLACK_WEBHOOK_URL` repository secret. ### 2. Socket — behavioral early-warning @@ -71,3 +73,7 @@ These steps live outside the repo and require admin access: 3. *(Optional)* For a Socket CI gate in addition to the App, add a `SOCKET_SECURITY_API_KEY` repository secret and the Socket CI action — deferred until tuned, since behavioral findings can have false positives. +4. **Slack alert on a failed daily scan**: set the `SLACK_WEBHOOK_URL` repository + secret to a Slack Incoming Webhook URL (the same secret `integration-suite` + already posts to). Without it, the daily job still runs and still fails CI on a + finding — it just skips the Slack POST and logs that the secret is unset. diff --git a/bun.lock b/bun.lock index f2943e937..4f74b7cbb 100644 --- a/bun.lock +++ b/bun.lock @@ -42,7 +42,7 @@ "overrides": { "brace-expansion": "5.0.9", "eslint-plugin-react-hooks": "7.0.1", - "postcss": "8.5.23", + "postcss": "8.5.26", "sharp": "0.35.0", "undici": "7.29.0", "vite": "8.0.16", @@ -978,7 +978,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="], + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], @@ -1034,7 +1034,7 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], "posthog-node": ["posthog-node@5.47.7", "", { "dependencies": { "@posthog/core": "^1.46.4" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-ZfvGL2DQB9mQmM+9hFRtX8h4WeXRANA6ASK/6eLJuAg2BPxvLpihRyB8pmgFm6Ye+t2drZJkDklEARPj/3OWAA=="], diff --git a/package.json b/package.json index dd2476a2e..c1baf61d0 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "yaml": "^2.9.0" }, "overrides": { - "postcss": "8.5.23", + "postcss": "8.5.26", "eslint-plugin-react-hooks": "7.0.1", "vite": "8.0.16", "undici": "7.29.0",