Repository navigation
191 lines (172 loc) · 7.77 KB
/
Copy pathfuzz.yml
File metadata and controls
191 lines (172 loc) · 7.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
name: Fuzz (crates)
# libFuzzer fuzzing for the stack crates' untrusted-input parsers (the
# detached `packages/*/fuzz/` crates and the `fuzz:*` mise tasks). Ported from
# cipherstash-suite's `fuzz.yml`, for the six targets in the imported set; the
# suite keeps its three cts-common targets. Two jobs with different roles:
#
# * fuzz-regression (pull_request and manual, blocking on PRs): builds every harness, which
# catches harness and API drift, and replays the committed seed corpus with
# `-runs=0`. Deterministic, so it is safe to gate PRs.
#
# * fuzz-campaign (schedule and manual, never on PRs): the time-boxed
# bug-finding run. Each target's corpus persists across runs in the Actions
# cache, so coverage compounds; it is minimised with `cargo fuzz cmin`, and
# any crash reproducer is uploaded as an artifact.
#
# cargo-fuzz needs nightly; the tasks run `cargo +nightly fuzz`. Pull requests
# are the only filtered event, so `push` has no filter to mirror; recorded in
# scripts/__tests__/workflow-paths-filter-parity.test.mjs.
on:
pull_request:
paths:
- packages/stack-auth/**
- packages/stack-kms/**
- packages/stack-encrypt/**
- Cargo.toml
- Cargo.lock
- mise.toml
- mise.test.toml
- .github/workflows/fuzz.yml
# Keep these excludes last so docs-only changes are skipped.
- "!**.md"
- "!**.example"
schedule:
# Nightly at 04:47 UTC. Scheduled runs fire from the default branch only.
- cron: "47 4 * * *"
workflow_dispatch:
inputs:
max_total_time:
description: "Seconds to fuzz each target (campaign job)"
default: "120"
defaults:
run:
shell: bash
permissions:
contents: read
env:
# The cargo tools are pinned in mise.test.toml, which mise loads only in
# the test environment. With no install_args, mise-action installs (and
# caches) the whole test toolset once: `mise run` and `mise x` install any
# tool of the toolset that is missing, uncached, so narrowing the install
# would only move the rest out of the cache.
MISE_ENV: test
RUST_BACKTRACE: full
CARGO_TERM_COLOR: always
CARGO_NET_GIT_FETCH_WITH_CLI: true
jobs:
fuzz-regression:
name: fuzz regression (${{ matrix.slug }})
# Not on the nightly schedule, which is the campaign's. A manual dispatch
# runs both jobs.
if: github.event_name != 'schedule'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- { task: "fuzz:access-key", slug: access-key }
- { task: "fuzz:jwt-decode", slug: jwt-decode }
- { task: "fuzz:client-key", slug: client-key }
- { task: "fuzz:sealed-value", slug: sealed-value }
- { task: "fuzz:term-decode", slug: term-decode }
- { task: "fuzz:check-record", slug: check-record }
- { task: "fuzz:plan-build", slug: plan-build }
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2404
- name: Install the nightly toolchain (cargo-fuzz requires it)
run: rustup toolchain install nightly --profile minimal
# Each fuzz crate is its own workspace with its own target/.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: |
packages/stack-auth/fuzz
packages/stack-kms/fuzz
packages/stack-encrypt/fuzz
key: ${{ matrix.slug }}
# `-runs=0` replays the committed seed corpus once and exits without
# fuzzing. The trailing args override the task's `-max_total_time`.
- name: Build the harness and replay the seed corpus (${{ matrix.slug }})
run: mise run ${{ matrix.task }} -- -runs=0
fuzz-campaign:
name: fuzz campaign (${{ matrix.slug }})
if: github.event_name != 'pull_request'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
# `dir` and `target` drive the corpus path and `cargo fuzz cmin`.
include:
- { task: "fuzz:access-key", slug: access-key, dir: packages/stack-auth, target: access_key_parse }
- { task: "fuzz:jwt-decode", slug: jwt-decode, dir: packages/stack-auth, target: jwt_decode }
- { task: "fuzz:client-key", slug: client-key, dir: packages/stack-kms, target: client_key_encoded }
- { task: "fuzz:sealed-value", slug: sealed-value, dir: packages/stack-encrypt, target: sealed_value_decode }
- { task: "fuzz:term-decode", slug: term-decode, dir: packages/stack-encrypt, target: term_decode }
- { task: "fuzz:check-record", slug: check-record, dir: packages/stack-encrypt, target: check_record }
- { task: "fuzz:plan-build", slug: plan-build, dir: packages/stack-encrypt, target: plan_build }
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2404
- name: Install the nightly toolchain (cargo-fuzz requires it)
run: rustup toolchain install nightly --profile minimal
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: ${{ matrix.dir }}/fuzz
key: ${{ matrix.slug }}
# A cache key is write-once, so save under a per-run key and restore the
# most recent prior corpus by prefix. The committed seeds come from the
# checkout and merge with the restored corpus at run time.
- name: Restore the corpus
uses: actions/cache/restore@v4
with:
path: ${{ matrix.dir }}/fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.slug }}-${{ github.run_id }}
restore-keys: fuzz-corpus-${{ matrix.slug }}-
# The trailing `-max_total_time` (last value wins) overrides the task's.
- name: Fuzz ${{ matrix.slug }}
env:
MAX_TOTAL_TIME: ${{ github.event.inputs.max_total_time || '120' }}
run: mise run ${{ matrix.task }} -- "-max_total_time=$MAX_TOTAL_TIME"
# Drops inputs that add no coverage, so the saved corpus stays small.
# Skipped when the fuzz step found a crash.
- name: Minimise the corpus (${{ matrix.slug }})
working-directory: ${{ matrix.dir }}
env:
TARGET: ${{ matrix.target }}
run: |
cargo +nightly fuzz cmin "$TARGET" --sanitizer none --target "$(rustc -vV | sed -n 's/^host: //p')"
# Saved even on a crash: the grown corpus is still worth keeping, and the
# crash input lives in artifacts/, not corpus/.
- name: Save the corpus
if: always()
uses: actions/cache/save@v4
with:
path: ${{ matrix.dir }}/fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.slug }}-${{ github.run_id }}
- name: Upload the crash reproducer
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: fuzz-artifacts-${{ matrix.slug }}
path: ${{ matrix.dir }}/fuzz/artifacts/**
if-no-files-found: ignore