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
2 changes: 1 addition & 1 deletion .github/workflows/auto-merge-dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
steps:
- name: Fetch Dependabot metadata
id: meta
uses: dependabot/fetch-metadata@v3
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main

- name: Set up Go
uses: actions/setup-go@v7
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
# Read from go.mod so the toolchain is declared once.
go-version-file: go.mod
Expand Down
44 changes: 39 additions & 5 deletions .github/workflows/build-sandbox-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,38 @@ jobs:
BUNDLE_DIR: ${{ inputs.bundle_dir }}
steps:
- name: Checkout caller repo
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# The build script lives in fleet core; a client-config caller doesn't
# have it. Checking it out into a subdir keeps ONE canonical builder
# (same manifest parsing, same flags) instead of a drifting copy per repo.
# fleet_ref chooses which ref of ElcanoTek/fleet the BUILD SCRIPT below is
# taken from — and that script is then EXECUTED. Every ref in this repo is
# collaborator-written except refs/pull/* (fork PRs), so those are exactly
# the refs that would let non-collaborator code run here; refuse them
# before the checkout. The checkout then consumes this step's validated
# output rather than the raw input. (Found via CodeQL
# actions/untrusted-checkout under security-extended; the same pattern in
# publish-sandbox-image.yml was hardened symmetrically even though the
# query's privileged/taint split happened to flag neither variant there.)
- name: Pin fleet_ref to collaborator-controlled refs
id: pin
env:
REQUESTED: ${{ inputs.fleet_ref || 'main' }}
run: |
set -euo pipefail
case "$REQUESTED" in
refs/pull/*|pull/*|-*)
echo "::error::fleet_ref '$REQUESTED' is refused: pull-request refs carry fork-controlled code, and this workflow executes the checked-out build script."
exit 1 ;;
esac
printf 'resolved=%s\n' "$REQUESTED" >> "$GITHUB_OUTPUT"

- name: Checkout fleet (build script)
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ElcanoTek/fleet
ref: ${{ inputs.fleet_ref || 'main' }}
ref: ${{ steps.pin.outputs.resolved }}
path: .fleet-core
sparse-checkout: |
scripts/build-sandbox-image.sh
Expand All @@ -130,14 +152,26 @@ jobs:

- name: Report what was built
if: ${{ always() && steps.build.outcome != 'skipped' }}
env:
# The step outcome comes in through env, not interpolated into the
# script: a ${{ }} expression expanded inside a run: block is the
# script-injection shape semgrep's github-actions rules flag, and the
# inline form also broke their bash sub-parser, silently costing this
# file rule coverage (same fix as codeql.yml's $RUNNER_TEMP).
BUILD_OUTCOME: ${{ steps.build.outcome }}
run: |
tag="$(FLEET_CLIENT_CONFIG_DIR="$GITHUB_WORKSPACE/$BUNDLE_DIR" \
bash .fleet-core/scripts/build-sandbox-image.sh --print-tag 2>/dev/null || true)"
if [ "${{ steps.build.outcome }}" = "success" ]; then
# Plain assignment instead of a ${tag:-(…)} expansion default: the
# parenthesis inside the default value is valid bash but chokes
# semgrep's bash sub-parser, which partial-parsed this file and
# silently dropped two rules' coverage of it.
if [ -z "$tag" ]; then tag="(tag unavailable)"; fi
if [ "$BUILD_OUTCOME" = "success" ]; then
{
echo "### Sandbox builds clean"
echo
echo "\`${tag:-(tag unavailable)}\` built from \`$BUNDLE_DIR/sandbox/Containerfile\`."
echo "\`$tag\` built from \`$BUNDLE_DIR/sandbox/Containerfile\`."
echo
echo "Nothing was pushed — this is a build canary. The base tracks"
echo "\`fedora-minimal:latest\`, so this is the check that a Fedora"
Expand Down
132 changes: 112 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
docs_only: ${{ steps.detect.outputs.docs_only }}
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Full history so the base/head (or before/after) SHAs are present to diff.
fetch-depth: 0
Expand Down Expand Up @@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install gitleaks
# Pin a specific gitleaks release and verify its checksum so the gate is
Expand Down Expand Up @@ -110,7 +110,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Full history so the merge-base with origin/main resolves and the
# linter can diff the branch's new/changed migration files.
Expand Down Expand Up @@ -163,10 +163,10 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Go
uses: actions/setup-go@v7
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
# Read from go.mod so the toolchain is declared once.
go-version-file: go.mod
Expand Down Expand Up @@ -231,7 +231,7 @@ jobs:
run: go vet -tags fleet_host_executor ./...

- name: golangci-lint
uses: golangci/golangci-lint-action@v9
uses: golangci/golangci-lint-action@db9de0fc1a667e1a49d2291a1a042dff081d78f6 # v9
with:
# Repo .golangci.yml is the v2 schema (version: "2"). It no longer
# pins run.go: golangci-lint's documented default is "use Go version
Expand Down Expand Up @@ -316,6 +316,71 @@ jobs:
# on a schedule instead of ambushing the next unrelated PR.
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...

python:
name: Python lint (ruff)
runs-on: ubuntu-latest
needs: changes
# Skipped for a docs-only change; the `CI gate` job treats a skip as a pass.
if: ${{ needs.changes.outputs.docs_only != 'true' }}
# The lane the repo did not have. fleet ships 13 Python files — the sandbox
# FileOp helper, the python bridge, the bento-slides and data-profiler skill
# scripts, MCP test servers — and nothing linted any of them: Go had
# golangci-lint, the web tier had oxlint, Python had neither. Its only
# coverage was whatever CodeQL's code-quality suite happened to notice, which
# is a ~40s job with no autofix; ruff does the same class of check in well
# under a second. See ruff.toml for why the rule set is narrow (default rules
# find 3 issues here; the broad selection finds 333, almost all style churn).
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install ruff
# Pinned like every other tool this repo installs in CI, so an upstream
# release cannot change the verdict without a visible diff.
env:
RUFF_VERSION: '0.15.8'
run: |
set -euo pipefail
python3 -m pip install --user --quiet "ruff==${RUFF_VERSION}"
# GITHUB_PATH only affects LATER steps, so export for this one too.
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.local/bin:$PATH"
ruff --version

- name: Lint
# Config (rule selection, excludes, per-file ignores) lives in ruff.toml
# so a local `ruff check .` and this gate cannot disagree.
run: ruff check --output-format github .

- name: Formatting check
# A gate, and safe to be one: the whole tree was ruff-formatted in the
# change that flipped this, so a failure here is a NEW unformatted file,
# fixable with one `ruff format .`.
run: ruff format --check .

codeql:
# Reusable-workflow call: brings codeql.yml's jobs into THIS workflow's
# graph so `CI gate` (the single required check on main) blocks on them.
# Its `Fail on findings` step means a finding — not just a broken scanner —
# fails this job and therefore the gate.
needs: changes
# Docs-only changes cannot touch Go/JS/Python/workflow code (the allowlist
# is *.md, docs/, LICENSE); the gate treats the skip as a pass.
if: ${{ needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
security-events: write # the called workflow uploads SARIF to code scanning
actions: read
uses: ./.github/workflows/codeql.yml

semgrep:
# Same mechanism as codeql above: called here so `CI gate` blocks on it.
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
uses: ./.github/workflows/semgrep.yml

web:
name: Web lint / test / build
runs-on: ubuntu-latest
Expand All @@ -328,16 +393,43 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
# Read from web/.nvmrc so the deployed node major is declared once.
node-version-file: web/.nvmrc
cache: npm
cache-dependency-path: web/package-lock.json

- name: Audit dependencies for known CVEs
# The npm counterpart of the Go job's govulncheck gate: reads
# package-lock.json against the npm advisory DB and fails on ANY
# severity. Like govulncheck, the verdict is a function of the clock as
# well as the commit — a new advisory can redden an unchanged tree, and
# that is the point. Needs no node_modules (lockfile-only), so it runs
# before the expensive install and fails fast.
run: npm audit --audit-level=low

- name: Audit rampart-service dependencies for known CVEs
# Separate tree, same gate. Its lockfile pins `overrides` forcing
# sharp >=0.35 (libvips CVEs) and adm-zip >=0.6 (GHSA-xcpc-8h2w-3j85)
# because no release of @huggingface/transformers/onnxruntime-node has
# picked the fixes up yet — see scripts/rampart-service/package.json.
working-directory: scripts/rampart-service
run: npm audit --audit-level=low

- name: Check whether the rampart security overrides are droppable
# scripts/rampart-service/package.json force-patches sharp and adm-zip
# because their parents have not released fixes. The day upstream does,
# this FAILS with removal instructions — an override left behind after
# upstream fixes itself silently pins Dependabot's updates down. A
# registry flake skips with a notice (the audit above is the CVE gate).
# Absolute path: this job's default working-directory is web/, which is
# exactly how run 32579378165 caught the repo-relative form (exit 127).
run: "$GITHUB_WORKSPACE/scripts/check-npm-overrides.sh"

- name: Install dependencies
run: npm ci

Expand Down Expand Up @@ -389,10 +481,10 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
# Read from web/.nvmrc so the deployed node major is declared once.
node-version-file: web/.nvmrc
Expand All @@ -415,7 +507,7 @@ jobs:

- name: Upload Playwright HTML report
if: ${{ failure() }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
path: web/playwright-report/
Expand Down Expand Up @@ -460,17 +552,17 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Go
uses: actions/setup-go@v7
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
# Read from go.mod so the toolchain is declared once.
go-version-file: go.mod
check-latest: true

- name: Set up Node
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
# Read from web/.nvmrc so the deployed node major is declared once.
node-version-file: web/.nvmrc
Expand Down Expand Up @@ -588,15 +680,15 @@ jobs:

- name: Upload Playwright live report
if: ${{ failure() }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-live-report
path: web/playwright-report/
retention-days: 14

- name: Upload server logs on failure
if: ${{ failure() }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2e-live-server-logs
path: |
Expand All @@ -621,7 +713,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install Grype
# Pin a specific Grype release and verify its checksum, exactly as the
Expand Down Expand Up @@ -694,12 +786,12 @@ jobs:

- name: Upload Grype SARIF results to the GitHub Security tab
# Upload whenever the scan produced a SARIF — including when the policy
# step rejects a fixable CRITICAL RPM — so
# step rejects a fixable CRITICAL/HIGH RPM — so
# findings reach GitHub Security → Code scanning with full CVE details,
# affected packages, and fix versions rather than only a red CI job. The
# hashFiles guard skips this step (instead of erroring "file not found")
# when an EARLIER step failed before grype could write the SARIF.
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@4c0873ef8656cb3c50b3f42fb63bc1ade0cfa827 # v4 (4.37.8)
if: ${{ !cancelled() && hashFiles('grype-results.sarif') != '' }}
with:
sarif_file: 'grype-results.sarif'
Expand All @@ -715,7 +807,7 @@ jobs:
# allowed (docs-only), but any failure or cancellation fails the gate.
name: CI gate
if: ${{ always() }}
needs: [changes, gitleaks, migrations, go, web, playwright, e2e-live, grype-scan]
needs: [changes, gitleaks, migrations, go, python, codeql, semgrep, web, playwright, e2e-live, grype-scan]
runs-on: ubuntu-latest
steps:
- name: Require all upstream jobs to have succeeded or been skipped
Expand Down
Loading
Loading