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
6 changes: 3 additions & 3 deletions .github/actions/setup-dev-image/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ description: Pull (or build) the afm dev image, install `just`, and enable sccac
runs:
using: composite
steps:
- uses: taiki-e/install-action@v2
- uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2
with:
tool: just

Expand All @@ -30,7 +30,7 @@ runs:
# inside the dev image. Setting the three env vars below routes
# container-side `sccache` calls through the GHA cache via the
# runner's ACTIONS_CACHE_URL + ACTIONS_RUNTIME_TOKEN.
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9

# Forward the GHA cache env into the compose services. Without these
# the container sccache silently falls back to the disk volume at
Expand Down Expand Up @@ -68,7 +68,7 @@ runs:
# fallback below. Same-repo runs always log in.
- name: Log in to GHCR (same-repo runs only)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
Expand Down
18 changes: 13 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: msrv
- run: cargo check --workspace --all-targets
Expand Down Expand Up @@ -159,7 +159,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: taiki-e/install-action@v2
- uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
with:
tool: just
- run: just verify-version-pins
Expand Down Expand Up @@ -207,15 +207,23 @@ jobs:
# Phase-1 partial (deny, audit gate cheaply on Cargo.lock alone), but
# `udeps` is nightly-bound and needs the dep graph compiled — so the
# job gates on check to skip wasted work when typecheck has already
# failed. `fail-fast: false` keeps `deny` failures from masking an
# `audit` failure and vice-versa.
# failed. `fail-fast: false` keeps one leg's failure from masking the
# others.
#
# `audit-comrak` (C1/F4) is its own matrix leg so a RUSTSEC advisory
# against the vendored path-dep comrak (which `cargo audit`/`cargo deny`
# can't see — it's absent from the registry dep graph) surfaces as a
# distinctly-named failing check. It also runs transitively as a
# dependency of the `audit` leg, so local `just ci` is covered too; the
# standalone leg just sharpens the CI signal. No scheduled workflow is
# used (user preference: no cron) — this runs per-PR.
audit:
runs-on: ubuntu-latest
needs: check
strategy:
fail-fast: false
matrix:
target: [deny, audit, udeps]
target: [deny, audit, audit-comrak, udeps]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/setup-dev-image
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
# *changes* a PR introduces and comments the diff directly in the
# review UI.
#
# Action pinning: @v<major> tags for now; Dependabot will upgrade to
# commit SHA pins on its first sweep.
# Action pinning: the actions below are pinned to full commit SHAs
# (the `# vX.Y.Z` trailer is the resolved tag); Dependabot's
# github-actions ecosystem keeps the SHAs current.

name: dependency-review

Expand All @@ -24,9 +25,9 @@ jobs:
name: dependency review
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.2

- uses: actions/dependency-review-action@v5
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
# Match the cargo-deny policy: reject any newly introduced
# advisory of moderate severity or worse.
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/dev-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,31 +49,31 @@ jobs:
name: build & push (ghcr.io/p4suta/afm-dev)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.2

- uses: docker/setup-buildx-action@v4
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Log in to GHCR
# PR builds from forks must not have GHCR credentials — we still
# build to verify but skip the login + push there.
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract image metadata
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ghcr.io/p4suta/afm-dev
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long

- name: Build and push (ci stage)
uses: docker/build-push-action@v7
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: Dockerfile
Expand Down
22 changes: 13 additions & 9 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
# WASM_PACK_VERSION arg so dev and CI agree on the wasm-bindgen-cli
# that gets auto-fetched.
#
# Action pinning: @v<major> tags are intentional pre-first-run;
# Dependabot will upgrade them to commit SHAs on its first weekly sweep.
# Action pinning: every third-party action below is pinned to a full
# commit SHA (the `# vX.Y.Z` trailer is the tag it resolved to).
# Dependabot's github-actions ecosystem keeps those SHAs current.
# `dtolnay/rust-toolchain@stable` is the deliberate exception — its ref
# is the moving `stable` branch (we want the newest stable rustc), and
# dependabot.yml ignores it.

name: docs

Expand All @@ -43,15 +47,15 @@ jobs:
name: build site
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.2

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: docs

- uses: peaceiris/actions-mdbook@v2
- uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0
with:
mdbook-version: latest

Expand All @@ -68,13 +72,13 @@ jobs:
run: mdbook build crates/afm-book

- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
# Pinned alongside the Dockerfile BUN_VERSION arg.
bun-version: '1.3.14'

