Document security policy and threat model #2491
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
| name: CI | |
| on: | |
| push: | |
| tags: | |
| - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 | |
| branches: | |
| - "main" | |
| paths-ignore: | |
| - "docs/**" | |
| - "README.md" | |
| - ".github/workflows/release.yml" | |
| - ".github/workflows/official-site.yml" | |
| pull_request: | |
| branches: | |
| - "main" | |
| paths-ignore: | |
| - "docs/**" | |
| - "README.md" | |
| - ".github/workflows/release.yml" | |
| - ".github/workflows/official-site.yml" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| REGISTRY_USERNAME: lovasoa | |
| REGISTRY_IMAGE: lovasoa/sqlpage | |
| jobs: | |
| compile_and_lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - run: npm ci | |
| - run: npm test | |
| - name: Set up cargo cache | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 | |
| env: | |
| NODE_OPTIONS: --no-deprecation | |
| with: | |
| shared-key: rust-sqlpage-proj-test | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - run: cargo fmt --all -- --check | |
| - run: cargo clippy --all-targets --all-features -- -D warnings | |
| # The database matrix below runs the same Rust test harnesses against | |
| # different DATABASE_URL values. Package the Linux test executables once | |
| # here so those jobs do not recompile SQLPage or its dependencies. | |
| - name: Package Linux Rust test binaries | |
| run: | | |
| set -euo pipefail | |
| rm -rf target/sqlpage-test-binaries | |
| mkdir -p target/sqlpage-test-binaries | |
| cargo test --features odbc-static --no-run --message-format=json \ | |
| | jq -r 'select(.profile.test == true and .executable != null) | .executable' \ | |
| | while IFS= read -r test_binary; do | |
| cp -- "$test_binary" target/sqlpage-test-binaries/ | |
| done | |
| test -n "$(find target/sqlpage-test-binaries -maxdepth 1 -type f -print -quit)" | |
| tar -C target/sqlpage-test-binaries -czf target/sqlpage-linux-test-binaries.tar.gz . | |
| - name: Build Linux binary | |
| run: cargo build --features odbc-static | |
| - name: Upload Linux Rust test binaries | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sqlpage-linux-test-binaries | |
| path: "target/sqlpage-linux-test-binaries.tar.gz" | |
| - name: Upload Linux binary | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sqlpage-linux-debug | |
| path: "target/debug/sqlpage" | |
| test: | |
| runs-on: ubuntu-latest | |
| needs: compile_and_lint | |
| strategy: | |
| matrix: | |
| include: | |
| - database: sqlite | |
| container: "" | |
| db_url: "sqlite::memory:" | |
| - database: postgres | |
| container: postgres | |
| db_url: "postgres://root:Password123!@127.0.0.1/sqlpage" | |
| - database: mysql | |
| container: mysql | |
| db_url: "mysql://root:Password123!@127.0.0.1/sqlpage" | |
| - database: mssql | |
| container: mssql | |
| db_url: "mssql://root:Password123!@127.0.0.1/sqlpage" | |
| - database: odbc | |
| container: postgres | |
| db_url: "Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!" | |
| setup_odbc: true | |
| - database: oracle | |
| container: oracle | |
| db_url: "Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| # Reuse the exact Linux test harnesses produced by compile_and_lint. | |
| # This keeps the DB matrix focused on database behavior instead of | |
| # compiling the same Rust crate five more times. | |
| - name: Download Linux Rust test binaries | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: sqlpage-linux-test-binaries | |
| path: target | |
| - name: Extract Linux Rust test binaries | |
| run: | | |
| mkdir -p target/sqlpage-test-binaries | |
| tar -xzf target/sqlpage-linux-test-binaries.tar.gz -C target/sqlpage-test-binaries | |
| - name: Install PostgreSQL ODBC driver | |
| if: matrix.setup_odbc | |
| run: sudo apt-get install -y odbc-postgresql | |
| - name: Install Oracle ODBC driver | |
| if: matrix.database == 'oracle' | |
| run: | | |
| sudo apt-get install -y alien libaio1t64 libodbcinst2 unixodbc | |
| sudo rpm --import https://yum.oracle.com/RPM-GPG-KEY-oracle-ol8 | |
| wget https://yum.oracle.com/repo/OracleLinux/OL8/oracle/instantclient21/x86_64/getPackage/oracle-instantclient-{basic,odbc}-21.21.0.0.0-1.el8.x86_64.rpm | |
| sudo alien -i oracle-instantclient-basic-21.21.0.0.0-1.el8.x86_64.rpm | |
| sudo alien -i oracle-instantclient-odbc-21.21.0.0.0-1.el8.x86_64.rpm | |
| sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/libaio.so.1 | |
| sudo /usr/lib/oracle/21/client64/bin/odbc_update_ini.sh / /usr/lib/oracle/21/client64/lib | |
| echo "LD_LIBRARY_PATH=/usr/lib/oracle/21/client64/lib:$LD_LIBRARY_PATH" >> "$GITHUB_ENV" | |
| - name: Start database container | |
| if: matrix.container != '' | |
| run: docker compose up --wait ${{ matrix.container }} | |
| - name: Show container logs | |
| if: failure() && matrix.container != '' | |
| run: docker compose logs ${{ matrix.container }} | |
| - name: Run tests against ${{ matrix.database }} | |
| timeout-minutes: 5 | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| test_binaries=(target/sqlpage-test-binaries/*) | |
| if ((${#test_binaries[@]} == 0)); then | |
| echo "No test binaries were found in target/sqlpage-test-binaries" >&2 | |
| exit 1 | |
| fi | |
| for test_binary in "${test_binaries[@]}"; do | |
| echo "::group::$(basename "$test_binary")" | |
| "$test_binary" | |
| echo "::endgroup::" | |
| done | |
| env: | |
| DATABASE_URL: ${{ matrix.db_url }} | |
| MALLOC_CHECK_: 3 | |
| MALLOC_PERTURB_: 10 | |
| windows_test: | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up cargo cache | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 | |
| env: | |
| NODE_OPTIONS: --no-deprecation | |
| - name: Set up Windows incremental cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| target/debug/.fingerprint/sqlpage-* | |
| target/debug/build/sqlpage-* | |
| target/debug/deps/libsqlpage-*.rlib | |
| target/debug/deps/libsqlpage-*.rmeta | |
| target/debug/deps/sqlpage-*.d | |
| target/debug/incremental/sqlpage-* | |
| key: windows-incremental-${{ github.event.pull_request.number || github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| windows-incremental-${{ github.event.pull_request.number || github.ref_name }}- | |
| - run: cargo test | |
| env: | |
| CARGO_INCREMENTAL: 1 | |
| RUST_BACKTRACE: 1 | |
| - name: Upload Windows binary | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sqlpage-windows-debug | |
| path: "target/debug/sqlpage.exe" | |
| playwright: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: compile_and_lint | |
| defaults: | |
| run: | |
| working-directory: ./tests/end-to-end | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: lts/* | |
| cache: 'npm' | |
| cache-dependency-path: ./tests/end-to-end/package-lock.json | |
| - run: sudo apt-get update && sudo apt-get install -y unixodbc-dev | |
| - run: npm ci && npx playwright install --with-deps chromium | |
| # The browser tests exercise the official site, but they do not need a | |
| # separate Rust build. Reuse the binary compiled and tested above. | |
| - name: Download Linux binary | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: sqlpage-linux-debug | |
| path: ${{ runner.temp }}/sqlpage-bin | |
| - name: Start official site and wait for it to be ready | |
| timeout-minutes: 1 | |
| run: | | |
| chmod +x "${{ runner.temp }}/sqlpage-bin/sqlpage" | |
| "${{ runner.temp }}/sqlpage-bin/sqlpage" 2>/tmp/stderrlog & | |
| tail -f /tmp/stderrlog | grep -q "started successfully" | |
| working-directory: ./examples/official-site | |
| - name: Run Playwright tests | |
| run: npx playwright test | |
| - name: show server logs | |
| if: failure() | |
| run: cat /tmp/stderrlog | |
| - uses: actions/upload-artifact@v7 | |
| if: always() | |
| with: | |
| name: playwright-report | |
| path: ./tests/end-to-end/playwright-report/ | |
| retention-days: 30 | |
| docker_build: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| - linux/amd64 | |
| - linux/arm/v7 | |
| - linux/arm64 | |
| variant: | |
| - minimal | |
| - duckdb | |
| exclude: | |
| # DuckDB ODBC is not available for armv7 | |
| - platform: linux/arm/v7 | |
| variant: duckdb | |
| # This build is shared with the Hurl example jobs. | |
| - platform: linux/amd64 | |
| variant: minimal | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - id: suffix | |
| name: Cache name suffix | |
| run: | | |
| suffix="-$(tr '/' '-' <<< "${{ matrix.platform }}")" | |
| if [[ "${{ matrix.variant }}" != "minimal" ]]; then | |
| suffix="${suffix}-${{ matrix.variant }}" | |
| fi | |
| echo "suffix=${suffix}" >> "$GITHUB_OUTPUT" | |
| - id: cache-scope | |
| name: Docker cache scope | |
| run: | | |
| ref_scope="main" | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| ref_scope="pr-${{ github.event.pull_request.number }}" | |
| fi | |
| recipe_hash="${{ hashFiles('Dockerfile', '.cargo/**', 'Cargo.toml', 'Cargo.lock', 'build.rs', 'scripts/**', 'sqlpage/**') }}" | |
| { | |
| echo "current=sqlpage-${ref_scope}${{ steps.suffix.outputs.suffix }}-${recipe_hash}" | |
| echo "main=sqlpage-main${{ steps.suffix.outputs.suffix }}-${recipe_hash}" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.REGISTRY_IMAGE }} | |
| flavor: suffix=${{ steps.suffix.outputs.suffix }} | |
| labels: | | |
| org.opencontainers.image.created=1970-01-01T00:00:00Z | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to Docker Hub | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ env.REGISTRY_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v7 | |
| with: | |
| # Use BuildKit's Git context instead of the mutable runner workspace. | |
| # The dependency cache should be keyed by committed source, not by a | |
| # per-job local context stream. | |
| context: "{{defaultContext}}" | |
| platforms: ${{ matrix.platform }} | |
| target: ${{ matrix.variant }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| cache-from: | | |
| type=gha,scope=${{ steps.cache-scope.outputs.current }} | |
| type=gha,scope=${{ steps.cache-scope.outputs.main }} | |
| type=registry,ref=${{ env.REGISTRY_IMAGE }}:main${{ steps.suffix.outputs.suffix }} | |
| cache-to: type=gha,scope=${{ steps.cache-scope.outputs.current }},mode=max | |
| - name: Export digest | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v7 | |
| if: github.event_name != 'pull_request' | |
| with: | |
| name: digests-${{ matrix.variant }}${{ steps.suffix.outputs.suffix }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| docker_build_amd64_minimal: | |
| name: docker_build (linux/amd64, minimal) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.REGISTRY_IMAGE }} | |
| flavor: suffix=-linux-amd64 | |
| labels: | | |
| org.opencontainers.image.created=1970-01-01T00:00:00Z | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - id: cache-scope | |
| name: Docker cache scope | |
| run: | | |
| ref_scope="main" | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| ref_scope="pr-${{ github.event.pull_request.number }}" | |
| fi | |
| recipe_hash="${{ hashFiles('Dockerfile', '.cargo/**', 'Cargo.toml', 'Cargo.lock', 'build.rs', 'scripts/**', 'sqlpage/**') }}" | |
| { | |
| echo "artifact=sqlpage-${ref_scope}-linux-amd64-hurl-${recipe_hash}" | |
| echo "current=sqlpage-${ref_scope}-linux-amd64-${recipe_hash}" | |
| echo "main=sqlpage-main-linux-amd64-${recipe_hash}" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Login to Docker Hub | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ env.REGISTRY_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Build image for Hurl examples | |
| uses: docker/build-push-action@v7 | |
| with: | |
| # Use BuildKit's Git context instead of the mutable runner workspace. | |
| # The dependency cache should be keyed by committed source, not by a | |
| # per-job local context stream. | |
| context: "{{defaultContext}}" | |
| platforms: linux/amd64 | |
| target: minimal | |
| labels: ${{ steps.meta.outputs.labels }} | |
| tags: | | |
| ${{ steps.meta.outputs.tags }} | |
| ${{ env.REGISTRY_IMAGE }}:main | |
| outputs: type=docker,dest=${{ runner.temp }}/sqlpage.tar | |
| cache-from: | | |
| type=gha,scope=${{ steps.cache-scope.outputs.artifact }} | |
| type=gha,scope=${{ steps.cache-scope.outputs.current }} | |
| type=gha,scope=${{ steps.cache-scope.outputs.main }} | |
| type=registry,ref=${{ env.REGISTRY_IMAGE }}:main-linux-amd64 | |
| cache-to: type=gha,scope=${{ steps.cache-scope.outputs.artifact }},mode=max | |
| - name: Upload SQLPage image | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sqlpage-linux-amd64-minimal-image | |
| path: ${{ runner.temp }}/sqlpage.tar | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Build and push by digest | |
| id: build | |
| if: github.event_name != 'pull_request' | |
| uses: docker/build-push-action@v7 | |
| with: | |
| # Use BuildKit's Git context instead of the mutable runner workspace. | |
| # The dependency cache should be keyed by committed source, not by a | |
| # per-job local context stream. | |
| context: "{{defaultContext}}" | |
| platforms: linux/amd64 | |
| target: minimal | |
| labels: ${{ steps.meta.outputs.labels }} | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| cache-from: | | |
| type=gha,scope=${{ steps.cache-scope.outputs.current }} | |
| type=gha,scope=${{ steps.cache-scope.outputs.main }} | |
| type=registry,ref=${{ env.REGISTRY_IMAGE }}:main-linux-amd64 | |
| cache-to: type=gha,scope=${{ steps.cache-scope.outputs.current }},mode=max | |
| - name: Export digest | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v7 | |
| if: github.event_name != 'pull_request' | |
| with: | |
| name: digests-minimal-linux-amd64 | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| hurl_examples: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| examples: ${{ steps.examples.outputs.examples }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: examples | |
| run: | | |
| examples="$(find examples -mindepth 2 -maxdepth 2 -name test.hurl -print | sed 's#/test.hurl$##' | sort | jq -R -s -c 'split("\n")[:-1]')" | |
| echo "examples=$examples" >> "$GITHUB_OUTPUT" | |
| hurl: | |
| name: hurl (${{ matrix.example }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: | |
| - docker_build_amd64_minimal | |
| - hurl_examples | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| example: ${{ fromJSON(needs.hurl_examples.outputs.examples) }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Hurl | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| version=8.0.0 | |
| archive="hurl-${version}-x86_64-unknown-linux-gnu.tar.gz" | |
| gh release download "$version" --repo Orange-OpenSource/hurl --pattern "$archive" --dir /tmp | |
| tar xzf "/tmp/$archive" -C /tmp | |
| echo "/tmp/hurl-${version}-x86_64-unknown-linux-gnu/bin" >> "$GITHUB_PATH" | |
| - name: Download SQLPage image | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: sqlpage-linux-amd64-minimal-image | |
| path: ${{ runner.temp }}/sqlpage-image | |
| - name: Load SQLPage image | |
| run: | | |
| docker load --input "${{ runner.temp }}/sqlpage-image/sqlpage.tar" | |
| docker tag "${{ env.REGISTRY_IMAGE }}:main" "${{ env.REGISTRY_IMAGE }}:latest" | |
| - name: Run example Hurl test | |
| run: scripts/test-examples-hurl.sh "${{ matrix.example }}" | |
| docker_push: | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'pull_request' | |
| needs: | |
| - docker_build_amd64_minimal | |
| - docker_build | |
| strategy: | |
| matrix: | |
| variant: | |
| - minimal | |
| - duckdb | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v8 | |
| env: | |
| NODE_OPTIONS: --no-deprecation | |
| with: | |
| pattern: digests-${{ matrix.variant }}* | |
| merge-multiple: true | |
| path: /tmp/digests | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.REGISTRY_IMAGE }} | |
| flavor: suffix=${{ matrix.variant != 'minimal' && format('-{0}', matrix.variant) || '' }} | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ env.REGISTRY_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | |
| $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) | |
| - name: Inspect image | |
| run: | | |
| docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} |