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
43 changes: 35 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ permissions:
jobs:
ci:
name: Lint, build, verify
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand All @@ -31,8 +31,9 @@ jobs:
bun-version: 1.3.13

# Bun is the package manager and script runner, but Next.js (and tsc)
# run on Node. ubuntu-latest's default Node version drifts; pin via
# .nvmrc so a future GitHub bump can't break the build silently.
# run on Node. The runner image is pinned (ubuntu-24.04) but Node
# version inside it can still drift; pin via .nvmrc so a future
# GitHub bump can't break the build silently.
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
Expand Down Expand Up @@ -153,20 +154,46 @@ jobs:

# Runs only on PRs (no baseline diff to compute on a push to main).
# Compares the PR's dependency manifest against main and flags
# high-severity advisories or license incompatibilities. Posts a summary
# comment on the PR when it finds something. continue-on-error while we
# establish a baseline of acceptable findings.
# high-severity advisories or license incompatibilities. Hard-gated to
# match the `bun audit` posture: a PR introducing a new high-severity
# advisory must block merge, not just post a comment.
dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Dependency Review
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
continue-on-error: true
with:
fail-on-severity: high
comment-summary-in-pr: on-failure

# Defense-in-depth on top of GitHub's push protection. Push protection
# covers high-entropy / known-provider patterns at push time; TruffleHog
# re-scans diffs on PRs + push to main, catching secrets that slipped
# past push protection (low-entropy formats, detector patterns added
# after the secret was committed, or push-protection bypass via the
# "I'll fix it later" allow path).
secret-scan:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# TruffleHog diffs base..head and needs the full history present.
fetch-depth: 0
- name: TruffleHog scan
uses: trufflesecurity/trufflehog@17456f8c7d042d8c82c9a8ca9e937231f9f42e26 # v3.95.2
with:
# On PRs: scan the diff between base and head. On push to main:
# scan the previous commit to HEAD. The action infers both from
# the event context.
# --results=verified,unknown drops trufflehog's "unverified" tier
# (pattern-matched but couldn't reach the verifier endpoint) to
# keep noise down without losing coverage of secrets whose
# verifiers don't recognize them yet.
extra_args: --results=verified,unknown
2 changes: 1 addition & 1 deletion .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: write
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/lighthouse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ concurrency:
jobs:
lighthouse:
name: Lighthouse audit
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand All @@ -27,8 +27,9 @@ jobs:
bun-version: 1.3.13

# Bun is the package manager and script runner, but Next.js (and the
# lhci binary) run on Node. Pin Node via .nvmrc so a future GitHub
# bump can't break the audit silently.
# lhci binary) run on Node. The runner image is pinned (ubuntu-24.04)
# but Node version inside it can still drift; pin via .nvmrc so a
# future GitHub bump can't break the audit silently.
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
Expand Down
88 changes: 88 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Scorecard

# OpenSSF Scorecard — weekly supply-chain hygiene grade for this repo.
# Checks include: branch protection, pinned dependencies, signed releases,
# token permissions, dangerous workflow patterns, dependency-update
# tooling, vulnerability backlog, and code review coverage. Results post
# to GitHub's Security tab as SARIF and (when publish_results is on) get
# published to the public Scorecard registry at https://scorecard.dev.
#
# Required setup (one-time):
# - Settings → Code security → Enable "Code scanning" so the SARIF
# upload has somewhere to land. Without it the upload step no-ops.
#
# Runs on:
# - branch_protection_rule events (re-grades whenever protection changes)
# - schedule (weekly Monday 08:00 UTC — keeps the grade current as
# dependencies and Actions versions drift)
# - push to main (catches workflow changes immediately)
# - workflow_dispatch (manual re-run when investigating a finding)
#
# Note on PRs: Scorecard is intentionally NOT triggered on `pull_request`.
# Every check Scorecard runs (branch protection state, pinned dependencies
# in main, code-review-coverage history, vulnerability backlog) reflects
# `main` as it currently sits — running on a PR would just re-grade main,
# not preview the PR's effect. The weekly schedule + push-to-main triggers
# are the right cadence.

on:
branch_protection_rule:
schedule:
- cron: "0 8 * * 1"
push:
branches: [main]
workflow_dispatch:

# Cancel an in-flight schedule/push run if a new one starts on the same ref.
concurrency:
group: scorecard-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# Least-privilege default; the job opts up explicitly for SARIF upload +
# OIDC publish.
permissions: read-all

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-24.04
permissions:
# Needed by github/codeql-action/upload-sarif to write Security tab
# entries.
security-events: write
# Needed by scorecard's `publish_results: true` — OIDC token proves
# to scorecard.dev that the run came from this repo.
id-token: write
contents: read
# Required for org-level scorecard repo discovery (no-op for a
# single-repo case, but the action documents it as required).
actions: read

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Run Scorecard
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# Publish to https://scorecard.dev so the badge + history are
# visible. Set to false to keep results private to the
# Security tab.
publish_results: true

