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
66 changes: 50 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ jobs:
name: Lint, typecheck, test, build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: npm
Expand All @@ -30,28 +32,51 @@ jobs:
- run: npm run lint
- run: npm run format:check
- run: npm run typecheck
- run: npm run test
# V49 — the same suite, with a coverage floor (see vite.config.ts).
- run: npm run test:coverage
- run: npm run build
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: dist
path: dist
retention-days: 7

workflows:
name: Workflow audit (zizmor)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# V49 — a static audit of these workflows: template injection, unpinned
# or impostor action commits, credentials left on disk, over-broad
# permissions. Pinned by version and installed from PyPI rather than
# through a third-party action, so the auditor is not itself an
# unpinned dependency. The token lets the online audits check that each
# pinned SHA really belongs to the action's repository.
- name: Audit the workflows
env:
GH_TOKEN: ${{ github.token }}
run: |
pipx run --spec zizmor==1.30.1 zizmor --format=github .github/workflows

e2e:
name: End-to-end (Playwright)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npm run e2e
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: failure()
with:
name: playwright-report
Expand All @@ -65,8 +90,10 @@ jobs:
# Informational for now — scores on shared runners are noisy.
continue-on-error: true
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: npm
Expand Down Expand Up @@ -95,12 +122,12 @@ jobs:
# `quality`, `e2e` and `deploy` keep their scripts: they need esbuild to
# build and workerd to deploy.
- run: npm ci --ignore-scripts
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: dist
path: dist
- run: npx lhci autorun
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: lighthouse-report
Expand All @@ -118,13 +145,15 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: npm
- run: npm ci
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: dist
path: dist
Expand All @@ -139,7 +168,7 @@ jobs:
# the optional HF_TOKEN secret to lift the anonymous limit.
- name: Cache the sharded language model
id: llm
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: dist/llm
key: llm-sharded-${{ hashFiles('scripts/prepare-llm.mjs') }}
Expand All @@ -150,5 +179,10 @@ jobs:
run: npm run llm:prepare -- dist/llm
- name: Ensure the Pages project exists
run: npx wrangler pages project create labml --production-branch=main || true
# V49 — the branch name reaches the shell through the environment, never
# through template expansion: `${{ }}` is pasted into the script before
# bash parses it, so a branch named `x";curl …;"` would have run as code.
- name: Deploy
run: npx wrangler pages deploy dist --project-name=labml --branch="${{ github.event_name == 'push' && 'main' || github.head_ref }}"
env:
DEPLOY_BRANCH: ${{ github.event_name == 'push' && 'main' || github.head_ref }}
run: npx wrangler pages deploy dist --project-name=labml --branch="$DEPLOY_BRANCH"
8 changes: 5 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: github/codeql-action/init@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
languages: javascript-typescript
- uses: github/codeql-action/analyze@v4
- uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
10 changes: 6 additions & 4 deletions .github/workflows/llm-bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: npm
Expand All @@ -42,7 +44,7 @@ jobs:
# reads them from disk and has no 25 MiB limit to work around.
- name: Cache the pinned weights
id: weights
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .llm-cache
key: llm-cache-qwen3-0.6b-dq-q4f16-v1
Expand All @@ -58,7 +60,7 @@ jobs:
env:
LABML_LLM_OUT: bench-report.json
run: npm run llm:bench:node
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: llm-bench-report
path: bench-report.json
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ entry per wave, newest first; the first six waves (V1–V6, the MVP) predate the
are recorded in PLAN.md §B and §J. Each entry carries the opening sentence of its row and
the reason the wave was built.

## V49 — CI that cannot be steered by a branch name

The wave opened with zizmor 1.30.1 on the three workflows: **28 findings** (1 template injection, 21 actions pinned by a movable tag, 6 checkouts leaving the git token on the runner disk).

_Why:_ Approved 09/10/2026 after V48. Continues V44: a security gate is worth its maintenance only when it distinguishes a real exposure, and every rule here is now enforced by a test or a job rather than by a reviewer remembering it.

## V48 — A damaged share link ends on a refusal, never on a crash

The plan for this row was a deeper validator: check `insights.importance`, numeric metrics, `classes` and `confusion`, the fields the audit had named.
Expand Down
Loading
Loading