- name: Install wasm-pack
uses: jetli/wasm-pack-action@v0.4.0
uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
with:
# Pinned alongside the Dockerfile WASM_PACK_VERSION arg.
version: 'v0.15.0'
Expand All @@ -100,7 +104,7 @@ jobs:
# GitHub Pages otherwise runs Jekyll and hides paths like `_sources`.
touch site/.nojekyll

- uses: actions/upload-pages-artifact@v5
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: site

Expand All @@ -112,5 +116,5 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/deploy-pages@v5
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
id: deployment
28 changes: 15 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
# toolchain here. See CONTRIBUTING.md "How to release" for the full
# rationale.
#
# Action pinning: we use `@v<major>` tags below rather than commit SHAs
# because Dependabot (`.github/dependabot.yml`) only discovers and
# SHA-pins new workflow files on its weekly sweep. Once that sweep has
# run, the pins will be upgraded automatically and this file will align
# with `ci.yml`'s commit-SHA convention.
# Action pinning: every third-party action below is pinned to a full
# commit SHA (the `# vX.Y.Z` trailer is the human-readable tag it
# resolved to) so a re-pointed tag can't silently change what runs in a
# release. Dependabot's github-actions ecosystem bumps these SHAs weekly.
# The sole exception is `dtolnay/rust-toolchain@stable`: its ref is the
# moving `stable` branch by design (we want the latest stable rustc for
# release artefacts), and dependabot.yml ignores it for that reason.

name: release

Expand Down Expand Up @@ -50,7 +52,7 @@ jobs:
runner: windows-latest
archive: zip
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.2

- uses: dtolnay/rust-toolchain@stable

Expand All @@ -59,7 +61,7 @@ jobs:
# add` is idempotent and unambiguous on every cache state.
- run: rustup target add ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: release-${{ matrix.target }}

Expand Down Expand Up @@ -116,7 +118,7 @@ jobs:
echo "name=${name}" >> "$GITHUB_OUTPUT"

- name: Upload build artefacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.archive.outputs.name }}
path: |
Expand All @@ -134,9 +136,9 @@ jobs:
id-token: write
attestations: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.2

- uses: actions/download-artifact@v8
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: afm-*
merge-multiple: true
Expand All @@ -153,14 +155,14 @@ jobs:
cat SHA256SUMS

- name: Attest build provenance
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-path: |
artefacts/afm-*.tar.gz
artefacts/afm-*.zip

- name: Generate release notes with git-cliff
uses: orhun/git-cliff-action@v4
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0
with:
config: cliff.toml
args: --latest --strip all
Expand All @@ -171,7 +173,7 @@ jobs:
- name: Show release notes
run: cat CHANGES.md

- uses: softprops/action-gh-release@v3
- uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
files: |
artefacts/afm-*.tar.gz
Expand Down
25 changes: 21 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,26 @@
# Every developer and CI job runs inside this image. Host toolchain is never invoked.
#
# Layered so upstream-sync / dependency bumps rebuild minimal surface.

ARG RUST_VERSION=1.95.0
#
# External base images (rust, playwright) are pinned by immutable
# manifest-list digest (supply-chain hardening, C2/F9): a floating tag
# like `rust:1.95.0-bookworm` can be re-pushed, so we pin the sha256 and
# keep the human-readable tag inline. Dependabot's `docker` ecosystem
# (.github/dependabot.yml) bumps the tag AND the digest together on its
# weekly sweep, so the pin stays current without a human resolving the
# sha by hand. Resolve a fresh digest with
# `docker buildx imagetools inspect rust:1.95.0-bookworm`.
#
# NODE_VERSION stays an ARG: it parameterises an apt source URL in the
# node-base stage (deb.nodesource.com/setup_<N>.x), not a FROM line, so
# there is no base-image digest to pin for it.
ARG NODE_VERSION=22

########################################################################
# Stage: toolchain — Rust stable + system deps for builds and CJK work
########################################################################
FROM rust:${RUST_VERSION}-bookworm AS toolchain
# rust:1.95.0-bookworm (digest pinned; tag kept for humans / Dependabot)
FROM rust:1.95.0-bookworm@sha256:6258907abe69656e41cd992e0b705cdcfabcbbe3db374f92ed2d47121282d4a1 AS toolchain

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
Expand Down Expand Up @@ -270,8 +282,13 @@ CMD ["mdbook", "serve", "--hostname", "0.0.0.0", "--port", "3000"]

