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
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,37 @@ on:
pull_request:
branches: [main]

# ── GITHUB_TOKEN permission policy ────────────────────────────────────────────
#
# Hardened to the minimum required by every job in this workflow.
#
# Why contents: read and nothing else:
# All jobs here only need to check out source code (contents: read).
# None of them write commits, create releases, post PR comments, call the
# Packages API, or use any other GitHub API scope. GITHUB_STEP_SUMMARY
# writes are local runner file writes — not a GitHub API permission.
#
# Trigger coverage:
# For `pull_request` events from forks, GitHub already restricts the token
# to read-only. For `push` to `main` from the same repo, the default token
# carries `contents: write`. This explicit block overrides both trigger
# types and removes that unneeded write privilege.
#
# Future jobs that need elevated scope:
# Add an explicit job-level `permissions:` override with only the additional
# scope that job requires (e.g. `pull-requests: write` for a PR comment job).
# Do NOT widen the top-level block.
#
# Per-job audit (all trigger types, checked 2026-08-31):
# fmt — checkout only → contents: read ✓
# contract — checkout, clippy, test → contents: read ✓
# wasm-size — checkout, build, STEP_SUMMARY → contents: read ✓
# proptest — checkout, cargo test → contents: read ✓
# audit — checkout, cargo audit → contents: read ✓
# mutants — checkout, cargo mutants → contents: read ✓
permissions:
contents: read

# Cancel in-progress runs for the same branch/PR
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down Expand Up @@ -34,6 +65,7 @@ jobs:
contract:
name: Contract (${{ matrix.crate }} / ${{ matrix.toolchain }})
runs-on: ubuntu-latest
# Needs: contents: read (checkout only). Inherits workflow-level minimum.
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -175,6 +207,7 @@ jobs:
proptest:
name: Bond-conservation proptest
runs-on: ubuntu-latest
# Needs: contents: read (checkout only). Inherits workflow-level minimum.
defaults:
run:
working-directory: intent_settlement
Expand Down Expand Up @@ -218,6 +251,9 @@ jobs:
mutants:
name: Mutation testing
runs-on: ubuntu-latest
# Needs: contents: read (checkout only). cargo-mutants mutates source in a
# temporary copy on the runner — no GitHub API calls. Inherits workflow-level
# minimum.
defaults:
run:
working-directory: intent_settlement
Expand Down
42 changes: 42 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,48 @@ once they are merged:
| `WASM size gate` | `ci.yml` (planned) | Blocks merges that grow the wasm by > N KB |
| `Coverage` | `coverage.yml` (planned) | Advisory until a baseline is established |

### GITHUB_TOKEN permission model

All jobs in `ci.yml` run under a **`permissions: contents: read`** top-level
policy. This is the minimum required: every job only needs to check out source
code.

**Why this matters:** Without an explicit `permissions:` block, GitHub applies
its default token scopes, which include `contents: write` for `push` events on
the same repository. A compromised third-party action (e.g. `Swatinem/rust-cache`,
`dtolnay/rust-toolchain`) or a malicious PR-triggered step would then have
write access to the repository — more privilege than any CI job here actually
needs.

**Per-job audit (checked 2026-08-31):**

| Job | What it does | Minimum scope |
|---|---|---|
| `fmt` | Checkout + `cargo fmt --check` | `contents: read` |
| `contract` | Checkout + clippy + test + (optional) fmt | `contents: read` |
| `wasm-size` | Checkout + build + write to `$GITHUB_STEP_SUMMARY` (local runner file, not a GitHub API call) | `contents: read` |
| `proptest` | Checkout + `cargo test` | `contents: read` |
| `audit` | Checkout + `cargo audit` (queries RustSec DB over HTTPS, not the GitHub API) | `contents: read` |
| `mutants` | Checkout + `cargo mutants` (mutates source in a runner-local temp copy) | `contents: read` |

**Adding a job that needs elevated scope:**

If a future job needs to post PR comments, push a commit, create a release, or
call any other GitHub API, add a **job-level** `permissions:` override with
only the additional scope that specific job requires. Do not widen the
workflow-level block:

```yaml
jobs:
my-new-job:
permissions:
contents: read # still needed for checkout
pull-requests: write # needed to post a PR comment — only grant here
```

See the [GitHub docs on workflow permissions](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
for the full list of available scopes.

### MSRV policy

The declared MSRV is **Rust 1.78** (see README.md). CI enforces this via a
Expand Down
Loading