fix(security): resolve Security CI failures on main #5
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: Desktop CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: desktop-ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| quality: | |
| name: Desktop quality gates | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 45 | |
| outputs: | |
| desktop_changed: ${{ steps.scope.outputs.desktop_changed }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect desktop-impacting changes | |
| id: scope | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BEFORE_SHA: ${{ github.event.before }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| run: | | |
| if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| echo "desktop_changed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [[ "$EVENT_NAME" == "pull_request" ]]; then | |
| compare_from="$BASE_SHA" | |
| else | |
| compare_from="$BEFORE_SHA" | |
| fi | |
| if [[ -z "$compare_from" || "$compare_from" =~ ^0+$ ]]; then | |
| echo "desktop_changed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if ! git cat-file -e "${compare_from}^{commit}" 2>/dev/null; then | |
| echo "desktop_changed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git diff --no-renames --name-only -z "$compare_from" "$HEAD_SHA" \ | |
| | python3 scripts/desktop_ci_scope.py >> "$GITHUB_OUTPUT" | |
| - name: Skip unaffected Desktop checks | |
| if: steps.scope.outputs.desktop_changed != 'true' | |
| run: echo "No Desktop or bundled App Server inputs changed." | |
| - name: Install Tauri Linux dependencies | |
| if: steps.scope.outputs.desktop_changed == 'true' | |
| timeout-minutes: 30 | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| run: | | |
| # Hosted-runner apt stalls on dead mirrors and debconf prompts | |
| # instead of failing; bound every network step and retry so a | |
| # flaky mirror costs minutes, not the job. | |
| packages=( | |
| libwebkit2gtk-4.1-dev | |
| build-essential | |
| curl | |
| wget | |
| file | |
| libxdo-dev | |
| libssl-dev | |
| libayatana-appindicator3-dev | |
| librsvg2-dev | |
| ) | |
| for attempt in 1 2 3; do | |
| if [ "$attempt" -gt 1 ]; then | |
| # The Azure-local mirror goes fully unresponsive at times; | |
| # retrying it is useless. Runner images configure apt through | |
| # different mechanisms — a mirrorlist on some, sources lists | |
| # on others — so point every one of them at the global archive. | |
| if [ -f /etc/apt/apt-mirrors.txt ]; then | |
| echo 'https://archive.ubuntu.com/ubuntu/' | sudo tee /etc/apt/apt-mirrors.txt >/dev/null | |
| fi | |
| sudo sed -i 's|azure.archive.ubuntu.com|archive.ubuntu.com|g' \ | |
| /etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true | |
| fi | |
| # timeout sits inside sudo so its KILL reaches apt itself (a | |
| # TERM relayed through sudo dies unanswered when apt is wedged | |
| # on a stalled socket); the Acquire options make apt abandon | |
| # such sockets on its own instead of waiting forever. | |
| apt_opts=(-o Acquire::ForceIPv4=true -o Acquire::http::Timeout=30 | |
| -o Acquire::https::Timeout=30 -o Acquire::Retries=2) | |
| if sudo -E timeout -k 30 180 apt-get update "${apt_opts[@]}" && | |
| sudo -E timeout -k 30 480 apt-get install -y "${apt_opts[@]}" \ | |
| -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold \ | |
| "${packages[@]}"; then | |
| exit 0 | |
| fi | |
| echo "apt attempt ${attempt} failed or stalled; retrying" >&2 | |
| sleep 15 | |
| done | |
| echo "Tauri dependency install failed after 3 attempts" >&2 | |
| exit 1 | |
| - name: Set up Node | |
| if: steps.scope.outputs.desktop_changed == 'true' | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: desktop/package-lock.json | |
| - name: Set up Rust | |
| if: steps.scope.outputs.desktop_changed == 'true' | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy, rustfmt | |
| - name: Install frontend dependencies | |
| if: steps.scope.outputs.desktop_changed == 'true' | |
| working-directory: desktop | |
| run: npm ci | |
| - name: Verify frontend and protocol | |
| if: steps.scope.outputs.desktop_changed == 'true' | |
| working-directory: desktop | |
| run: | | |
| npm run check:version | |
| npm run check:protocol | |
| npm run typecheck | |
| npm run lint | |
| npm test | |
| npm run build | |
| - name: Prepare Tauri resources for Rust-only checks | |
| if: steps.scope.outputs.desktop_changed == 'true' | |
| working-directory: desktop | |
| run: mkdir -p build/sidecar/dist/deepcode-app-server | |
| - name: Verify Tauri crate | |
| if: steps.scope.outputs.desktop_changed == 'true' | |
| working-directory: desktop/src-tauri | |
| run: | | |
| cargo fmt --check | |
| cargo clippy --all-targets -- -D warnings | |
| cargo test --all-targets | |
| bundle: | |
| name: Bundle ${{ matrix.name }} | |
| needs: quality | |
| if: >- | |
| needs.quality.result == 'success' && | |
| needs.quality.outputs.desktop_changed == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: macOS arm64 | |
| runner: macos-15 | |
| bundle: app | |
| - name: macOS x64 | |
| runner: macos-15-intel | |
| bundle: app | |
| - name: Windows x64 | |
| runner: windows-2022 | |
| bundle: nsis | |
| - name: Linux x64 | |
| runner: ubuntu-22.04 | |
| bundle: deb | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 90 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v5 | |
| - name: Install Tauri Linux dependencies | |
| if: runner.os == 'Linux' | |
| timeout-minutes: 30 | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| run: | | |
| # Hosted-runner apt stalls on dead mirrors and debconf prompts | |
| # instead of failing; bound every network step and retry so a | |
| # flaky mirror costs minutes, not the job. | |
| packages=( | |
| libwebkit2gtk-4.1-dev | |
| build-essential | |
| curl | |
| wget | |
| file | |
| libxdo-dev | |
| libssl-dev | |
| libayatana-appindicator3-dev | |
| librsvg2-dev | |
| patchelf | |
| ) | |
| for attempt in 1 2 3; do | |
| if [ "$attempt" -gt 1 ]; then | |
| # The Azure-local mirror goes fully unresponsive at times; | |
| # retrying it is useless. Runner images configure apt through | |
| # different mechanisms — a mirrorlist on some, sources lists | |
| # on others — so point every one of them at the global archive. | |
| if [ -f /etc/apt/apt-mirrors.txt ]; then | |
| echo 'https://archive.ubuntu.com/ubuntu/' | sudo tee /etc/apt/apt-mirrors.txt >/dev/null | |
| fi | |
| sudo sed -i 's|azure.archive.ubuntu.com|archive.ubuntu.com|g' \ | |
| /etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true | |
| fi | |
| # timeout sits inside sudo so its KILL reaches apt itself (a | |
| # TERM relayed through sudo dies unanswered when apt is wedged | |
| # on a stalled socket); the Acquire options make apt abandon | |
| # such sockets on its own instead of waiting forever. | |
| apt_opts=(-o Acquire::ForceIPv4=true -o Acquire::http::Timeout=30 | |
| -o Acquire::https::Timeout=30 -o Acquire::Retries=2) | |
| if sudo -E timeout -k 30 180 apt-get update "${apt_opts[@]}" && | |
| sudo -E timeout -k 30 480 apt-get install -y "${apt_opts[@]}" \ | |
| -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold \ | |
| "${packages[@]}"; then | |
| exit 0 | |
| fi | |
| echo "apt attempt ${attempt} failed or stalled; retrying" >&2 | |
| sleep 15 | |
| done | |
| echo "Tauri dependency install failed after 3 attempts" >&2 | |
| exit 1 | |
| - name: Set up Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: desktop/package-lock.json | |
| - name: Set up Python for App Server sidecar | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| cache-dependency-path: desktop/sidecar-requirements.lock | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install frontend dependencies | |
| working-directory: desktop | |
| run: npm ci | |
| - name: Create locked sidecar environment | |
| working-directory: desktop | |
| run: npm run setup:sidecar | |
| - name: Build platform bundle | |
| working-directory: desktop | |
| run: npm run tauri -- build --bundles ${{ matrix.bundle }} | |
| - name: Verify bundle resources and runtime | |
| working-directory: desktop | |
| run: >- | |
| node scripts/run-python.mjs scripts/verify-release-bundle.py | |
| --bundle ${{ matrix.bundle }} | |
| - name: Upload bundle smoke artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: deepcode-${{ matrix.name }} | |
| path: desktop/src-tauri/target/release/bundle/ | |
| if-no-files-found: error | |
| retention-days: 7 |