deps(security): cryptography >= 46.0.7 (GHSA-p423-j2cm-9vmq) #331
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: MIT | |
| # | |
| # PR Gate — synchronous merge contract for `main`. | |
| # | |
| # Every job declared here is a required status check in the branch-protection | |
| # ruleset for `main` (see .github/BRANCH_PROTECTION_MAIN.md). Jobs are | |
| # fail-closed (`continue-on-error: false`) so a green tick deterministically | |
| # means "all gates satisfied". | |
| # | |
| # Invariants: | |
| # * Jobs are content-aware: they exit successfully when no relevant files | |
| # changed, preserving merge queue throughput without losing enforcement. | |
| # * All third-party actions are pinned by 40-character commit SHA | |
| # (repo-policy validates this on every run). | |
| # * Permissions follow principle of least privilege. | |
| name: PR Gate | |
| on: | |
| pull_request: | |
| branches: [main] | |
| merge_group: | |
| permissions: | |
| contents: read | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' | |
| concurrency: | |
| group: pr-gate-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: true | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # repo-policy — workflow hygiene invariants (permissions, pinning, actionlint) | |
| # --------------------------------------------------------------------------- | |
| repo-policy: | |
| name: repo-policy | |
| runs-on: ubuntu-latest | |
| continue-on-error: false | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| - name: Validate workflow policy invariants | |
| run: | | |
| set -euo pipefail | |
| for wf in .github/workflows/*.yml; do | |
| grep -Eq '^permissions:' "$wf" || { echo "ERROR: $wf missing permissions"; exit 1; } | |
| if grep -Eq '^\s*pull_request_target:' "$wf"; then | |
| echo "ERROR: $wf uses forbidden pull_request_target" | |
| exit 1 | |
| fi | |
| done | |
| - name: Enforce pinned actions policy | |
| run: | | |
| set -euo pipefail | |
| python - <<'PY' | |
| import re | |
| from pathlib import Path | |
| pattern = re.compile(r'^\s*uses:\s*([^\s#]+)') | |
| violations = [] | |
| for wf in sorted(Path('.github/workflows').glob('*.yml')): | |
| for lineno, line in enumerate(wf.read_text(encoding='utf-8').splitlines(), start=1): | |
| m = pattern.search(line) | |
| if not m: | |
| continue | |
| ref = m.group(1) | |
| if ref.startswith('./') or ref.startswith('docker://'): | |
| continue | |
| if not re.search(r'@[a-f0-9]{40}$', ref): | |
| violations.append(f"{wf}:{lineno}: unpinned action reference: {ref}") | |
| if violations: | |
| print('\n'.join(violations)) | |
| raise SystemExit(1) | |
| print('Pinned action policy passed.') | |
| PY | |
| - name: Actionlint validation | |
| run: | | |
| set -euo pipefail | |
| docker run --rm -v "$PWD":/repo -w /repo rhysd/actionlint:1.7.8 -color | |
| - name: Inventory and manifest integrity checks | |
| run: | | |
| set -euo pipefail | |
| python scripts/ci/check_inventory_sync.py | |
| python scripts/ci/check_manifest_hashes.py | |
| # --------------------------------------------------------------------------- | |
| # python-quality — ruff + black + mypy on changed Python files | |
| # --------------------------------------------------------------------------- | |
| python-quality: | |
| name: python-quality | |
| runs-on: ubuntu-latest | |
| continue-on-error: false | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python environment | |
| uses: ./.github/actions/setup-geosync | |
| with: | |
| python-version: '3.11' | |
| cache-prefix: pr-quality | |
| - name: Verify pip executable and Python contract | |
| run: | | |
| set -euo pipefail | |
| .venv/bin/python -m pip --version | |
| .venv/bin/python -m pip check | |
| .venv/bin/python - <<'PY' | |
| import tomllib | |
| from pathlib import Path | |
| requires = tomllib.loads(Path('pyproject.toml').read_text(encoding='utf-8'))['project']['requires-python'] | |
| if requires != '>=3.11,<3.13': | |
| raise SystemExit(f'Unexpected requires-python: {requires}') | |
| print(f'requires-python validated: {requires}') | |
| PY | |
| - name: Run lint/type gate on changed Python files | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| base_ref="origin/${{ github.base_ref }}" | |
| changed_files=$(git diff --name-only "$base_ref...$GITHUB_SHA") | |
| else | |
| changed_files=$(git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA") | |
| fi | |
| mapfile -t py_files < <( | |
| printf '%s\n' "$changed_files" \ | |
| | awk '/\.py$/' \ | |
| | while IFS= read -r f; do | |
| [[ -f "$f" ]] && printf '%s\n' "$f" | |
| done | |
| ) | |
| if [[ ${#py_files[@]} -eq 0 ]]; then | |
| echo "No Python file changes detected; python-quality passes." | |
| exit 0 | |
| fi | |
| .venv/bin/ruff check "${py_files[@]}" | |
| .venv/bin/black --check "${py_files[@]}" | |
| .venv/bin/mypy "${py_files[@]}" | |
| # --------------------------------------------------------------------------- | |
| # python-fast-tests — fast deterministic pytest suite | |
| # --------------------------------------------------------------------------- | |
| python-fast-tests: | |
| name: python-fast-tests | |
| runs-on: ubuntu-latest | |
| needs: python-quality | |
| continue-on-error: false | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python environment | |
| uses: ./.github/actions/setup-geosync | |
| with: | |
| python-version: '3.11' | |
| cache-prefix: pr-fast-tests | |
| - name: Verify pip executable | |
| run: .venv/bin/python -m pip --version | |
| - name: Verify test-level classification (fail-fast) | |
| run: | | |
| set -euo pipefail | |
| [[ -x .venv/bin/pytest ]] || { echo ".venv/bin/pytest missing"; exit 1; } | |
| .venv/bin/pytest --collect-only -q \ | |
| tests/connectors/test_fail_closed_connectors.py \ | |
| tests/connectors/test_polygon_adapter_reproducible.py \ | |
| scripts/tests/test_generate_stakeholder_assets.py | |
| - name: Run fast deterministic pytest gate | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| base_ref="origin/${{ github.base_ref }}" | |
| changed_files=$(git diff --name-only "$base_ref...$GITHUB_SHA") | |
| else | |
| changed_files=$(git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA") | |
| fi | |
| if ! printf '%s\n' "$changed_files" | awk ' | |
| /^tests\// || /^core\// || /^backtest\// || /^execution\// || /^src\// || | |
| /^scripts\// || /^pyproject\.toml$/ || /^requirements(-dev)?\.(txt|lock)$/ || | |
| /^pytest\.ini$/ || /^\.github\/workflows\/pr-gate\.yml$/ || /^\.pre-commit-config\.yaml$/ { | |
| found=1; exit | |
| } | |
| END { exit !found } | |
| '; then | |
| echo "No Python runtime/test surface changes detected; python-fast-tests passes." | |
| exit 0 | |
| fi | |
| [[ -x .venv/bin/pytest ]] || { echo ".venv/bin/pytest missing"; exit 1; } | |
| timeout 1200s .venv/bin/pytest tests/ \ | |
| -m "not slow and not heavy_math and not nightly and not flaky" \ | |
| --maxfail=1 \ | |
| -q | |
| - name: Run mandatory connector hygiene tests | |
| run: | | |
| set -euo pipefail | |
| [[ -x .venv/bin/pytest ]] || { echo ".venv/bin/pytest missing"; exit 1; } | |
| timeout 300s .venv/bin/pytest \ | |
| tests/connectors/test_fail_closed_connectors.py \ | |
| tests/connectors/test_polygon_adapter_reproducible.py \ | |
| scripts/tests/test_generate_stakeholder_assets.py \ | |
| --maxfail=1 \ | |
| -q | |
| - name: Run formal verification gate | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| base_ref="origin/${{ github.base_ref }}" | |
| changed_files=$(git diff --name-only "$base_ref...$GITHUB_SHA") | |
| else | |
| changed_files=$(git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA") | |
| fi | |
| if ! printf '%s\n' "$changed_files" | awk ' | |
| /^formal\// || /^cortex_service\// || /^src\// || /^tests\// || | |
| /^Makefile$/ || /^requirements(-dev)?\.(txt|lock)$/ { | |
| found=1; exit | |
| } | |
| END { exit !found } | |
| '; then | |
| echo "No formal-sensitive changes detected; skipping formal-verify." | |
| exit 0 | |
| fi | |
| make formal-verify | |
| # --------------------------------------------------------------------------- | |
| # python-heavy-tests — heavy compute test lane | |
| # --------------------------------------------------------------------------- | |
| python-heavy-tests: | |
| name: python-heavy-tests | |
| runs-on: ubuntu-latest | |
| needs: python-fast-tests | |
| continue-on-error: false | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python environment | |
| uses: ./.github/actions/setup-geosync | |
| with: | |
| python-version: '3.11' | |
| cache-prefix: pr-heavy-tests | |
| - name: Run heavy invariant gate | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| base_ref="origin/${{ github.base_ref }}" | |
| changed_files=$(git diff --name-only "$base_ref...$GITHUB_SHA") | |
| else | |
| changed_files=$(git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA") | |
| fi | |
| if ! printf '%s\n' "$changed_files" | awk ' | |
| /^formal\// || /^cortex_service\// || /^src\// || /^tests\// || | |
| /^scripts\// || /^Makefile$/ || /^core\// { | |
| found=1; exit | |
| } | |
| END { exit !found } | |
| '; then | |
| echo "No heavy-test-sensitive changes detected; skipping test-heavy." | |
| exit 0 | |
| fi | |
| [[ -x .venv/bin/pytest ]] || { echo ".venv/bin/pytest missing"; exit 1; } | |
| timeout 1200s .venv/bin/pytest tests/ \ | |
| -m "slow or heavy_math or nightly" \ | |
| --maxfail=3 \ | |
| -q | |
| # --------------------------------------------------------------------------- | |
| # frontend-gate — Next.js lint/typecheck/tests when apps/web or root manifests change | |
| # --------------------------------------------------------------------------- | |
| frontend-gate: | |
| name: frontend-gate | |
| runs-on: ubuntu-latest | |
| continue-on-error: false | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine if frontend gate applies | |
| id: changes | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| base_ref="origin/${{ github.base_ref }}" | |
| changed_files=$(git diff --name-only "$base_ref...$GITHUB_SHA") | |
| else | |
| changed_files=$(git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA") | |
| fi | |
| if printf '%s\n' "$changed_files" | awk ' | |
| /^apps\/web\// || /^package\.json$/ || /^package-lock\.json$/ { found=1; exit } | |
| END { exit !found } | |
| '; then | |
| echo "frontend_changed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "frontend_changed=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Verify frontend lockfile | |
| if: steps.changes.outputs.frontend_changed == 'true' | |
| run: test -f apps/web/package-lock.json | |
| - name: Setup Node.js | |
| if: steps.changes.outputs.frontend_changed == 'true' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '24' | |
| cache: npm | |
| cache-dependency-path: apps/web/package-lock.json | |
| - name: Run frontend quality and tests | |
| if: steps.changes.outputs.frontend_changed == 'true' | |
| working-directory: apps/web | |
| run: | | |
| npm ci | |
| npm run format:check | |
| npm run lint | |
| npm run typecheck | |
| npm run test -- --ci | |
| - name: Frontend gate not applicable | |
| if: steps.changes.outputs.frontend_changed != 'true' | |
| run: echo 'No frontend changes detected; frontend gate passed deterministically.' | |
| # --------------------------------------------------------------------------- | |
| # dependency-review — GitHub-native vulnerability/license gate on PR diffs | |
| # --------------------------------------------------------------------------- | |
| dependency-review: | |
| name: dependency-review | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: false | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| - name: GitHub dependency review | |
| uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 | |
| with: | |
| fail-on-severity: critical | |
| deny-licenses: GPL-2.0, GPL-3.0, AGPL-3.0 | |
| comment-summary-in-pr: on-failure | |
| # --------------------------------------------------------------------------- | |
| # secrets-supply-chain — bandit SAST + detect-secrets baseline enforcement | |
| # --------------------------------------------------------------------------- | |
| secrets-supply-chain: | |
| name: secrets-supply-chain | |
| runs-on: ubuntu-latest | |
| continue-on-error: false | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python environment | |
| uses: ./.github/actions/setup-geosync | |
| with: | |
| python-version: '3.11' | |
| cache-prefix: pr-security | |
| - name: Verify pip executable | |
| run: .venv/bin/python -m pip --version | |
| - name: Ensure security tooling | |
| run: | | |
| set -euo pipefail | |
| missing=() | |
| .venv/bin/python -m pip show bandit >/dev/null 2>&1 || missing+=(bandit) | |
| .venv/bin/python -m pip show detect-secrets >/dev/null 2>&1 || missing+=(detect-secrets) | |
| if [[ ${#missing[@]} -gt 0 ]]; then | |
| .venv/bin/python -m pip install -c constraints/security.txt "${missing[@]}" | |
| fi | |
| - name: Collect changed files | |
| id: changed | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| base_ref="origin/${{ github.base_ref }}" | |
| git diff --name-only "$base_ref...$GITHUB_SHA" > /tmp/changed-files.txt | |
| else | |
| git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA" > /tmp/changed-files.txt | |
| fi | |
| cat /tmp/changed-files.txt | |
| - name: Bandit on changed security-critical Python files | |
| run: | | |
| set -euo pipefail | |
| mapfile -t bandit_targets < <(awk '/^(core|backtest|execution|src)\/.*\.py$/' /tmp/changed-files.txt) | |
| if [[ ${#bandit_targets[@]} -eq 0 ]]; then | |
| echo "No security-critical Python changes detected; Bandit gate passes." | |
| else | |
| .venv/bin/bandit -ll "${bandit_targets[@]}" | |
| fi | |
| - name: Detect-secrets using baseline | |
| run: | | |
| set -euo pipefail | |
| mapfile -t existing_changed_files < <( | |
| while IFS= read -r f; do | |
| [[ -f "$f" ]] && echo "$f" | |
| done < /tmp/changed-files.txt | |
| ) | |
| if [[ ${#existing_changed_files[@]} -eq 0 ]]; then | |
| echo "No file additions/modifications to scan; secrets gate passes." | |
| exit 0 | |
| fi | |
| .venv/bin/detect-secrets-hook \ | |
| --baseline .github/detect-secrets.baseline \ | |
| --exclude-files '^(INVENTORY\.json|stakeholders/manifest\.json|\.github/detect-secrets\.baseline)$' \ | |
| "${existing_changed_files[@]}" | |
| go-workspace-integrity: | |
| name: go-workspace-integrity | |
| runs-on: ubuntu-latest | |
| continue-on-error: false | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect Go changes | |
| id: go-changes | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| changed=$(git diff --name-only "origin/${{ github.base_ref }}...$GITHUB_SHA") | |
| else | |
| changed=$(git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA") | |
| fi | |
| printf '%s\n' "$changed" | grep -qE '(go\.work|go\.mod|go\.sum|\.go$)' \ | |
| && echo "applicable=true" >> "$GITHUB_OUTPUT" \ | |
| || echo "applicable=false" >> "$GITHUB_OUTPUT" | |
| - name: Set up Go | |
| if: steps.go-changes.outputs.applicable == 'true' | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v5 | |
| with: | |
| go-version-file: 'go.work' | |
| cache: false | |
| - name: go work sync check | |
| if: steps.go-changes.outputs.applicable == 'true' | |
| run: | | |
| set -euo pipefail | |
| go work sync | |
| git diff --exit-code go.work || { | |
| echo "go.work out of sync — run 'go work sync' locally" | |
| exit 1 | |
| } | |
| - name: go vet + build (workspace modules only) | |
| if: steps.go-changes.outputs.applicable == 'true' | |
| run: | | |
| set -euo pipefail | |
| cd examples/go-client | |
| go mod tidy | |
| git diff --exit-code go.mod || { | |
| echo "go.mod out of sync — run 'cd examples/go-client && go mod tidy' locally" | |
| exit 1 | |
| } | |
| go vet ./... | |
| go build ./... | |
| - name: go test (workspace modules only) | |
| if: steps.go-changes.outputs.applicable == 'true' | |
| run: | | |
| set -euo pipefail | |
| cd examples/go-client | |
| go test -race -count=1 ./... | |
| - name: Skip (no Go changes) | |
| if: steps.go-changes.outputs.applicable != 'true' | |
| run: echo "No Go manifest changes — go-workspace-integrity passes." |