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: 11 additions & 25 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
name: Semgrep SAST

# Report-only SAST gate (constructorfabric/insight#1797, split from #1463; umbrella #1478).
#
# Findings (Semgrep `--config auto` — per-language auto-detection via the registry,
# matching the org GitLab semgrep-scan component — plus the p/rust and p/csharp packs,
# since auto has thin Rust/C# coverage) surface in the
# GitHub "Security -> Code scanning" tab (SARIF upload)
# AND as a count in the job summary, but they DO NOT block: `semgrep scan` runs WITHOUT
# `--error`. Flip to blocking once the baseline is triaged and clean (zero un-waived
# findings) by adding `--error` here and marking `sast` a required status check.
#
# Secret detection is intentionally excluded (`--exclude-rule generic.secrets...`); the
# separate TruffleHog gate owns secret scanning.
# SAST, report-only (#2020). `--config auto` plus p/rust and p/csharp, which auto covers thinly.
# Secrets are excluded here — trufflehog.yml owns them.

on:
pull_request:
branches: [main]
push:
# A default-branch baseline the moment something lands, rather than only at 03:27 the next
# morning: without this, Code Scanning holds no SAST data for `main` between a merge and the
# nightly run, so a merge that introduces a finding stays invisible until then.
branches: [main]
schedule:
# Nightly full-tree baseline (independent of what any PR touched), 03:27 UTC.
- cron: "27 3 * * *"
Expand Down Expand Up @@ -43,12 +38,8 @@ jobs:
persist-credentials: false

- name: Semgrep scan (report-only — ratchet to blocking with --error once baseline is clean)
# Runs the digest-pinned Semgrep image via `docker run` (not a `container:` job) so the
# checkout and SARIF-upload JS actions keep the host runner's Node — the Semgrep image
# does not ship Node. No `--error`: findings are reported, never block. Secrets excluded
# (TruffleHog owns them). `.semgrepignore` in the repo root prunes build/dep artifacts.
# NB: `--config auto` requires metrics ON (it sends language/rule/finding counts — not
# source — to semgrep.dev to select rules), so `--metrics off` is intentionally omitted.
# `docker run` rather than a `container:` job so the JS actions keep the runner's Node.
# `--config auto` needs metrics on to select rules, so `--metrics off` is omitted.
run: |
docker run --rm \
-v "${{ github.workspace }}:/src" \
Expand All @@ -63,14 +54,10 @@ jobs:

- name: Summarize findings in the job summary
if: always()
# Null-guarded append (repo convention, cf. e2e-bronze-to-api.yml). Renders a
# severity + per-rule breakdown from the SARIF using the stdlib (no jq dependency).
run: |
[ -f semgrep.sarif ] || exit 0
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0
# Link the summary to Code scanning. On PRs the analysis is attributed to the PR
# (merge ref) — NOT the branch ref — so filter by pr:<n>; on push/schedule/dispatch
# filter by branch. GITHUB_REF_NAME is "<n>/merge" on pull_request events.
# On PRs the analysis is attributed to the PR, not the branch.
CS_BASE="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/security/code-scanning"
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
CS_URL="${CS_BASE}?query=is%3Aopen+pr%3A${GITHUB_REF_NAME%/merge}"
Expand Down Expand Up @@ -103,8 +90,7 @@ jobs:
PY

- name: Upload SARIF to GitHub Code Scanning
# Skip on fork PRs: they receive a read-only token and cannot upload to Code Scanning,
# which would otherwise red-X this report-only job.
# Fork PRs get a read-only token and cannot upload.
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
uses: github/codeql-action/upload-sarif@fb0994ef1c058010acf1efccff928b0a83b1ed54 # v4.32.6
with:
Expand Down
48 changes: 11 additions & 37 deletions .github/workflows/trivy-images.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,7 @@
name: Trivy Images