# Retained for 7 days as a fallback when the Security tab view is
# truncated or the SARIF upload fails.
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 7

- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
sarif_file: results.sarif
25 changes: 22 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,30 @@ This is a Next.js 15 marketing website for Vortex, a columnar file format. The s
- `src/app/page.tsx` - Homepage with metadata for SEO
- `src/components/hero/index.tsx` - Complex WebGL rendering with custom shaders
- `src/app/layout.tsx` - Root layout with analytics providers and font loading
- `next.config.ts` - Plausible proxy configuration
- `next.config.mjs` - Plausible proxy configuration, security headers (CSP, HSTS, Permissions-Policy)

The site is optimized for performance with font optimization, analytics integration, and responsive WebGL rendering.

## Audit advisories
## Supply chain hardening

Defense in depth against the npm-worm class (Shai-Hulud, mini-Shai-Hulud, the May 2026 TanStack incident, etc.). Layers, in order of which one trips first when a bad package surfaces:

1. **`bunfig.toml` → `install.minimumReleaseAge = 1209600`** (14 days). Bun refuses to resolve to a package version younger than 14 days. Applies to every `bun install` / `bun add` / `bun update` (local dev, lockfile regens, and Vercel builds), so we can't accidentally pull a freshly published version that hasn't had time to be observed. If a fresh dep is genuinely needed before the window elapses, allowlist it via `install.minimumReleaseAgeExcludes`.
2. **`renovate.json` → `minimumReleaseAge: "14 days"`** — symmetric with #1, applied at PR-proposal time. Renovate won't open a PR for a version younger than the window, and `bunfig.toml` won't let Bun resolve to one either. Both gates are required: a local `bun add` bypasses Renovate; a Renovate `lockFileMaintenance` cycle would otherwise pull fresh transitives.
3. **`trustedDependencies` in `package.json`** — explicit allowlist for which packages may run lifecycle scripts (`preinstall` / `install` / `postinstall`). Bun's default behavior is name-only trust against a built-in ~366-package allowlist, which lets a transitive named like a popular package hijack scripts (the PackageGate class of attack). Current set: `["esbuild", "sharp"]` — esbuild's `postinstall` builds its native bin (pulled in transitively via velite); sharp's `install` builds libvips for next/image. When adding a top-level dep that ships native bins or needs a build step, audit its lifecycle scripts via `bun pm untrusted`, then extend this list with one-line justification in the commit message.
4. **`bun audit`** is the CI hard gate on every PR and push to main. When a new advisory surfaces, the resolution is one of three:
- **Direct dep bump** — if the advisory is in a top-level dep with a patched release, bump in `package.json`.
- **`overrides` entry** — if the advisory is in a transitive dep whose parent hasn't released a fix, force-pin the patched version in `package.json`'s `overrides` block. This is the most common case. Pick the latest patched version that is ≥14 days old (matching the cooldown policy) so a fresh install can't resolve to a too-new version.
- **Document and ignore** — if no upstream fix exists yet, append `--ignore=GHSA-...` to the `Dependency audit` step in `.github/workflows/ci.yml` and add an entry under "Audit advisories" below with: GHSA ID, vulnerable range, package, why exposure is acceptable (e.g. dev-only, not in client bundle), and a removal trigger.
- When a parent dep eventually patches its own transitive, drop the corresponding `overrides` entry — leaving stale overrides means we keep deduping a fix that was already merged upstream.
5. **`actions/dependency-review-action`** runs on every PR — hard-fails the build if a PR introduces a new advisory at severity `high` or above. Catches what `bun audit` would catch on the merge commit, but earlier in the review loop.
6. **`trufflesecurity/trufflehog`** secret scan runs on every PR and push to main. Defense-in-depth on top of GitHub's push protection; catches verified secrets that slipped past push protection (low-entropy formats, detector patterns added after the secret was committed, or push-protection bypass).
7. **OpenSSF Scorecard** (`.github/workflows/scorecard.yml`) grades the repo weekly on supply-chain hygiene (pinned actions, branch protection, token permissions, dangerous workflow patterns). SARIF posts to the Security tab; aggregate score publishes to https://scorecard.dev.
8. **All third-party GitHub Actions are SHA-pinned**, not tag-pinned. A tag can be moved to point at a malicious commit (and has been, in prior supply-chain incidents); a SHA can't. Renovate's `helpers:pinGitHubActionDigests` preset enforces this on auto-bump PRs. When bumping an action manually, update both the SHA and the trailing `# vX.Y.Z` comment in the same diff.
9. **`runs-on: ubuntu-24.04`** (not `ubuntu-latest`) so runner-image bumps are deliberate PRs, not silent infrastructure drift. The Node version inside the runner image can still drift; `.nvmrc` pins that separately.
10. **Renovate auto-merge** is patch + minor only via `:automergeStableNonMajor`. Major bumps stay open for human review. The 14-day cooldown is the first gate; CI (lint, build, typecheck, `bun audit`, `dependency-review`, `secret-scan`, `bun run verify`, Playwright, Lighthouse) is the second.

