Skip to content

🔒(ci) harden every workflow install and pin every action to a hash - #1607

Open
davd-gzl wants to merge 6 commits into
suitenumerique:mainfrom
davd-gzl:fix/pin-workflow-installs
Open

🔒(ci) harden every workflow install and pin every action to a hash#1607
davd-gzl wants to merge 6 commits into
suitenumerique:mainfrom
davd-gzl:fix/pin-workflow-installs

Conversation

@davd-gzl

@davd-gzl davd-gzl commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Every install step in these workflows fetches packages and runs code from them before the job's own commands start, with GITHUB_TOKEN in the environment.

Every uses: names a tag or a branch, and either one its owner can repoint at different code without the reference changing.

SonarCloud counts 60 security findings across the four files.

Design

Installs

pip install refuses source distributions, so nothing runs a setup.py on the runner. uv run stops re-resolving what the uv sync --locked above it already pinned. npm ci and yarn install skip dependency lifecycle scripts. The dockerize download stays on https across redirects.

Pins

All 65 uses: name a 40-character commit, each one the commit its old ref already pointed at, with the version in a trailing comment. Nothing about what runs changes.

Renovate is configured to write and update this form, though its GitHub Actions branch has been sitting on the dashboard rather than opening. Either way the refs are as current as they were: a stale tag is not fresher than a stale hash, it just changes underneath you.

The rename

meet.yml says nothing the repository name does not, so it becomes ci.yml.

What this leaves

Five of the 60 findings, all MAJOR. The gate reads the worst finding rather than the count, so it stays red.

Three are uv sync --locked, which has nowhere to put --no-build while brevo-python ships as source only. Two are pip install .[dev], which wants a lock file for src/summary that is a maintainer's call rather than this branch's.

Glossary

wheel: a package that is a zip and gets unpacked.

source distribution: a package that is a tarball whose setup.py the installer runs, on the runner, with the job's environment around it.

lifecycle script: preinstall, install, postinstall or prepare, which npm and yarn run from every package in the resolved tree, not only the ones package.json names.

pin: a uses: naming a commit rather than a tag or a branch, so the owner cannot change what runs.

A package could run arbitrary code on the runner while installing,
and the versions were resolved rather than taken from the pins.
The two pip calls now take wheels only, and uv run no longer
resolves an environment of its own.
gitlint-core keeps the linter at 0.19.1, which the wheels-only pin was
resolving down to 0.18.0 through an sdist-only sh. --ignore-scripts
stops three npm ci, one yarn install and one npm install -g running
the scripts of what they fetch, and yarn is pinned to 1.22.22. curl
holds the dockerize download and its redirects to https.
A tag and a branch both move, so actions/checkout@v6 and
numerique-gouv/action-trivy-cache@main ran whatever the owner had last
pushed. Each of the 65 uses now names a 40-character commit, with the
version it resolved to in a trailing comment.

