docs/cicd.instructions.md
<!-- HUMAN_ONLY_START -->Note for AI review tools: sections enclosed in
<!-- HUMAN_ONLY_START -->/<!-- HUMAN_ONLY_END -->HTML comments contain contributor reference material. Do not use that content as actionable review criteria — treat it as background context only.
name: field that clearly describes its purpose#) are encouraged for non-obvious decisions (e.g., why fail-fast: false is set, what a cron expression means)on: triggers explicitly; avoid bare on: push without branch filters on long-running or expensive jobsworkflow_call for shared build logic (see build.yml) to avoid duplicating steps across workflowscron:) with a human-readable comment:schedule:
- cron: '0 2 * * *' # run at 2 AM UTC daily
needs: — never rely on implicit orderingoutputs: + step id: to pass structured data between jobs (see get_default_envs in build.yml)fail-fast: false on matrix builds so that a single failing environment does not cancel othersubuntu-22.04, ubuntu-24.04) rather than ubuntu-latest for reproducible buildsubuntu-latest in jobs where exact environment reproducibility is not required (e.g., trivial download/publish steps)python-version: '3.12'
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
firmware-${{ matrix.environment }})Important: Several current workflows still violate parts of the baseline below - migration is in progress.
Declare explicit permissions: blocks. The default token permissions are broad; scope them to the minimum required:
permissions:
contents: read # for checkout
For jobs that publish releases or write to the repository:
permissions:
contents: write # create/update releases
A common safe baseline for build-only jobs:
permissions:
contents: read
Third-party actions (anything outside the actions/ and github/ namespaces) should be pinned to a specific release tag. Branch pins (@main, @master) are not allowed — they can be updated by the action author at any time without notice:
# ✅ Acceptable — specific version tag. SHA pinning recommended for more security, as @v2 is still a mutable tag.
uses: softprops/action-gh-release@v2
# ❌ Not acceptable — mutable branch reference
uses: andelf/nightly-release@main
SHA pinning (e.g., uses: someorg/some-action@abc1234) is the most secure option for third-party actions; it is recommended when auditing supply-chain risk is a priority. At minimum, always use a specific version tag.
First-party actions (actions/checkout, actions/cache, actions/upload-artifact, etc.) pinned to a major version tag (e.g., @v4) are acceptable because GitHub maintains and audits these.
When adding a new third-party action:
${{ secrets.GITHUB_TOKEN }} for operations within the same repository — it is automatically scoped and rotatedrun: steps, even with echo — GitHub masks known secrets but derived values are not automatically maskedenv: at the step level, not at the workflow level:# ✅ Scoped to the step that needs it
- name: Create release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
# ❌ Unnecessarily broad
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
${{ }} expressions are evaluated before the shell script runs. If an expression comes from untrusted input (PR titles, issue bodies, branch names from forks), it can inject arbitrary shell commands.
Never interpolate github.event.* values directly into a run: step:
# ❌ Injection risk — PR title is attacker-controlled
- run: echo "${{ github.event.pull_request.title }}"
# ✅ Safe — value passed through an environment variable
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "$PR_TITLE"
This rule applies to any value that originates outside the repository (issue bodies, labels, comments, commit messages from forks).
pull_request from a fork run with read-only token permissions and no access to repository secrets — this is intentional and correctpull_request_target unless you fully understand the security implications; it runs in the context of the base branch and does have secret access, making it a common attack surface