### Audit advisories

`bun audit` is the source of truth for dependency advisories. State as of 2026-05-04:

Expand All @@ -54,4 +73,4 @@ The site is optimized for performance with font optimization, analytics integrat
- **uuid `<14.0.0`** (GHSA-w5hq-g745-h8pq, moderate missing buffer bounds in v3/v5/v6 when `buf` provided). **Upstream-blocked.** Two parent paths: `resend@6.12.2 → svix@1.90.0 → uuid@^10.0.0` and `@lhci/cli@0.15.1 → uuid@8.3.2`. Neither parent admits a 14.x override without risking CJS imports. Exposure is theoretical on both: `/api/subscribe` uses Resend's send-email endpoint (not svix's webhook-signing path), `@lhci/cli` is dev-only and runs in CI on its own controlled inputs, and the vulnerable code (v3/v5/v6 with explicit `buf`) isn't called by either. Remove the `--ignore` when both parents ship releases bumping uuid to `^14.0.0`.
- **tmp `<=0.2.3`** (GHSA-52f5-9888-hmc6, low symbolic-link path traversal in `dir` param). **Upstream-blocked.** Pulled exclusively by `@lhci/cli@0.15.1` (dev-only, runs in CI on controlled inputs). The symlink-traversal scenario doesn't apply. Remove the `--ignore` when `@lhci/cli` ships a release with patched transitives.

CI hard-gates on `bun audit` (`.github/workflows/ci.yml`) with `--ignore=GHSA-w5hq-g745-h8pq` and `--ignore=GHSA-52f5-9888-hmc6` for the upstream-blocked advisories. Any new advisory fails the job. The `dependency-review-action` PR job is a separate gate (license/severity-focused) that remains `continue-on-error: true` while a baseline of acceptable findings is established.
CI hard-gates on `bun audit` (`.github/workflows/ci.yml`) with `--ignore=GHSA-w5hq-g745-h8pq` and `--ignore=GHSA-52f5-9888-hmc6` for the upstream-blocked advisories. Any new advisory fails the job.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# vortex.dev

[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/vortex-data/vortex.dev/badge)](https://scorecard.dev/viewer/?uri=github.com/vortex-data/vortex.dev)

Source for [vortex.dev](https://vortex.dev), the marketing site for [Vortex](https://github.com/vortex-data/vortex) — an extensible, state-of-the-art columnar file format. Vortex is a [Linux Foundation](https://www.linuxfoundation.org/) incubating project, a Series of LF Projects, LLC.

## Local development
Expand Down
4 changes: 4 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Bun configuration. Project-scoped — Bun merges this with $HOME/.bunfig.toml.

[install]
# Refuse to install package versions younger than 14 days. Mirrors the
# Renovate minimumReleaseAge in renovate.json so a local `bun install`,
# `bun add`, `bun update`, or a Vercel build can't pull a freshly
# published (and potentially compromised) version that Renovate would
# have held back.
#
# The window matters because npm worms (Shai-Hulud, mini-Shai-Hulud, the
# May 2026 TanStack incident) propagate, get detected, and get yanked
# within hours-to-days; 14 days is the observed safe horizon for those
# events to have surfaced.
#
# Units: seconds. 14 * 86400 = 1209600.
minimumReleaseAge = 1209600
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
"tailwindcss": "4.2.3",
"typescript": "^6.0.3"
},
"trustedDependencies": [
"esbuild",
"sharp"
],
"overrides": {
"postcss": "8.5.10",
"mdast-util-to-hast": "^13.2.1"
Expand Down
1 change: 1 addition & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"enabled": true
},
"automergeStrategy": "squash",
"minimumReleaseAge": "14 days",
"rebaseWhen": "conflicted",
"platformAutomerge": true,
"labels": ["dependencies"],
Expand Down
Loading