Skip to content
Draft
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
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
name: CI

on:
pull_request:
# Push to main, non-draft pull requests and the weekly schedule run this through
# pipeline.yml (`workflow_call`), in parallel with the other checks and behind its `gate` job.
workflow_call:
secrets:
CODECOV_TOKEN:
required: false
workflow_dispatch:

permissions:
actions: read
contents: read
pull-requests: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# CodeQL code scanning for every language in the repo that supports `build-mode: none`;
# results land in the Security tab. Repo default setup must stay off —
# GitHub rejects advanced-workflow uploads while it is on.
name: codeql

on:
# Push to main, non-draft pull requests and the weekly schedule run this through
# pipeline.yml (`workflow_call`), in parallel with the other checks and behind its `gate` job.
workflow_call:
workflow_dispatch:

jobs:
analyze:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
actions: read
strategy:
fail-fast: false
matrix:
# `build-mode: none` for all: every language is extracted from source, so no build runs here.
# Not scanned: swift (no `build-mode: none` support), java-kotlin (Kotlin only, and
# none mode extracts Java only), ruby (only a CocoaPods Podfile, no Ruby sources), python
# (only vendored WAMR test scripts).
language: [actions, c-cpp, javascript-typescript, rust]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
languages: ${{ matrix.language }}
build-mode: none
queries: security-and-quality
- uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
category: /language:${{ matrix.language }}
89 changes: 89 additions & 0 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Gated pipeline. ci, codeql, semgrep and snyk run in parallel as reusable workflows; `gate`
# passes only when all four succeeded. Anything downstream (deploy, release, publish) must
# `needs: gate`. Draft pull requests skip every job, the gate included, until marked ready.
# Secrets are passed explicitly (no `secrets: inherit`): each child gets only what it uses.
name: pipeline

on:
push:
branches: [main]
pull_request:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review]
schedule:
- cron: "17 4 * * 1"
workflow_dispatch:

concurrency:
group: pipeline-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
ci:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
uses: ./.github/workflows/ci.yml
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# ci.yml's jobs request these (e.g. revert-on-failure); a called workflow cannot exceed them.
permissions:
actions: read
contents: read
pull-requests: write

codeql:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
uses: ./.github/workflows/codeql.yml
permissions:
security-events: write
contents: read
actions: read

semgrep:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
uses: ./.github/workflows/semgrep.yml
permissions:
security-events: write
contents: read
actions: read

snyk:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
uses: ./.github/workflows/snyk.yml
secrets:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
permissions:
security-events: write
contents: read
actions: read

gate:
needs: [ci, codeql, semgrep, snyk]
# !cancelled(): run even when a need failed, was cancelled or skipped, so the gate reports it,
# but not when the whole run is cancelled (always() would hold the run and its concurrency
# group until a runner picks up the gate). Skipped on draft PRs like every other job, so a
# draft run never leaves a green `gate` check behind.
if: ${{ !cancelled() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
runs-on: ubuntu-latest
steps:
- name: Require every job to succeed
env:
R_CI: ${{ needs.ci.result }}
R_CODEQL: ${{ needs.codeql.result }}
R_SEMGREP: ${{ needs.semgrep.result }}
R_SNYK: ${{ needs.snyk.result }}
run: |
# Only `success` passes: failure, cancelled and skipped all fail the gate.
fail=0
for pair in "ci=$R_CI" "codeql=$R_CODEQL" "semgrep=$R_SEMGREP" "snyk=$R_SNYK"; do
name=${pair%%=*}
result=${pair#*=}
echo "$name: $result"
if [ "$result" != "success" ]; then
echo "::error title=gate::$name finished with '$result'"
fail=1
fi
done
exit "$fail"
32 changes: 32 additions & 0 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Semgrep static analysis with the free open-source CLI and the community `p/default` registry
# ruleset (no Semgrep account or token). Results land in the Security tab (Code scanning,
# category `semgrep`). Findings never fail the job, so this workflow does not block CI.
name: semgrep

on:
# Push to main, non-draft pull requests and the weekly schedule run this through
# pipeline.yml (`workflow_call`), in parallel with the other checks and behind its `gate` job.
workflow_call:
workflow_dispatch:

jobs:
scan:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
actions: read
container:
# semgrep/semgrep:1.177.0
image: semgrep/semgrep@sha256:acaac22ffc7b7cc5926de0751b223bce0b2491c33d18422fa72f632c78d81198
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# No `--error`: findings are reported, not enforced. Metrics off since no account is used.
- name: Semgrep scan
run: semgrep scan --config p/default --sarif --output semgrep.sarif --metrics off
- uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
if: always()
with:
sarif_file: semgrep.sarif
category: semgrep
70 changes: 70 additions & 0 deletions .github/workflows/snyk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Snyk Open Source dependency scan with the free Snyk CLI. Results land in the Security tab
# (Code scanning, category `snyk`); findings never fail the job. Runs from pipeline.yml.
#
# Needs the repo secret SNYK_TOKEN. Without it every step is skipped with a notice and the job
# succeeds, so the pipeline gate stays green until the token is added.
#
# Ecosystems in this repo: Cargo (Rust), pnpm/npm workspaces, Gradle Kotlin DSL (Android), Swift Package Manager (Package.resolved), CocoaPods (Podfile), pip (requirements.txt), Dockerfiles.
# pnpm/npm, SwiftPM and Gradle are attempted; pip needs its packages installed and CocoaPods needs Podfile.lock, so those may show as unresolved. Dockerfiles are not scanned (`snyk container test` needs built images).
# Rust/Cargo: Snyk documents that `snyk test` / `snyk monitor` do not support Cargo projects
# (Rust is limited to `snyk sbom test` on a CycloneDX/SPDX SBOM, and API use needs a paid plan),
# so `--all-projects` does not cover Cargo.lock here.
name: snyk

on:
workflow_call:
secrets:
SNYK_TOKEN:
required: false
workflow_dispatch:

permissions:
contents: read

jobs:
test:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
actions: read
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
steps:
- name: Check for SNYK_TOKEN
id: token
run: |
if [ -z "$SNYK_TOKEN" ]; then
echo "::notice title=snyk skipped::SNYK_TOKEN secret is not set; Snyk scan skipped."
echo "present=false" >> "$GITHUB_OUTPUT"
else
echo "present=true" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: steps.token.outputs.present == 'true'
- uses: snyk/actions/setup@9adf32b1121593767fc3c057af55b55db032dc04 # v1.0.0
if: steps.token.outputs.present == 'true'
# Exit 1 = vulnerabilities found (reported, not enforced); 2/3 = scan error or no
# supported manifest (warned, not enforced).
- name: Snyk test
if: steps.token.outputs.present == 'true'
run: |
code=0
snyk test --all-projects --sarif-file-output=snyk.sarif || code=$?
case "$code" in
0) ;;
1) echo "::notice title=snyk::Vulnerable dependencies found; see Security > Code scanning (category snyk)." ;;
*) echo "::warning title=snyk::snyk test exited $code (scan error or no supported project); not failing the job." ;;
esac
- name: Snyk monitor
if: steps.token.outputs.present == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
code=0
snyk monitor --all-projects || code=$?
if [ "$code" != 0 ]; then echo "::warning title=snyk::snyk monitor exited $code"; fi
- uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
if: steps.token.outputs.present == 'true' && hashFiles('snyk.sarif') != ''
with:
sarif_file: snyk.sarif
category: snyk