dependabot.yml keeps the hash and that comment moving together.
The repository is already called meet, so the file name said nothing
about what the workflow holds. The print-statement check now excludes
the whole workflows directory rather than one file by name: its own
grep carries the literal print(, so the rename would otherwise match
it on the deleted lines and fail the job.
--no-sync already stops uv run resolving an environment of its own,
and that is what the version findings were about. Building is a
separate guarantee that nothing on the line carried, so --no-build
now says it outright. It is inert beside --no-sync, and the three
lint jobs are unchanged.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@davd-gzl
davd-gzl marked this pull request as ready for review August 18, 2026 15:54
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Harden GitHub Actions installs and pin all workflow actions to commit SHAs

✨ Enhancement ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Pin every uses: reference to a full commit SHA to prevent tag/branch drift.
• Harden dependency installation steps (pip/uv/npm/yarn/curl) to reduce code-execution risk.
• Rename main CI workflow to ci.yml and keep print-statement guard working after rename.
Diagram

graph TD
  W["Workflow files"] --> P["Pinned actions (SHA)"] --> GH[("GitHub Action repos")]
  W --> I["Hardened install steps"] --> REG[("PyPI/NPM/GitHub Releases")] --> R["CI jobs (lint/test/build)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Lock + hash Python deps (pip --require-hashes / uv lock everywhere)
  • ➕ Strongest supply-chain guarantee for Python installs (content-addressed).
  • ➕ Makes “latest compatible” resolution impossible during CI runs.
  • ➖ Requires maintaining lockfiles (and potentially per-subproject locks).
  • ➖ Harder when sdists are unavoidable (some deps ship no wheels).
2. Centralize hardening via reusable workflow/composite action
  • ➕ Reduces duplication and keeps install hardening consistent across workflows.
  • ➕ Easier to roll out future policy changes (e.g., curl flags, install flags).
  • ➖ Adds indirection; reviewers must navigate shared workflow/action.
  • ➖ May limit per-job flexibility (different language/tooling needs).
3. Rely on GitHub action pinning automation only (Renovate/Dependabot)
  • ➕ Less manual maintenance; automated PRs keep digests fresh.
  • ➕ Avoids large one-time diffs if done incrementally.
  • ➖ Doesn’t address risky install steps (npm scripts, pip sdists, curl redirects).
  • ➖ Still needs an initial pin/hardening baseline to converge from.

Recommendation: The PR’s approach is a good baseline: pinning uses: to immutable SHAs and hardening install commands reduces immediate workflow supply-chain risk without changing functional behavior. If you want to fully clear remaining security findings later, the next step is adopting/expanding lockfile + hash-based dependency verification where feasible (noting wheel/sdist constraints).

Files changed (4) +85 / -83

Other (4) +85 / -83
ci.ymlRename CI workflow and harden install steps + action pins +44/-42

Rename CI workflow and harden install steps + action pins

• Renames the main workflow to 'ci.yml' with 'name: CI', updates the print-statement guard to exclude the workflows directory, and pins checkout/setup/cache/uv actions to commit SHAs. Hardens installs by restricting 'pip install' to wheels with explicit pins, making 'uv run' avoid implicit sync/build, adding '--ignore-scripts' to npm/yarn installs, and tightening the dockerize download curl to HTTPS-only redirects.

.github/workflows/ci.yml

crowdin-download.ymlPin Crowdin workflow actions to commit SHAs +2/-2

Pin Crowdin workflow actions to commit SHAs

• Pins 'actions/checkout' and 'crowdin/github-action' to full commit digests to avoid mutable tag execution while preserving existing behavior.

.github/workflows/crowdin-download.yml

docker-hub.ymlPin Docker build/publish workflow actions to commit SHAs +36/-36

Pin Docker build/publish workflow actions to commit SHAs

• Pins all Docker build pipeline actions (checkout, setup-qemu, buildx, metadata, login, build-push) and internal Numerique-Gouv actions (trivy cache, ArgoCD webhook notification) to specific commit SHAs to prevent tag/branch drift.

.github/workflows/docker-hub.yml

release-helm-chart.yamlPin Helm release workflow actions to commit SHAs +3/-3

Pin Helm release workflow actions to commit SHAs

• Pins 'actions/checkout', 'azure/setup-helm', and 'numerique-gouv/helm-gh-pages' to commit digests to ensure the chart publishing workflow runs immutable action code.

.github/workflows/release-helm-chart.yaml

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Grey Divider


Action required

1. SDK install skips SWC setup 🐞 Bug ☼ Reliability
Description
In build-sdk, npm ci --ignore-scripts suppresses lifecycle scripts, but the SDK build uses
@vitejs/plugin-react-swc which depends on @swc/core (install script present) to set up its
native binary, so npm run build can fail when SWC isn't provisioned.
Code

.github/workflows/ci.yml[394]

+        run: npm ci --ignore-scripts
Evidence
The workflow change suppresses install scripts in the SDK jobs, while the SDK build is implemented
with Vite + the SWC React plugin, and the lockfile shows that plugin depends on @swc/core which
declares an install script (i.e., expects lifecycle scripts to run).

.github/workflows/ci.yml[361-397]
src/sdk/library/package.json[22-51]
src/sdk/package-lock.json[2728-2737]
src/sdk/package-lock.json[2116-2123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`build-sdk` (and `lint-sdk`) now run `npm ci --ignore-scripts`, which disables dependency lifecycle scripts. The SDK build uses `@vitejs/plugin-react-swc` -> `@swc/core`, and `@swc/core` has an install script; skipping it can leave SWC unusable and break `npm run build`.

### Issue Context
This change was introduced as part of workflow hardening. For the SDK jobs, lifecycle scripts appear to be required for native/binary tooling.

### Fix Focus Areas
- .github/workflows/ci.yml[372-397]

### Suggested fix
- Change SDK jobs back to `npm ci` (remove `--ignore-scripts`) at least for `build-sdk`.
- If you must keep `--ignore-scripts`, add an explicit, scoped post-step that provisions required binaries (e.g., a controlled `npm rebuild @swc/core` / `npm rebuild esbuild`) and document why those packages are safe to run scripts for.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread .github/workflows/ci.yml

@lebaudantoine lebaudantoine left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably squash your last commit.
Thank you for your contribution

Comment thread .github/dependabot.yml Outdated
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll push for frequent updates when security issues arise, and monthly upgrades on a regular basis. Regarding the repo configuration, we already allowlist specific actions that are permitted to run. Updating every action hash every week would be a pain, without actually making the team more secure or more productive.

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already allowlist specific action hashes in the repository settings, so @main actions cannot run regardless of what the owner pushes. However, making this explicit and versioned directly in the code remains a solid best practice.

@lebaudantoine

Copy link
Copy Markdown
Collaborator

@greptileai

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The PR appears safe to merge, with one non-blocking opportunity to narrow the print-check exclusion so other workflow files remain covered.

The action pins and installation restrictions have no established current failure path, while the broadened workflow exclusion only weakens future print-statement enforcement.

Files Needing Attention: .github/workflows/ci.yml

Important Files Changed

Filename Overview
.github/workflows/ci.yml Hardens dependency installation and action references; the print-check exclusion is broader than required and creates a non-blocking enforcement gap.
.github/workflows/crowdin-download.yml Pins checkout and Crowdin actions to immutable commits without changing their configuration.
.github/workflows/docker-hub.yml Pins Docker, Trivy, and deployment actions to the commits corresponding to their previous references.
.github/workflows/release-helm-chart.yaml Pins checkout, Helm setup, and chart publication actions without changing release inputs.

Reviews (1): Last reviewed commit: "🔥(ci) leave the action pins to Renovate" | Re-trigger Greptile

Comment thread .github/workflows/ci.yml
if: always()
run: |
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude)**/meet.yml' | grep "print("
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude).github/workflows/**' | grep "print("

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Print check excludes all workflows

The directory-wide pathspec removes every workflow from print-statement enforcement even though only ci.yml contains the self-referential pattern. Restricting the exclusion to this file preserves coverage for changes to the other workflows.

Suggested change
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude).github/workflows/**' | grep "print("
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude).github/workflows/ci.yml' | grep "print("

@davd-gzl

davd-gzl commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

dependabot.yml is gone already, 6b655562 dropped it before this review, since renovate.json already covers that job.

Your point about the weekly noise stands either way. What tells you a dependency needs update right now? I can shape this pull request around that if possible

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants