Skip to content

test(personfit): execute deterministic Monte Carlo acceptance #7189

test(personfit): execute deterministic Monte Carlo acceptance

test(personfit): execute deterministic Monte Carlo acceptance #7189

# Central multi-language SAST gate for every ContextualWisdomLab repo.
#
# Fills a governance gap left when the duplicate LOCAL Semgrep workflow was
# removed (xtrmLLMBatchPython) in favour of the central required workflows.
# Semgrep auto-detects the languages present, so this runs everywhere and is a
# no-op on repos with no supported source.
#
# semgrep multi-language SAST -> SARIF uploaded under category "semgrep"
#
# Gating is by the JOB result (high sensitivity: fail on WARNING/ERROR, i.e.
# Medium+), ref-independent, exactly like trivy-fs in security-scan.yml. The
# SARIF is uploaded under a DISTINCT category ("semgrep") and is NOT added to
# the code_scanning ruleset rule, so it does not affect auto-merge. The SARIF
# upload is best-effort (continue-on-error) so a repo that has not enabled code
# scanning still gets the gate without a JOB_STATUS_CONFIGURATION_ERROR.
#
# Engine license: Semgrep OSS CLI is LGPL-2.1 (a containerized CLI invoked in
# CI, not linked) — acceptable under the commercial-only OSS policy. Registry
# ruleset p/default is the Semgrep community pack.
name: SAST Semgrep
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, closed]
# Scan every PR base ref, including feature branches used by stacked PRs.
push:
branches: [main, master, develop]
schedule:
- cron: "23 3 * * 1"
repository_dispatch:
types: [sast-semgrep-scan]
concurrency:
group: sast-semgrep-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
changed-scope:
name: Detect changed scope
# The org ruleset IGNORES every `on:` filter (paths, branches, types) when it
# runs this workflow in another repository, and a trigger-level skip would
# leave `.github`'s classic required contexts Pending forever. Both
# mechanisms honour a JOB-level skip, so the doc/image-only decision is made
# here and consumed through `needs`. See
# docs/doctoring/required-workflow-path-filter-boundary.md.
# Fails OPEN: an unreadable, empty, or truncated file list scans everything.
if: github.event.action != 'closed'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
outputs:
code: ${{ steps.scope.outputs.code }}
deps: ${{ steps.scope.outputs.deps }}
steps:
- name: Classify changed paths
id: scope
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
PR: ${{ github.event.pull_request.number }}
EXPECTED_FILES: ${{ github.event.pull_request.changed_files }}
shell: bash
run: |
set -uo pipefail
code=true
deps=true
if [ -n "${PR}" ] && [ -n "${EXPECTED_FILES}" ]; then
changed=""
for attempt in 1 2 3; do
if changed="$(gh api --paginate "repos/${REPO}/pulls/${PR}/files?per_page=100" --jq '.[].filename')" && [ -n "$changed" ]; then
break
fi
changed=""
sleep $((attempt * 3))
done
# GitHub caps /pulls/N/files at 3000 entries; a short list would hide
# source files behind a doc-only verdict, so require an exact count.
if [ -n "$changed" ] && [ "$(printf '%s\n' "$changed" | wc -l | tr -d ' ')" = "${EXPECTED_FILES}" ]; then
code=false
deps=false
while IFS= read -r changed_path; do
case "$changed_path" in
*.md|*.markdown|*.rst|*.png|*.jpg|*.jpeg|*.gif|*.webp|*.bmp|*.ico|LICENSE|LICENSE.txt|COPYING|COPYING.txt|NOTICE|NOTICE.txt|.github/ISSUE_TEMPLATE/*) ;;
*) code=true ;;
esac
case "$changed_path" in
requirements*.txt|*/requirements*.txt|pyproject.toml|*/pyproject.toml|uv.lock|*/uv.lock|pylock.*.toml|*/pylock.*.toml|package.json|*/package.json|package-lock.json|*/package-lock.json|pnpm-lock.yaml|*/pnpm-lock.yaml|yarn.lock|*/yarn.lock|Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock|go.mod|*/go.mod|go.sum|*/go.sum|pom.xml|*/pom.xml|build.gradle|*/build.gradle|build.gradle.kts|*/build.gradle.kts|DESCRIPTION|*/DESCRIPTION) deps=true ;;
esac
done <<<"$changed"
else
echo "::notice::changed-scope could not read a complete PR file list; scanning everything."
fi
fi
echo "code=${code}" >> "$GITHUB_OUTPUT"
echo "deps=${deps}" >> "$GITHUB_OUTPUT"
echo "changed-scope code=${code} deps=${deps}"
semgrep:
name: Semgrep (multi-language SAST)
needs: changed-scope
if: github.event.action != 'closed' && needs.changed-scope.outputs.code == 'true'
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
actions: read
env:
# Deterministic, no telemetry: registry rules are fetched but no scan data
# is sent back.
SEMGREP_SEND_METRICS: "off"
# Semgrep OSS 1.169.0. Keep the immutable manifest reference in one
# place so hosted scans and local reproduction cannot drift.
SEMGREP_IMAGE: "semgrep/semgrep@sha256:2b33f46ba66cf8cc2ad59ccfa7d22951fd00c632c38f1339e84ec8e6e641a942"
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit
- name: Checkout exact submitted revision
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Verify exact submitted revision
env:
EXPECTED_CHECKOUT_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
EXPECTED_CHECKOUT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
actual_sha="$(git rev-parse HEAD)"
if [ "$actual_sha" != "$EXPECTED_CHECKOUT_SHA" ]; then
echo "::error::Semgrep checkout identity mismatch for ${EXPECTED_CHECKOUT_REPOSITORY}: expected ${EXPECTED_CHECKOUT_SHA}, actual ${actual_sha}."
exit 1
fi
echo "SAST_CHECKOUT scanner=semgrep repository=${EXPECTED_CHECKOUT_REPOSITORY} expected_sha=${EXPECTED_CHECKOUT_SHA} actual_sha=${actual_sha}"
- name: Verify pinned Semgrep manifest
run: |
set -euo pipefail
if [[ "${SEMGREP_IMAGE}" =~ ^semgrep/semgrep@sha256:[0-9a-f]{64}$ ]]; then
docker manifest inspect "${SEMGREP_IMAGE}" >/dev/null
echo "Verified immutable Semgrep manifest: ${SEMGREP_IMAGE}"
else
echo "::error::SEMGREP_IMAGE must be a complete semgrep/semgrep@sha256:<64-hex-digest> reference."
exit 1
fi
- name: Run Semgrep (SARIF)
id: semgrep
run: |
set +e
echo "Using ${SEMGREP_IMAGE}"
docker run --rm \
-v "${GITHUB_WORKSPACE}:/src" \
-w /src \
-e SEMGREP_SEND_METRICS=off \
--entrypoint semgrep \
"${SEMGREP_IMAGE}" \
scan \
--config=p/default \
--severity=WARNING \
--severity=ERROR \
--exclude=.github/workflows \
--exclude='docs/research/**/standards' \
--error \
--sarif \
--output=semgrep-results.raw.sarif \
--metrics=off
echo "rc=$?" >> "$GITHUB_OUTPUT"
set -e
- name: Remove explicitly suppressed findings from Semgrep SARIF
id: semgrep_sarif
if: always() && hashFiles('semgrep-results.raw.sarif') != ''
run: |
set -euo pipefail
suppressed_count=$(jq '[.runs[]?.results[]? | select(((.suppressions // []) | length) > 0)] | length' semgrep-results.raw.sarif)
jq '(.runs[]? | .results) |= ((. // []) | map(select(((.suppressions // []) | length) == 0)))' \
semgrep-results.raw.sarif > semgrep-results.sarif
finding_count=$(jq '[.runs[]?.results[]?] | length' semgrep-results.sarif)
echo "suppressed_count=$suppressed_count" >> "$GITHUB_OUTPUT"
echo "finding_count=$finding_count" >> "$GITHUB_OUTPUT"
echo "SEMGREP_SUPPRESSED_COUNT=$suppressed_count SEMGREP_FINDING_COUNT=$finding_count"
- name: Upload Semgrep SARIF to code scanning
if: always() && hashFiles('semgrep-results.sarif') != ''
continue-on-error: true
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
sarif_file: semgrep-results.sarif
category: semgrep
ref: ${{ github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
sha: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Report every Semgrep finding in the job log
if: always() && hashFiles('semgrep-results.sarif') != ''
env:
SEMGREP_RC: ${{ steps.semgrep.outputs.rc }}
SEMGREP_SUPPRESSED_COUNT: ${{ steps.semgrep_sarif.outputs.suppressed_count }}
run: |
set -euo pipefail
finding_count=$(jq '[.runs[]?.results[]?] | length' semgrep-results.sarif)
echo "SEMGREP_FINDING_COUNT=${finding_count} SEMGREP_SUPPRESSED_COUNT=${SEMGREP_SUPPRESSED_COUNT:-missing} SEMGREP_RC=${SEMGREP_RC:-missing}"
jq -r '
.runs[]? as $run
| ($run.tool.driver.rules // []
| map({key: .id, value: (.defaultConfiguration.level // "unknown")})
| from_entries) as $levels
| $run.results[]?
| (.locations[0].physicalLocation // {}) as $location
| "SEMGREP_FINDING rule=\(.ruleId // "unknown")"
+ " level=\(.level // $levels[.ruleId] // "unknown")"
+ " path=\($location.artifactLocation.uri // "unknown")"
+ " line=\($location.region.startLine // 0)"
+ " message=\((.message.text // "no message") | gsub("[\r\n]+"; " "))"
' semgrep-results.sarif
if [ "$finding_count" -eq 0 ] && [ "${SEMGREP_RC:-missing}" != "0" ]; then
echo "SEMGREP_ENGINE_FAILURE rc=${SEMGREP_RC:-missing}: Semgrep failed without a WARNING/ERROR SARIF result; inspect the scan command output above."
fi
- name: Enforce Semgrep gate (fail on Medium+ findings)
if: always() && (steps.semgrep_sarif.outputs.finding_count != '0' || steps.semgrep.outputs.rc != '0')
env:
SEMGREP_RC: ${{ steps.semgrep.outputs.rc }}
SEMGREP_FINDING_COUNT: ${{ steps.semgrep_sarif.outputs.finding_count }}
run: |
if [ "${SEMGREP_FINDING_COUNT:-missing}" != "0" ]; then
echo "::error::Semgrep found WARNING/ERROR (Medium+) findings. Every rule, path, line, and message is listed in the preceding report step and the 'semgrep' code scanning category."
else
echo "::error::Semgrep engine/configuration failed with rc=${SEMGREP_RC}. The concrete scan output and SARIF report are logged above."
fi
exit 1