Merge pull request #2239 from oscal-compass/security/path-traversal-v3 #792
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
| # This workflow will install Python dependencies, run tests and lint with a variety of Python versions | |
| # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions | |
| name: Trestle Deploy | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - 'v[0-9]*' | |
| permissions: {} | |
| jobs: | |
| set-versions: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| min: ${{ steps.versions.outputs.min }} | |
| max: ${{ steps.versions.outputs.max }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - id: versions | |
| run: | | |
| min_version=$(jq '.PYTHON_MIN' -r version.json) | |
| max_version=$(jq '.PYTHON_MAX' -r version.json) | |
| echo "min=$min_version" | |
| echo "max=$max_version" | |
| echo "min=$min_version" >> $GITHUB_OUTPUT | |
| echo "max=$max_version" >> $GITHUB_OUTPUT | |
| build: | |
| needs: set-versions | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| include: | |
| - os: ubuntu-latest | |
| pip-cache: ~/.cache/pip | |
| hatch-cache: ~/.cache/hatch | |
| hatch-data: ~/.local/share/hatch | |
| - os: macos-latest | |
| pip-cache: ~/Library/Caches/pip | |
| hatch-cache: ~/Library/Caches/hatch | |
| hatch-data: ~/Library/Application Support/hatch | |
| - os: windows-latest | |
| pip-cache: ~\AppData\Local\pip\Cache | |
| hatch-cache: ~\AppData\Local\hatch\Cache | |
| hatch-data: ~\AppData\Local\hatch | |
| python-version: [ '${{ needs.set-versions.outputs.min }}', '${{ needs.set-versions.outputs.max }}'] | |
| steps: | |
| - name: Don't mess with line endings | |
| run: | | |
| git config --global core.autocrlf false | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| submodules: true | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: | | |
| ${{ matrix.pip-cache }} | |
| ${{ matrix.hatch-cache }} | |
| ${{ matrix.hatch-data }} | |
| key: ${{ matrix.os }}-${{ matrix.python-version }}-hatch-${{ hashFiles('pyproject.toml') }} | |
| restore-keys: | | |
| ${{ matrix.os }}-${{ matrix.python-version }}-hatch- | |
| - name: Is core test version | |
| id: core-version | |
| run: echo "core=${{ (matrix.os == 'ubuntu-latest' && matrix.python-version == needs.set-versions.outputs.max ) }}" >> $GITHUB_OUTPUT | |
| - name: Install build tools | |
| run: | | |
| make develop | |
| - name: Setup pre-commit | |
| if: steps.core-version.outputs.core == 'true' | |
| run: | | |
| make pre-commit | |
| - name: Run md document formatting (mdformat) | |
| if: steps.core-version.outputs.core == 'true' | |
| run: | | |
| make mdformat | |
| - name: Run code formatting (ruff) | |
| if: steps.core-version.outputs.core == 'true' | |
| run: | | |
| make code-format | |
| - name: Run code linting (ruff) | |
| if: steps.core-version.outputs.core == 'true' | |
| run: | | |
| make code-lint | |
| - name: Run code typing check (mypy) | |
| if: steps.core-version.outputs.core == 'true' | |
| continue-on-error: true | |
| run: | | |
| make code-typing | |
| - name: Install documentation dependencies | |
| if: steps.core-version.outputs.core == 'true' | |
| run: | | |
| make docs-ubuntu-deps | |
| - name: Validate website content (mkdocs) | |
| if: steps.core-version.outputs.core == 'true' | |
| run: | | |
| make docs-validate | |
| - name: Pytest Fast | |
| if: steps.core-version.outputs.core != 'true' | |
| run: | | |
| make test | |
| - name: Pytest Cov | |
| if: steps.core-version.outputs.core == 'true' | |
| run: | | |
| make test-cov | |
| deploy: | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: deploy-${{ github.ref }} | |
| cancel-in-progress: false | |
| needs: [ build, set-versions ] | |
| permissions: | |
| id-token: write | |
| attestations: write | |
| contents: write # <<< REQUIRED to attach provenance to GitHub Release | |
| environment: | |
| name: release | |
| url: https://pypi.org/p/compliance-trestle | |
| if: >- | |
| github.repository == 'oscal-compass/compliance-trestle' | |
| && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/v')) | |
| steps: | |
| - name: Validate branch name | |
| if: github.ref != 'refs/heads/main' | |
| run: | | |
| if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9]+$ ]]; then | |
| echo "::error::Branch '$GITHUB_REF_NAME' does not match required pattern ^v[0-9]+$" | |
| exit 1 | |
| fi | |
| - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| id: app-token | |
| with: | |
| app-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.PRIVATE_KEY }} | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| submodules: true | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Set up Python ${{ needs.set-versions.outputs.max }} | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: ${{ needs.set-versions.outputs.max }} | |
| - name: Install build tools | |
| run: | | |
| make develop | |
| - name: Validate build command | |
| if: github.ref != 'refs/heads/main' | |
| run: | | |
| BUILD_CMD=$(python -c " | |
| import tomllib, sys | |
| with open('pyproject.toml', 'rb') as f: | |
| cfg = tomllib.load(f) | |
| cmd = cfg.get('tool', {}).get('semantic_release', {}).get('build_command', '') | |
| sys.stdout.write(cmd) | |
| ") | |
| ALLOWED=' | |
| python -m pip install build --upgrade | |
| python -m build | |
| ' | |
| NORM_BUILD=$(echo "$BUILD_CMD" | tr -s '[:space:]' ' ' | sed 's/^ *//;s/ *$//') | |
| NORM_ALLOW=$(echo "$ALLOWED" | tr -s '[:space:]' ' ' | sed 's/^ *//;s/ *$//') | |
| if [ "$NORM_BUILD" != "$NORM_ALLOW" ]; then | |
| echo "::error::build_command in pyproject.toml has been modified from the expected value." | |
| echo "::error::Expected: $NORM_ALLOW" | |
| echo "::error::Got: $NORM_BUILD" | |
| exit 1 | |
| fi | |
| - name: Validate version matches branch | |
| if: github.ref != 'refs/heads/main' | |
| run: | | |
| BRANCH_MAJOR="${GITHUB_REF_NAME#v}" | |
| FILE_VERSION=$(python -c " | |
| import re | |
| with open('trestle/__init__.py') as f: | |
| m = re.search(r\"__version__\s*=\s*['\\\"]([^'\\\"]+)\", f.read()) | |
| print(m.group(1) if m else '') | |
| ") | |
| FILE_MAJOR="${FILE_VERSION%%.*}" | |
| if [ "$BRANCH_MAJOR" != "$FILE_MAJOR" ]; then | |
| echo "::error::Version mismatch: branch $GITHUB_REF_NAME expects major version $BRANCH_MAJOR but trestle/__init__.py has $FILE_VERSION (major: $FILE_MAJOR)" | |
| exit 1 | |
| fi | |
| - name: Validate commit types on maintenance branch | |
| if: github.ref != 'refs/heads/main' | |
| run: | | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -z "$LAST_TAG" ]; then | |
| LAST_TAG=$(git merge-base HEAD origin/main) | |
| echo "No tag found; scanning commits since merge-base with main ($LAST_TAG)" | |
| fi | |
| ALLOWED_TYPES='^(fix|perf|chore|ci|docs|build|refactor|style|test|revert)(\(.*\))?:' | |
| BAD_COMMITS=$(git log "$LAST_TAG"..HEAD --no-merges --format='%s' | grep -iEvx "$ALLOWED_TYPES.*" || true) | |
| if [ -n "$BAD_COMMITS" ]; then | |
| echo "::error::All commits on maintenance branches must use an allowed type: fix, perf, chore, ci, docs, build, refactor, style, test, revert." | |
| echo "::error::The following commits do not match:" | |
| echo "$BAD_COMMITS" | |
| exit 1 | |
| fi | |
| - name: Python Semantic Release | |
| id: release | |
| uses: python-semantic-release/python-semantic-release@350c48fcb3ffcdfd2e0a235206bc2ecea6b69df0 # v10.5.3 | |
| with: | |
| github_token: ${{ steps.app-token.outputs.token }} | |
| # --- Generate provenance for the ACTUAL RELEASE ARTIFACTS --- | |
| - name: Publish package distributions to PyPI | |
| uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1 | |
| # NOTE: DO NOT wrap the conditional in ${{ }} as it will always evaluate to true. | |
| # See https://github.com/actions/runner/issues/1173 | |
| if: steps.release.outputs.released == 'true' | |
| # Sign artifacts to be uploaded to github releases after pypi. Pypi signs it's own | |
| - uses: sigstore/gh-action-sigstore-python@04cffa1d795717b140764e8b640de88853c92acc # v3.3.0 | |
| if: steps.release.outputs.released == 'true' | |
| with: | |
| inputs: "dist/*" | |
| - name: Publish package distributions to GitHub Releases | |
| uses: python-semantic-release/upload-to-gh-release@0a92b5d7ebfc15a84f9801ebd1bf706343d43711 # v9.8.9 | |
| if: steps.release.outputs.released == 'true' | |
| with: | |
| github_token: ${{ steps.app-token.outputs.token }} | |
| merge-main-to-develop: | |
| name: Merge main -> develop | |
| runs-on: ubuntu-latest | |
| needs: deploy | |
| concurrency: | |
| group: ${{ github.ref }}-${{ github.workflow }}-${{ github.job }}-main | |
| cancel-in-progress: true | |
| if: github.ref == 'refs/heads/main' | |
| steps: | |
| - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 | |
| id: app-token | |
| with: | |
| app-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.PRIVATE_KEY }} | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| submodules: true | |
| ref: main | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Get GitHub App User ID | |
| id: get-user-id | |
| run: echo "user-id=$(gh api "/users/${{ env.SLUG }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| SLUG: ${{ steps.app-token.outputs.app-slug }} | |
| - name: Configure Git | |
| run: | | |
| git config --global user.name '${{ env.SLUG }}[bot]' | |
| git config --global user.email '${{ env.ID }}+${{ env.SLUG }}[bot]@users.noreply.github.com' | |
| env: | |
| SLUG: ${{ steps.app-token.outputs.app-slug }} | |
| ID: ${{ steps.get-user-id.outputs.user-id }} | |
| # https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable | |
| - name: Merge Main to Develop | |
| run: | | |
| git checkout develop | |
| git merge --no-ff main -m "chore: Merge back version tags and changelog into develop." | |
| git push origin develop |