# CVE scan of the published images. This is the only place where three things are visible:
#
# 1. base-image and OS-package CVEs (nothing in the repository describes them);
# 2. .NET dependencies — the five .csproj projects ship no packages.lock.json, so
# `trivy fs` cannot resolve their versions and skips them entirely;
# 3. Python dependencies — same story for the eleven pyproject.toml projects (toolbox,
# jira-enrich and the connectors), which have no lock file either.
#
# Inside an image those dependencies are installed and therefore resolvable, so this workflow
# carries the SCA coverage that trivy.yml structurally cannot.
#
# Report-only: `--exit-code 0`, findings go to "Security -> Code scanning" under a per-image
# category plus a job-summary table. Nothing blocks. Barring a vulnerable image from
# promotion — the policy target — means adding the scan to build-images.yml between the merge
# and publish steps with `--exit-code 1`, which is a change to a much larger workflow and a
# separate step.
#
# Scans `:latest` rather than a build tag, so it reflects what is currently deployable. The
# legacy `insight-analytics-api` and `insight-api-gateway` packages are deliberately absent:
# they are pre-rename names that no current build job publishes.
# CVE scan of the published images — the only view of base layers and of what a Dockerfile
# installs without a manifest. Report-only, nightly, `:latest` (#2020).

on:
schedule:
Expand Down Expand Up @@ -68,10 +50,7 @@ jobs:
- source-github-copilot-insight
- source-github-v2-insight
- source-gitlab-insight
# The waiver below is per-line and must stay on the entry itself: TruffleHog's
# GitLab detector reads that 21-character image name as a token. Waiving the line
# keeps the detector enabled — it is one of the few that could catch a real
# connector credential.
# Keep the waiver on the entry line: the GitLab detector reads this image name as a token.
- source-hubspot-insight # trufflehog:ignore
- source-salesforce-insight
steps:
Expand All @@ -81,16 +60,15 @@ jobs:
persist-credentials: false

- name: Trivy image scan (CRITICAL/HIGH)
# Trivy pulls straight from the registry (TRIVY_USERNAME/TRIVY_PASSWORD) instead of a
# mounted docker socket, so no local `docker pull` is needed. `--ignore-unfixed` keeps
# the output to what a base-image or dependency bump can actually fix. A multi-arch
# manifest resolves to the runner's platform (linux/amd64); the arm64 leg is built from
# the same base image and package set.
# Pulls from the registry directly, so no docker daemon is needed. CRITICAL/HIGH only:
# everything here comes from a base layer and the remedy is the same at any severity.
env:
TRIVY_IMAGE: aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f
IMAGE_REF: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ inputs.tag || 'latest' }}
TRIVY_USERNAME: ${{ github.actor }}
TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
# CRITICAL/HIGH only: everything here comes from a base layer, and the remedy is the same
# at any severity — refresh the base image.
# One pull and one analysis per image: `trivy convert` derives the SARIF from the JSON
# rather than rescanning, which for a 14-image matrix saves 14 redundant registry pulls
# and layer analyses. `-w /work` so a repo-root `.trivyignore` is resolved — Trivy reads
Expand Down Expand Up @@ -119,9 +97,7 @@ jobs:

- name: Summarize findings in the job summary
if: always()
# Grouped by (package, version) — the unit of remediation. The ecosystem column
# separates base-image OS packages from application dependencies, which is the
# difference between "bump the base image" and "bump a dependency".
# Grouped by package — the unit of remediation.
run: |
[ -f trivy-image.json ] || exit 0
[ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0
Expand Down Expand Up @@ -150,17 +126,15 @@ jobs:
rank = {"CRITICAL": 0, "HIGH": 1}
w("| Ecosystem | Package | Max | CVEs | Fixed in |\n|---|---|---|---:|---|\n")
for (eco, pkg, ver), items in sorted(
bundles.items(), key=lambda kv: (min(rank.get(i["Severity"], 2) for i in kv[1]), kv[0])):
worst = min(items, key=lambda i: rank.get(i["Severity"], 2))["Severity"]
bundles.items(), key=lambda kv: (min(rank.get(i["Severity"], 4) for i in kv[1]), kv[0])):
worst = min(items, key=lambda i: rank.get(i["Severity"], 4))["Severity"]
fixes = sorted({i.get("FixedVersion") or "-" for i in items})
w(f"| {eco} | `{pkg}@{ver}` | {worst} | {len(items)} | {', '.join(fixes)[:60]} |\n")
out.close()
PY

- name: Upload SARIF to GitHub Code Scanning
# The hashFiles guard matters more here than elsewhere: with 14 matrix legs, one failed
# registry pull would otherwise report twice — once for the pull, once for a SARIF file
# that was never written.
# Guard: a failed pull must not also report a missing SARIF.
if: ${{ always() && hashFiles('trivy-image.sarif') != '' }}
uses: github/codeql-action/upload-sarif@fb0994ef1c058010acf1efccff928b0a83b1ed54 # v4.32.6
with:
Expand Down
Loading
Loading