From 415b58f2caa0db17c7286a20da6c74debe64fe9a Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Fri, 17 Jul 2026 20:23:18 +0300 Subject: [PATCH 1/9] ci(security): add report-only Semgrep SAST gate (#1797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standalone semgrep.yml running p/default (secrets excluded — TruffleHog owns those) in report-only mode: findings upload to Code Scanning (SARIF) and a job-summary count, but no --error so the check never blocks. PR + nightly + dispatch triggers, digest-pinned image, SHA-pinned actions. Adds .semgrepignore for build/dep artifacts and waives the known JWT ValidateLifetime finding inline (#346). Split from #1463; part of #1478. Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 81 +++++++++++++++++++++++++++++++++++ .semgrepignore | 26 +++++++++++ 2 files changed, 107 insertions(+) create mode 100644 .github/workflows/semgrep.yml create mode 100644 .semgrepignore diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 000000000..12c17c9f0 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,81 @@ +name: Semgrep SAST + +# Report-only SAST gate (constructorfabric/insight#1797, split from #1463; umbrella #1478). +# +# p/default findings 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. + +on: + pull_request: + branches: [main] + schedule: + # Nightly full-tree baseline (independent of what any PR touched), 03:27 UTC. + - cron: "27 3 * * *" + workflow_dispatch: + +# A new push obsoletes any run still in flight for the same ref. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + sast: + name: sast + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read # checkout + security-events: write # upload SARIF to Code Scanning + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + 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. + run: | + docker run --rm \ + -v "${{ github.workspace }}:/src" \ + -w /src \ + semgrep/semgrep:1.131.0@sha256:6bd07d7b166b097e1384f41b94a62d8c8a26a4fff8713992c296e053310da01f \ + semgrep scan \ + --config p/default \ + --exclude-rule generic.secrets.security.detected-generic-secret \ + --sarif --output semgrep.sarif \ + --metrics off + + - name: Summarize findings in the job summary + if: always() + # Null-guarded append (repo convention, cf. e2e-bronze-to-api.yml). Counts SARIF + # results with the stdlib json module (no jq dependency). + run: | + if [ -f semgrep.sarif ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + count=$(python3 -c "import json; d=json.load(open('semgrep.sarif')); print(sum(len(r.get('results', [])) for r in d.get('runs', [])))" 2>/dev/null || echo "?") + { + echo "## Semgrep SAST (report-only)" + echo "" + echo "**${count}** finding(s) from \`p/default\` — see the **Security → Code scanning** tab for details." + echo "" + echo "_Report-only: this check never blocks. It becomes blocking when \`--error\` is added and the baseline is clean (#1797)._" + } >> "$GITHUB_STEP_SUMMARY" + fi + + - 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. + if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} + uses: github/codeql-action/upload-sarif@fbcef3fba75224ed6476919775448de80b23cb15 # v3.32.0 + with: + sarif_file: semgrep.sarif + category: semgrep diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 000000000..140d90866 --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,26 @@ +# Semgrep scan exclusions — build outputs and vendored dependencies only. +# Application AND test source stay in scope (constructorfabric/insight#1797). +# Syntax is .gitignore-style. Point/path/rule-class waivers live elsewhere: +# - per-finding: inline `// nosemgrep: ` / `# nosemgrep: ` + issue link +# - per-rule: `--exclude-rule` in .github/workflows/semgrep.yml (e.g. the TruffleHog boundary) + +# --- Rust build output --- +target/ + +# --- Python build/dep/caches --- +.venv/ +venv/ +__pycache__/ +*.pyc +*.egg-info/ +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ + +# --- Node / frontend build & deps --- +node_modules/ +dist/ +build/ + +# --- VCS / editor noise --- +.git/ From 37748ebbe98a1f845d25d898fed9bf4d396d7dc3 Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Fri, 17 Jul 2026 20:52:51 +0300 Subject: [PATCH 2/9] ci(security): move Semgrep gate to Node-24 actions; robust JWT waiver - Bump actions/checkout v4.2.2 -> v5.0.1 and codeql-action/upload-sarif v3.32.0 -> v4.32.6 (SHA-pinned) to clear the Node-20 deprecation and the CodeQL Action v3 sunset warnings from the first run. - Switch the JWT #346 waiver from an inline rule-id nosemgrep to a bare // nosemgrep: under the full p/default ruleset the fully-namespaced rule id does not match, so no suppression was emitted and the alert stayed open. The bare form reliably emits SARIF suppressions[inSource], which Code Scanning honors as a dismissed alert. Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 12c17c9f0..d97a2c3ef 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -35,7 +35,7 @@ jobs: contents: read # checkout security-events: write # upload SARIF to Code Scanning steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -75,7 +75,7 @@ jobs: # 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. if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: github/codeql-action/upload-sarif@fbcef3fba75224ed6476919775448de80b23cb15 # v3.32.0 + uses: github/codeql-action/upload-sarif@fb0994ef1c058010acf1efccff928b0a83b1ed54 # v4.32.6 with: sarif_file: semgrep.sarif category: semgrep From a8363205280ec36e5556cabaa3f39363007119c8 Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Fri, 17 Jul 2026 21:08:42 +0300 Subject: [PATCH 3/9] ci(security): richer Semgrep job-summary (severity + per-rule breakdown) Replace the bare finding count with a severity table plus a collapsible per-rule breakdown rendered from the SARIF (stdlib only, no jq). Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 39 ++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index d97a2c3ef..0c0d4f98d 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -57,19 +57,34 @@ jobs: - name: Summarize findings in the job summary if: always() - # Null-guarded append (repo convention, cf. e2e-bronze-to-api.yml). Counts SARIF - # results with the stdlib json module (no jq dependency). + # 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: | - if [ -f semgrep.sarif ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then - count=$(python3 -c "import json; d=json.load(open('semgrep.sarif')); print(sum(len(r.get('results', [])) for r in d.get('runs', [])))" 2>/dev/null || echo "?") - { - echo "## Semgrep SAST (report-only)" - echo "" - echo "**${count}** finding(s) from \`p/default\` — see the **Security → Code scanning** tab for details." - echo "" - echo "_Report-only: this check never blocks. It becomes blocking when \`--error\` is added and the baseline is clean (#1797)._" - } >> "$GITHUB_STEP_SUMMARY" - fi + [ -f semgrep.sarif ] || exit 0 + [ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0 + python3 - semgrep.sarif "$GITHUB_STEP_SUMMARY" <<'PY' + import json, sys, collections + d = json.load(open(sys.argv[1])) + out = open(sys.argv[2], "a") + res = [x for r in d.get("runs", []) for x in r.get("results", [])] + total = len(res) + sev = collections.Counter((x.get("level") or "warning") for x in res) + rules = collections.Counter(x.get("ruleId", "?").split(".")[-1] for x in res) + w = out.write + w("## Semgrep SAST (report-only)\n\n") + w(f"**{total}** finding(s) from `p/default` (secrets excluded — TruffleHog owns those). " + "This check does **not** block; full details in **Security -> Code scanning**.\n\n") + if total: + w("| Severity | Count |\n|---|---:|\n") + for s in ("error", "warning", "note"): + if sev.get(s): + w(f"| {s} | {sev[s]} |\n") + w("\n
Findings by rule\n\n| Count | Rule |\n|---:|---|\n") + for rid, n in rules.most_common(): + w(f"| {n} | `{rid}` |\n") + w("\n
\n") + w("\n_Report-only: becomes blocking when `--error` is added and the baseline is clean (#1797)._\n") + PY - name: Upload SARIF to GitHub Code Scanning # Skip on fork PRs: they receive a read-only token and cannot upload to Code Scanning, From dec2ac916eacb6c876551ba0552ad2cebc09545f Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Fri, 17 Jul 2026 21:15:32 +0300 Subject: [PATCH 4/9] ci(security): broaden Semgrep to p/rust, p/csharp, p/python p/default has thin Rust/C# security coverage (0 findings across 184 Rust files); add the dedicated language packs so the actual backend code is scanned, not just CI/YAML. Surfaces real Rust (unsafe-usage, temp-dir) and C# (unsigned-security-token) findings p/default missed. Still report-only. Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 0c0d4f98d..84d09ab79 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -2,7 +2,8 @@ name: Semgrep SAST # Report-only SAST gate (constructorfabric/insight#1797, split from #1463; umbrella #1478). # -# p/default findings surface in the GitHub "Security -> Code scanning" tab (SARIF upload) +# Findings (p/default + the p/rust, p/csharp, p/python language packs) 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. @@ -51,6 +52,9 @@ jobs: semgrep/semgrep:1.131.0@sha256:6bd07d7b166b097e1384f41b94a62d8c8a26a4fff8713992c296e053310da01f \ semgrep scan \ --config p/default \ + --config p/rust \ + --config p/csharp \ + --config p/python \ --exclude-rule generic.secrets.security.detected-generic-secret \ --sarif --output semgrep.sarif \ --metrics off @@ -72,7 +76,7 @@ jobs: rules = collections.Counter(x.get("ruleId", "?").split(".")[-1] for x in res) w = out.write w("## Semgrep SAST (report-only)\n\n") - w(f"**{total}** finding(s) from `p/default` (secrets excluded — TruffleHog owns those). " + w(f"**{total}** finding(s) from `p/default` + `p/rust`/`p/csharp`/`p/python` (secrets excluded — TruffleHog owns those). " "This check does **not** block; full details in **Security -> Code scanning**.\n\n") if total: w("| Severity | Count |\n|---|---:|\n") From f706ed5841fdd73bdfca475ca9f887036c3467c3 Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Mon, 20 Jul 2026 18:23:08 +0300 Subject: [PATCH 5/9] ci(security): switch Semgrep to --config auto (align with org GitLab component) Match the org semgrep-scan component's config=auto (per-language auto-detection via the registry). Drops the explicit p/rust/p/csharp/p/python packs and the --metrics off flag (auto requires metrics on to fetch its ruleset). Still report-only, SARIF -> Code Scanning + job summary. Note: on this repo auto currently yields no Rust findings (same coverage as p/default); revisit language packs if Rust/C# depth is needed. Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 84d09ab79..a448ec50d 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -2,7 +2,8 @@ name: Semgrep SAST # Report-only SAST gate (constructorfabric/insight#1797, split from #1463; umbrella #1478). # -# Findings (p/default + the p/rust, p/csharp, p/python language packs) surface in the +# Findings (Semgrep `--config auto`, which auto-detects rules per language via the +# registry — matches the org GitLab semgrep-scan component) 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 @@ -45,19 +46,17 @@ jobs: # 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. run: | docker run --rm \ -v "${{ github.workspace }}:/src" \ -w /src \ semgrep/semgrep:1.131.0@sha256:6bd07d7b166b097e1384f41b94a62d8c8a26a4fff8713992c296e053310da01f \ semgrep scan \ - --config p/default \ - --config p/rust \ - --config p/csharp \ - --config p/python \ + --config auto \ --exclude-rule generic.secrets.security.detected-generic-secret \ - --sarif --output semgrep.sarif \ - --metrics off + --sarif --output semgrep.sarif - name: Summarize findings in the job summary if: always() @@ -76,7 +75,7 @@ jobs: rules = collections.Counter(x.get("ruleId", "?").split(".")[-1] for x in res) w = out.write w("## Semgrep SAST (report-only)\n\n") - w(f"**{total}** finding(s) from `p/default` + `p/rust`/`p/csharp`/`p/python` (secrets excluded — TruffleHog owns those). " + w(f"**{total}** finding(s) from `--config auto` (secrets excluded — TruffleHog owns those). " "This check does **not** block; full details in **Security -> Code scanning**.\n\n") if total: w("| Severity | Count |\n|---|---:|\n") From 6f9efd48ac2646c23a1a820e8f5536a561beb4b3 Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Mon, 20 Jul 2026 18:33:08 +0300 Subject: [PATCH 6/9] ci(security): add p/rust + p/csharp packs alongside --config auto auto matches the org component but has thin Rust/C# coverage (0 Rust findings on this repo). Layer the dedicated language packs on top so the Rust backend and C# identity service are actually scanned (unsafe-usage, temp-dir, unsigned-security-token). Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index a448ec50d..55871c7d6 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -2,8 +2,9 @@ name: Semgrep SAST # Report-only SAST gate (constructorfabric/insight#1797, split from #1463; umbrella #1478). # -# Findings (Semgrep `--config auto`, which auto-detects rules per language via the -# registry — matches the org GitLab semgrep-scan component) surface in the +# 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 @@ -55,6 +56,8 @@ jobs: semgrep/semgrep:1.131.0@sha256:6bd07d7b166b097e1384f41b94a62d8c8a26a4fff8713992c296e053310da01f \ semgrep scan \ --config auto \ + --config p/rust \ + --config p/csharp \ --exclude-rule generic.secrets.security.detected-generic-secret \ --sarif --output semgrep.sarif @@ -75,7 +78,7 @@ jobs: rules = collections.Counter(x.get("ruleId", "?").split(".")[-1] for x in res) w = out.write w("## Semgrep SAST (report-only)\n\n") - w(f"**{total}** finding(s) from `--config auto` (secrets excluded — TruffleHog owns those). " + w(f"**{total}** finding(s) from `auto` + `p/rust`/`p/csharp` (secrets excluded — TruffleHog owns those). " "This check does **not** block; full details in **Security -> Code scanning**.\n\n") if total: w("| Severity | Count |\n|---|---:|\n") From 0208680371e711c9331d19592d4213ba434e56bc Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Tue, 21 Jul 2026 07:37:48 +0300 Subject: [PATCH 7/9] ci(security): upgrade Semgrep image 1.131.0 -> 1.170.0 Bump the digest-pinned Semgrep image to the latest release (was inherited from the closed PR #1463; org GitLab component is on 1.154.x). Config output verified unchanged (auto + p/rust + p/csharp). Report-only. Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 55871c7d6..af3e1b77a 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -53,7 +53,7 @@ jobs: docker run --rm \ -v "${{ github.workspace }}:/src" \ -w /src \ - semgrep/semgrep:1.131.0@sha256:6bd07d7b166b097e1384f41b94a62d8c8a26a4fff8713992c296e053310da01f \ + semgrep/semgrep:1.170.0@sha256:c98f8829eea377274ee4b10656458b078b88232469b2ff913f091c2317347c9d \ semgrep scan \ --config auto \ --config p/rust \ From 3f350b54b156d54424be97f2400b20f52dbfa2f1 Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Tue, 21 Jul 2026 07:47:20 +0300 Subject: [PATCH 8/9] ci(security): link job-summary to branch-filtered Code scanning Turn the 'Security -> Code scanning' text in the job summary into a clickable link filtered to the current branch (GITHUB_HEAD_REF on PRs, else GITHUB_REF_NAME). Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index af3e1b77a..16cd6506c 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -68,10 +68,14 @@ jobs: run: | [ -f semgrep.sarif ] || exit 0 [ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0 - python3 - semgrep.sarif "$GITHUB_STEP_SUMMARY" <<'PY' + # Link the summary to Code scanning filtered to this branch (head branch on PRs). + BRANCH="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" + CS_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/security/code-scanning?query=is%3Aopen+branch%3A${BRANCH}" + python3 - semgrep.sarif "$GITHUB_STEP_SUMMARY" "$CS_URL" <<'PY' import json, sys, collections d = json.load(open(sys.argv[1])) out = open(sys.argv[2], "a") + cs_url = sys.argv[3] res = [x for r in d.get("runs", []) for x in r.get("results", [])] total = len(res) sev = collections.Counter((x.get("level") or "warning") for x in res) @@ -79,7 +83,7 @@ jobs: w = out.write w("## Semgrep SAST (report-only)\n\n") w(f"**{total}** finding(s) from `auto` + `p/rust`/`p/csharp` (secrets excluded — TruffleHog owns those). " - "This check does **not** block; full details in **Security -> Code scanning**.\n\n") + f"This check does **not** block; full details in [Security -> Code scanning (this branch)]({cs_url}).\n\n") if total: w("| Severity | Count |\n|---|---:|\n") for s in ("error", "warning", "note"): From dbc212eb350bc304d89d911a12ac09d83572cc4a Mon Sep 17 00:00:00 2001 From: Konstantin Tursunov Date: Tue, 21 Jul 2026 07:52:40 +0300 Subject: [PATCH 9/9] ci(security): fix job-summary Code-scanning link (pr: on PRs, not branch:) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR runs upload SARIF against the merge ref, so alerts live under pr:, not the branch ref — the branch-filtered link showed 0. Use pr: on pull_request events and branch: on push/schedule/dispatch. Signed-off-by: Konstantin Tursunov --- .github/workflows/semgrep.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 16cd6506c..672f6bb57 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -68,9 +68,15 @@ jobs: run: | [ -f semgrep.sarif ] || exit 0 [ -n "${GITHUB_STEP_SUMMARY:-}" ] || exit 0 - # Link the summary to Code scanning filtered to this branch (head branch on PRs). - BRANCH="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" - CS_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/security/code-scanning?query=is%3Aopen+branch%3A${BRANCH}" + # 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:; on push/schedule/dispatch + # filter by branch. GITHUB_REF_NAME is "/merge" on pull_request events. + 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}" + else + CS_URL="${CS_BASE}?query=is%3Aopen+branch%3A${GITHUB_REF_NAME}" + fi python3 - semgrep.sarif "$GITHUB_STEP_SUMMARY" "$CS_URL" <<'PY' import json, sys, collections d = json.load(open(sys.argv[1])) @@ -83,7 +89,7 @@ jobs: w = out.write w("## Semgrep SAST (report-only)\n\n") w(f"**{total}** finding(s) from `auto` + `p/rust`/`p/csharp` (secrets excluded — TruffleHog owns those). " - f"This check does **not** block; full details in [Security -> Code scanning (this branch)]({cs_url}).\n\n") + f"This check does **not** block; full details in [Security -> Code scanning]({cs_url}).\n\n") if total: w("| Severity | Count |\n|---|---:|\n") for s in ("error", "warning", "note"):