########################################################################
# Stage: browser — Playwright with Chromium + WebKit for M3 onward
#
# Pinned by digest (see the toolchain-stage note at the top); Dependabot
# bumps the tag + sha together. Refresh via
# `docker buildx imagetools inspect mcr.microsoft.com/playwright:v1.60.0-jammy`.
########################################################################
FROM mcr.microsoft.com/playwright:v1.60.0-jammy AS browser
# mcr.microsoft.com/playwright:v1.60.0-jammy (digest pinned; tag kept for humans / Dependabot)
FROM mcr.microsoft.com/playwright:v1.60.0-jammy@sha256:e1529a04087193966ea15d4a1617345bdaa0791690a24ab2c42b65f9ce5b2cdc AS browser

WORKDIR /workspace
CMD ["bash"]
60 changes: 58 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,66 @@ verify-version-pins:
deny:
{{_dev}} cargo deny check

# RustSec advisory scan
audit:
# RustSec advisory scan.
#
# Depends on `audit-comrak` so the vendored-comrak advisory gate runs in
# the same invocation: `cargo audit` alone keys advisories off the
# *registry* dependency graph in `Cargo.lock`, and comrak is a PATH dep
# (`upstream/comrak/`, ADR-0001) — it never appears there, so a future
# RUSTSEC advisory against comrak 0.52.0 would be invisible to the plain
# scan. `audit-comrak` closes that blind spot. Both are wired into
# `just ci` via this recipe (and the `audit` matrix leg in ci.yml).
audit: audit-comrak
{{_dev}} cargo audit

# Vendored-comrak RUSTSEC gate (C1/F4).
#
# comrak is vendored as a path dependency at `upstream/comrak/` (version
# in `upstream/comrak/Cargo.toml`, sha in `upstream/comrak/COMRAK_SHA`),
# pinned bit-for-bit to upstream v0.52.0 (ADR-0001, 0-line diff). Because
# it is a path dep it is absent from the registry dependency graph
# `cargo audit` / `cargo deny` walk, so neither tool would ever flag a
# RUSTSEC advisory filed against the `comrak` crate at our pinned
# version. This recipe re-introduces that coverage WITHOUT a scheduled
# workflow (user preference: no cron): it runs per-PR as part of
# `just ci`.
#
# Mechanism: synthesise a one-crate `Cargo.lock` that declares `comrak`
# at exactly the vendored version as a crates.io registry package, then
# point the real `cargo audit` at it (`--file`). This delegates the
# advisory version-range matching to the authoritative RustSec engine
# (no hand-rolled semver parsing) and uses the same advisory-db
# `cargo audit` already fetches. `--deny warnings` makes ANY advisory
# match — vulnerability, unmaintained, or yanked notice keyed to comrak
# — fail the gate. The version is read from `upstream/comrak/Cargo.toml`
# so there is no second source of truth to drift.
#
# Exit semantics: clean (no advisory affects the pinned version) → 0;
# an advisory matches → non-zero, halting `just ci`. On a hit, read the
# RUSTSEC id it prints, then either bump the vendored tree
# (`just upstream-sync <tag>`) past the patched version or, if the
# advisory does not apply to how afm drives comrak, record a documented
# `ignore` in the gate (see afm/SECURITY.md "Vendored comrak").
audit-comrak:
{{_dev}} bash -c '\
set -euo pipefail; \
ver=$(grep -m1 -E "^version[[:space:]]*=" upstream/comrak/Cargo.toml | sed -E "s/.*\"([^\"]+)\".*/\\1/"); \
if [ -z "$ver" ]; then echo "audit-comrak: could not read comrak version from upstream/comrak/Cargo.toml" >&2; exit 1; fi; \
echo "audit-comrak: checking vendored comrak $ver against RUSTSEC advisories"; \
lock=$(mktemp -d)/Cargo.lock; \
printf "%s\n" \
"# Synthetic lockfile — generated by just audit-comrak (C1/F4)." \
"# Pins the vendored comrak version as a registry crate so" \
"# cargo audit can match RUSTSEC advisories the path dep hides." \
"version = 3" \
"" \
"[[package]]" \
"name = \"comrak\"" \
"version = \"$ver\"" \
"source = \"registry+https://github.com/rust-lang/crates.io-index\"" \
> "$lock"; \
cargo audit --file "$lock" --deny warnings'

# Unused dependency scan (requires nightly)
udeps:
{{_fuzz}} cargo +nightly udeps --workspace --all-targets
Expand Down
Loading
Loading