name: PMG E2E Tests

on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main
  workflow_dispatch:

# PRs share a concurrency group (cancel/serialize); Pushes use unique groups to avoid cancellation/queuing
concurrency:
  group: ${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.ref) || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
  contents: read

jobs:
  e2e-test:
    name: PMG E2E Tests - All Package Managers
    runs-on: ${{ matrix.os }}
    timeout-minutes: 20
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
    defaults:
      run:
        shell: bash
    steps:
      - name: Checkout Source
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
        with:
          go-version-file: go.mod
          cache: true

      - name: Enable corepack
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: "24"

      - name: Setup PNPM
        uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

      - name: Setup Bun
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
        with:
          bun-version: latest

      - name: Setup Python
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
        with:
          python-version: "3.11"

      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

      - name: Install Poetry
        uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a

      - name: Build PMG
        run: make

      - name: Add pmg to PATH
        run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH

      - name: Setup PMG
        run: pmg setup install

      - name: Test PMG - Config File is Created
        run: |
          test -f $HOME/.config/safedep/pmg/config.yml

      - name: Test PMG Aliases and Shims are Installed
        run: |
          test -f $HOME/.pmg.rc
          test -d $HOME/.pmg/bin
          for shim in npm pip pip3 pnpm bun uv yarn poetry npx pnpx; do
            test -x $HOME/.pmg/bin/$shim || { echo "Missing shim: $shim"; exit 1; }
          done

      - name: Test NPM - Single Package & Manifest
        run: |
          echo "Testing NPM single package installation..."
          mkdir npm-test && cd npm-test
          pmg --proxy-mode=false npm init -y
          pmg --proxy-mode=false npm install express@5.2.1
          pmg --proxy-mode=false npm install lodash@4.17.21

          # Verification: npm added packages present and manifest updated
          test -d node_modules/express
          test -d node_modules/lodash
          grep -q '"express"' package.json
          grep -q '"lodash"' package.json

          echo "Testing NPM manifest installation..."
          rm -rf node_modules package-lock.json
          pmg --proxy-mode=false npm install

          # Verification: npm lockfile and installed modules exist after manifest install
          test -f package-lock.json
          test -d node_modules/express
          test -d node_modules/lodash
          cd .. && rm -rf npm-test

      - name: Test NPM - Proxy Mode
        run: |
          echo "Testing NPM with proxy-based interception..."
          mkdir npm-proxy-test && cd npm-proxy-test
          pmg npm init -y

          echo "Testing proxy mode single package installation..."
          pmg npm install express@5.2.1
          pmg npm install lodash@4.17.21

          # Verification: packages installed via proxy mode
          test -d node_modules/express
          test -d node_modules/lodash
          grep -q '"express"' package.json
          grep -q '"lodash"' package.json

          echo "Testing proxy mode manifest installation..."
          rm -rf node_modules package-lock.json
          pmg npm install

          # Verification: manifest install via proxy mode works
          test -f package-lock.json
          test -d node_modules/express
          test -d node_modules/lodash

          echo "Testing proxy mode with scoped package..."
          pmg npm install @types/node@18.0.0

          # Verification: scoped package installed via proxy
          test -d node_modules/@types
          test -d node_modules/@types/node
          grep -q '"@types/node"' package.json

          echo "Testing proxy mode dry-run (should not create files)..."
          rm -rf node_modules package-lock.json
          pmg --dry-run npm install

          # Verification: dry-run doesn't create files even in proxy mode
          test ! -d node_modules
          test ! -f package-lock.json

          cd .. && rm -rf npm-proxy-test

      - name: Test PyPI - Proxy Mode
        run: |
          echo "Testing PyPI package managers with proxy-based interception..."
          mkdir pypi-proxy-test && cd pypi-proxy-test

          echo "Setting up Python virtual environment for pip and pip3 tests..."
          python -m venv venv && source venv/bin/activate
          python --version
          pip --version

          echo "Testing pip single package installation via proxy mode..."
          pmg pip install requests==2.32.4
          pmg pip install numpy==2.3.5

          # Verification: packages installed and importable
          python -c "import requests, numpy; print('pip ok:', requests.__version__, numpy.__version__)"

          echo "Testing pip manifest installation via proxy mode..."
          pmg pip freeze > requirements.txt
          pmg pip uninstall -y requests numpy
          pmg pip install -r <(grep -v '^PMG:' requirements.txt)
          python -c "import requests, numpy; print('pip manifest ok:', requests.__version__, numpy.__version__)"
          deactivate

          echo "Setting up Python virtual environment for pip3 tests..."
          python -m venv venv3 && source venv3/bin/activate
          python --version
          pip3 --version

          echo "Testing pip3 single package installation via proxy mode..."
          pmg pip3 install requests==2.32.4
          pmg pip3 install numpy==2.3.5

          # Verification: packages installed and importable
          python -c "import requests, numpy; print('pip3 ok:', requests.__version__, numpy.__version__)"

          echo "Testing pip3 manifest installation via proxy mode..."
          pmg pip3 freeze > requirements3.txt
          pmg pip3 uninstall -y requests numpy
          pmg pip3 install -r <(grep -v '^PMG:' requirements3.txt)
          python -c "import requests, numpy; print('pip3 manifest ok:', requests.__version__, numpy.__version__)"
          deactivate

          echo "Testing uv add and uv pip install via proxy mode..."
          mkdir uv-proxy && cd uv-proxy
          pmg uv init --no-readme
          pmg uv add requests==2.32.4
          pmg uv add numpy==2.3.5

          # Verification: pyproject.toml lists expected dependencies
          test -f pyproject.toml
          grep -q 'requests' pyproject.toml
          grep -q 'numpy' pyproject.toml

          echo "Sync environment and verify installations..."
          pmg uv sync
          pmg uv pip show requests >/dev/null
          pmg uv pip show numpy >/dev/null

          echo "Testing uv pip install from requirements via proxy mode..."
          pmg uv pip freeze > requirements.txt
          pmg uv pip install -r <(grep -v '^PMG:' requirements.txt)
          pmg uv pip show requests >/dev/null
          pmg uv pip show numpy >/dev/null
          cd ..

          cd .. && rm -rf pypi-proxy-test

      - name: Test PyPI - httpx NO_PROXY Regression (#339)
        run: |
          echo "Testing that proxy-mode NO_PROXY does not crash Python httpx (#339)..."
          HTTPX_TESTDIR=$(mktemp -d) && cd "$HTTPX_TESTDIR"
          pmg uv init --no-readme
          pmg uv add httpx==0.28.1

          # Run under proxy mode (default). PMG injects NO_PROXY into the child env;
          # the bracketed [::1] it used to inject made urllib/httpx raise
          # "InvalidURL: Invalid port: ':1]'" when constructing a client.
          pmg uv run python -c '
          import os, httpx
          np = os.environ.get("NO_PROXY") or os.environ.get("no_proxy") or ""
          assert "::1" in np, "proxy-mode NO_PROXY not set, test is not exercising the fix: %r" % np
          assert "[::1]" not in np, "NO_PROXY still contains bracketed loopback: %r" % np
          httpx.Client().close()
          print("httpx ok; NO_PROXY=" + np)
          '

          cd - && rm -rf "$HTTPX_TESTDIR"

      - name: Test PNPM - Single Package & Manifest
        run: |
          echo "Testing PNPM single package installation..."
          PNPM_TESTDIR=$(mktemp -d) && cd "$PNPM_TESTDIR"
          pmg --proxy-mode=false pnpm init
          pmg --proxy-mode=false pnpm add express@5.2.1
          pmg --proxy-mode=false pnpm add lodash@4.17.21

          # Verification: pnpm packages installed and lockfile created
          test -d node_modules/express
          test -d node_modules/lodash
          test -f pnpm-lock.yaml

          echo "Testing PNPM manifest installation..."
          rm -rf node_modules pnpm-lock.yaml
          pmg --proxy-mode=false pnpm install

          # Verification: pnpm lockfile and modules exist after manifest install
          test -f pnpm-lock.yaml
          test -d node_modules/express
          test -d node_modules/lodash
          cd - && rm -rf "$PNPM_TESTDIR"

      - name: Test Bun - Single Package & Manifest
        run: |
          echo "Testing Bun single package installation..."
          mkdir bun-test && cd bun-test
          pmg --proxy-mode=false bun init -y
          pmg --proxy-mode=false bun add express@5.2.1
          pmg --proxy-mode=false bun add lodash@4.17.21

          # Verification: bun packages installed and lockfile created
          test -d node_modules/express
          test -d node_modules/lodash
          test -f bun.lock

          echo "Testing Bun manifest installation..."
          rm -rf node_modules bun.lock
          pmg --proxy-mode=false bun install

          # Verification: bun lockfile and modules exist after manifest install
          test -f bun.lock
          test -d node_modules/express
          test -d node_modules/lodash

          echo "Testing Bun frozen manifest installation with bun ci..."
          rm -rf node_modules
          pmg --proxy-mode=false bun ci

          # Verification: bun lockfile and modules exist after frozen manifest install
          test -f bun.lock
          test -d node_modules/express
          test -d node_modules/lodash
          cd .. && rm -rf bun-test

      # Runs before the classic-yarn step so corepack's Berry shim stays the `yarn`
      # on PATH (the later `npm install -g yarn@1.22.22` would shadow it).
      - name: Test safedep-test-pkg is Blocked using Yarn (Berry) Proxy mode
        env:
          COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
          YARN_NODE_LINKER: node-modules
          YARN_ENABLE_HARDENED_MODE: "0"
          # CI=true would enable immutable installs and fail before the proxy can block.
          YARN_ENABLE_IMMUTABLE_INSTALLS: "false"
        run: |
          echo "Testing that safedep-test-pkg is blocked with Yarn Berry in proxy mode..."
          YARN_BERRY_TESTDIR=$(mktemp -d) && cd "$YARN_BERRY_TESTDIR"
          npm init -y

          # Pin Yarn Berry without running an install (corepack use would trigger one).
          npm pkg set packageManager=yarn@4.16.0
          yarn --version

          # A non-zero exit alone is not enough — yarn could fail for unrelated
          # reasons (setup, network, CA trust) and falsely pass. Require PMG's
          # malware block signal in the output.
          if output=$(pmg yarn add safedep-test-pkg@0.1.3 2>&1); then
            echo "$output"
            echo "ERROR: safedep-test-pkg was not blocked with Yarn Berry!"
            exit 1
          fi
          echo "$output"
          if ! echo "$output" | grep -q "Malicious package blocked"; then
            echo "ERROR: yarn add failed, but not due to a PMG malware block"
            exit 1
          fi
          echo "SUCCESS: safedep-test-pkg correctly blocked with Yarn Berry"

          if [ -d "node_modules/safedep-test-pkg" ]; then
            echo "ERROR: safedep-test-pkg found in node_modules!"
            exit 1
          else
            echo "SUCCESS: safedep-test-pkg not present in node_modules"
          fi
          cd - && rm -rf "$YARN_BERRY_TESTDIR"

      - name: Test Yarn - Single Package & Manifest
        run: |
          echo "Testing Yarn single package installation..."
          export YARN_ENABLE_HARDENED_MODE=0
          npm install -g yarn@1.22.22
          yarn --version

          YARN_TESTDIR=$(mktemp -d) && cd "$YARN_TESTDIR"
          pmg --proxy-mode=false yarn init -y
          pmg --proxy-mode=false yarn add express@5.2.1
          pmg --proxy-mode=false yarn add lodash@4.17.21

          # Verification: yarn packages installed and lockfile created
          test -d node_modules/express
          test -d node_modules/lodash
          test -f yarn.lock

          echo "Testing Yarn manifest installation..."
          rm -rf node_modules yarn.lock
          pmg --proxy-mode=false yarn install

          # Verification: yarn lockfile and modules exist after manifest install
          test -f yarn.lock
          test -d node_modules/express
          test -d node_modules/lodash
          cd - && rm -rf "$YARN_TESTDIR"

      - name: Test NPX - Package Execution
        run: |
          echo "Testing NPX package execution..."
          mkdir npx-test && cd npx-test

          echo "Testing npx with a simple package..."
          pmg --proxy-mode=false npx cowsay@1.6.0 "Hello from pmg npx" | tee npx-output.txt

          # Verification: cowsay output contains our message
          grep -q "Hello from pmg npx" npx-output.txt

          echo "Testing npx with --package flag..."
          pmg --proxy-mode=false npx --package cowsay@1.6.0 -- cowsay "Hello with package flag" | tee npx-pkg-output.txt

          # Verification: package flag execution produces expected output
          grep -q "Hello with package flag" npx-pkg-output.txt

          echo "Testing npx dry-run mode..."
          pmg --proxy-mode=false --dry-run npx cowsay@1.6.0 "This should not execute" | tee npx-dry-output.txt

          # Verification: dry-run should NOT produce cowsay ASCII art (cow face ^__^ should not appear)
          ! grep -q '\^__\^' npx-dry-output.txt

          cd .. && rm -rf npx-test

      - name: Test PNPX - Package Execution
        run: |
          echo "Testing PNPX package execution..."
          PNPX_TESTDIR=$(mktemp -d) && cd "$PNPX_TESTDIR"

          echo "Testing pnpx with a simple package..."
          pmg --proxy-mode=false pnpx cowsay@1.6.0 "Hello from pmg pnpx" | tee pnpx-output.txt

          # Verification: cowsay output contains our message
          grep -q "Hello from pmg pnpx" pnpx-output.txt

          echo "Testing pnpx with --package flag..."
          pmg --proxy-mode=false pnpx --package cowsay@1.6.0 -- cowsay "Hello with package flag" | tee pnpx-pkg-output.txt

          # Verification: package flag execution produces expected output
          grep -q "Hello with package flag" pnpx-pkg-output.txt

          echo "Testing pnpx dry-run mode..."
          pmg --proxy-mode=false --dry-run pnpx cowsay@1.6.0 "This should not execute" | tee pnpx-dry-output.txt

          # Verification: dry-run should NOT produce cowsay ASCII art (cow face ^__^ should not appear)
          ! grep -q '\^__\^' pnpx-dry-output.txt

          cd - && rm -rf "$PNPX_TESTDIR"

      - name: Test Pip - Single Package & Manifest
        run: |
          echo "Testing Pip single package installation..."
          mkdir pip-test && cd pip-test
          python -m venv venv && source venv/bin/activate
          pmg --proxy-mode=false pip install requests==2.32.4
          pmg --proxy-mode=false pip install numpy==2.3.5
          pmg --proxy-mode=false pip freeze > requirements.txt

          # Verification: requirements.txt contains expected packages
          test -s requirements.txt
          grep -E '^requests==' requirements.txt
          grep -E '^numpy==' requirements.txt

          echo "Testing Pip manifest installation..."
          pmg --proxy-mode=false pip uninstall -y requests numpy
          pmg --proxy-mode=false pip install -r requirements.txt

          # Verification: imported packages are available in the environment
          python -c "import requests, numpy; print(requests.__version__); print(numpy.__version__)"
          deactivate
          cd .. && rm -rf pip-test

      - name: Test Pip3 - Single Package & Manifest
        run: |
          echo "Testing Pip3 single package installation..."
          mkdir pip3-test && cd pip3-test
          python -m venv venv && source venv/bin/activate
          pmg --proxy-mode=false pip3 install requests==2.32.4
          pmg --proxy-mode=false pip3 install numpy==2.3.5
          pmg --proxy-mode=false pip3 freeze > requirements.txt

          # Verification: requirements.txt contains expected packages
          test -s requirements.txt
          grep -E '^requests==' requirements.txt
          grep -E '^numpy==' requirements.txt

          echo "Testing Pip3 manifest installation..."
          pmg --proxy-mode=false pip3 uninstall -y requests numpy
          pmg --proxy-mode=false pip3 install -r requirements.txt

          # Verification: imported packages are available in the environment
          python -c "import requests, numpy; print(requests.__version__); print(numpy.__version__)"
          deactivate
          cd .. && rm -rf pip3-test

      - name: Test UV - Single Package & Manifest
        run: |
          echo "Testing UV single package installation..."
          mkdir uv-test && cd uv-test
          pmg --proxy-mode=false uv init --no-readme
          pmg --proxy-mode=false uv add requests==2.32.4
          pmg --proxy-mode=false uv add numpy==2.3.5

          # Verification: pyproject.toml lists expected dependencies
          test -f pyproject.toml
          grep -q 'requests' pyproject.toml
          grep -q 'numpy' pyproject.toml

          echo "Testing UV manifest installation..."
          rm -rf .venv uv.lock
          pmg --proxy-mode=false uv sync

          # Verification: uv lockfile and virtualenv created; packages present
          test -d .venv
          test -f uv.lock
          pmg --proxy-mode=false uv pip show requests >/dev/null
          pmg --proxy-mode=false uv pip show numpy >/dev/null

          echo "Testing UV pip commands..."
          pmg --proxy-mode=false uv pip freeze > requirements.txt
          pmg --proxy-mode=false uv pip install -r requirements.txt
          pmg --proxy-mode=false uv pip sync requirements.txt

          # Verification: uv pip can show installed packages after requirements sync
          pmg --proxy-mode=false uv pip show requests >/dev/null
          pmg --proxy-mode=false uv pip show numpy >/dev/null
          cd .. && rm -rf uv-test

      - name: Test Poetry - Single Package & Manifest
        run: |
          echo "Testing Poetry single package installation..."
          mkdir poetry-test && cd poetry-test
          pmg --proxy-mode=false poetry init --name poetry-test --no-interaction --quiet
          pmg --proxy-mode=false poetry add requests==2.32.4
          pmg --proxy-mode=false poetry add numpy==2.3.5

           # Verification: pyproject.toml dependencies updated
           test -f pyproject.toml
           grep -q 'requests' pyproject.toml
           grep -q 'numpy' pyproject.toml

          echo "Testing Poetry manifest installation..."
          rm -rf .venv poetry.lock
          pmg --proxy-mode=false poetry install --no-root
          cd .. && rm -rf poetry-test

      - name: Test Malicious Package Detection
        run: |
          echo "Testing malicious package detection..."
          mkdir malicious-test && cd malicious-test
          pmg --proxy-mode=false npm init -y
          ! pmg --proxy-mode=false npm install nyc-config@10.0.0 || echo "Malicious package correctly blocked"
          cd .. && rm -rf malicious-test

      - name: Test safedep-test-pkg is Blocked using Proxy mode
        run: |
          echo "Testing that safedep-test-pkg is blocked..."
          mkdir safedep-test-pkg-test && cd safedep-test-pkg-test
          pmg npm init -y
          # Attempt to install safedep-test-pkg - should fail
          if pmg npm --no-cache --prefer-online i safedep-test-pkg@0.1.3; then
            echo "ERROR: safedep-test-pkg was not blocked!"
            exit 1
          else
            echo "SUCCESS: safedep-test-pkg correctly blocked"
          fi
          # Verify package is not installed locally
          if [ -d "node_modules/safedep-test-pkg" ]; then
            echo "ERROR: safedep-test-pkg found in node_modules!"
            exit 1
          else
            echo "SUCCESS: safedep-test-pkg not present in node_modules"
          fi
          cd .. && rm -rf safedep-test-pkg-test

      - name: Test PMG Modes
        run: |
          echo "Testing different PMG modes..."
          mkdir pmg-modes-test && cd pmg-modes-test
          pmg npm init -y
          # Mode: --dry-run should not create node_modules or lockfiles
          pmg --proxy-mode=false --dry-run npm install express
          # Verification: no files created during dry-run
          test ! -d node_modules
          test ! -f package-lock.json

          # Mode: --silent should install without noisy output
          pmg --proxy-mode=false --silent npm install express
          # Verification: package installed
          test -d node_modules/express
          # Clean and test --verbose installation
          rm -rf node_modules package-lock.json
          pmg --proxy-mode=false --verbose npm install express
          # Verification: package installed
          test -d node_modules/express

          # Clean and test --debug with log output
          rm -rf node_modules package-lock.json
          pmg --proxy-mode=false --debug --log debug.json npm install express
          # Verification: debug log written
          test -f debug.json

          # Mode: --paranoid may require cloud credentials; run non-blocking with dry-run
          pmg --proxy-mode=false --paranoid --dry-run npm install express || true
          cd .. && rm -rf pmg-modes-test

  sandbox-e2e-macos:
    name: Sandbox E2E - macOS
    runs-on: macos-latest
    timeout-minutes: 10
    steps:
      - name: Checkout Source
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
        with:
          go-version-file: go.mod
          cache: true

      - name: Enable corepack
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: "24"

      - name: Setup PNPM
        uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

      - name: Build PMG
        run: make

      - name: Add pmg to PATH
        run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH

      - name: Setup PMG
        run: pmg setup install

      - name: Create Test Directories for Sandbox Permissions Tests
        run: mkdir -p ~/.aws ~/.gcloud ~/.kube ~/.ssh

      - name: Create Test Files for Sandbox Permissions Tests
        run: |
          touch ~/.aws/credentials
          touch ~/.gcloud/credentials.json
          touch ~/.kube/config
          touch ~/.ssh/id_rsa
          touch ./.env

      - name: Run Sandbox E2E Test
        env:
          E2E_ENV_SEEDED: "1"
          GITHUB_TOKEN: pmg-e2e-canary
          gh_token: pmg-e2e-canary
          AWS_SECRET_ACCESS_KEY: pmg-e2e-canary
          OP_SERVICE_ACCOUNT_TOKEN: pmg-e2e-canary
          CLOUDFLARE_API_TOKEN: pmg-e2e-canary
          TWINE_PASSWORD: pmg-e2e-canary
          NPM_TOKEN: pmg-e2e-keep
          NODE_AUTH_TOKEN: pmg-e2e-keep
        run: pmg --sandbox --sandbox-enforce npm exec -- node test/sandbox-e2e.js

      - name: Run Package Manager E2E Test
        run: pmg --sandbox --sandbox-enforce npm exec -- node test/pm-e2e.js

  sandbox-e2e-linux:
    name: Sandbox E2E - Linux (Bubblewrap)
    runs-on: ubuntu-latest
    timeout-minutes: 10
    defaults:
      run:
        shell: bash
    env:
      PMG_SANDBOX_DRIVER: bubblewrap
    steps:
      - name: Checkout Source
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
        with:
          go-version-file: go.mod
          cache: true

      - name: Enable corepack
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: "24"

      - name: Setup PNPM
        uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

      - name: Install Bubblewrap
        run: sudo apt-get update && sudo apt-get install -y bubblewrap

      - name: Verify Bubblewrap Installation
        run: bwrap --version

      - name: Build PMG
        run: make

      - name: Add pmg to PATH
        run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH

      - name: Setup PMG
        run: pmg setup install

      - name: Create Test Directories for Sandbox Permissions Tests
        run: mkdir -p ~/.aws ~/.gcloud ~/.kube ~/.ssh ~/.gnupg ~/.docker

      - name: Create Test Files for Sandbox Permissions Tests
        run: |
          touch ~/.aws/credentials
          touch ~/.gcloud/credentials.json
          touch ~/.kube/config
          touch ~/.ssh/id_rsa
          touch ~/.gnupg/pubring.kbx
          touch ~/.docker/config.json
          touch ./.env

      - name: Disable AppArmor for Bubblewrap
        run: |
          sudo systemctl stop apparmor
          sudo systemctl disable apparmor
          sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

      - name: Run Sandbox E2E Test
        env:
          E2E_ENV_SEEDED: "1"
          GITHUB_TOKEN: pmg-e2e-canary
          gh_token: pmg-e2e-canary
          AWS_SECRET_ACCESS_KEY: pmg-e2e-canary
          OP_SERVICE_ACCOUNT_TOKEN: pmg-e2e-canary
          CLOUDFLARE_API_TOKEN: pmg-e2e-canary
          TWINE_PASSWORD: pmg-e2e-canary
          NPM_TOKEN: pmg-e2e-keep
          NODE_AUTH_TOKEN: pmg-e2e-keep
        run: pmg --sandbox --sandbox-enforce npm exec -- node test/sandbox-e2e.js

      - name: Run Package Manager E2E Test
        run: pmg --sandbox --sandbox-enforce npm exec -- node test/pm-e2e.js

  sandbox-e2e-linux-landlock:
    name: Sandbox E2E - Linux (Landlock)
    runs-on: ubuntu-latest
    timeout-minutes: 10
    defaults:
      run:
        shell: bash
    env:
      PMG_SANDBOX_DRIVER: landlock
      PMG_LANDLOCK_E2E: "1"
      # Redirect npm's cache into /tmp so it sits outside any pre-existing
      # state in /home/runner/.npm (which setup-node / the runner image may
      # have populated with state the sandbox policy doesn't account for).
      # The npm-restrictive profile already grants /tmp/** read+write.
      npm_config_cache: /tmp/npm-cache
    steps:
      - name: Checkout Source
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
        with:
          go-version-file: go.mod
          cache: true

      - name: Enable corepack
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: "24"

      - name: Setup PNPM
        uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

      - name: Build PMG
        run: make

      - name: Add pmg to PATH
        run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH

      - name: Setup PMG
        run: pmg setup install

      - name: Create Test Directories for Sandbox Permissions Tests
        run: mkdir -p ~/.aws ~/.gcloud ~/.kube ~/.ssh ~/.gnupg ~/.docker

      - name: Create Test Files for Sandbox Permissions Tests
        run: |
          touch ~/.aws/credentials
          touch ~/.gcloud/credentials.json
          touch ~/.kube/config
          touch ~/.ssh/id_rsa
          touch ~/.gnupg/pubring.kbx
          touch ~/.docker/config.json
          touch ./.env

      - name: Disable AppArmor for User Namespaces
        run: |
          sudo systemctl stop apparmor
          sudo systemctl disable apparmor
          sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

      - name: Verify Landlock Available
        run: |
          if [ ! -d /sys/kernel/security/landlock ] && ! grep -q landlock /proc/kallsyms 2>/dev/null; then
            echo "Landlock not detected by sysfs probe (continuing — driver will fail loudly if unavailable)"
          fi
          uname -a

      - name: Run Landlock Helper E2E Tests (Go)
        run: go test -count=1 -v -run TestLandlockHelper ./sandbox/platform/...

      # The npm leaf profile's npm_config_* env allow is what keeps the
      # job-level npm_config_cache redirect alive.
      - name: Run Sandbox E2E Test
        env:
          E2E_ENV_SEEDED: "1"
          GITHUB_TOKEN: pmg-e2e-canary
          gh_token: pmg-e2e-canary
          AWS_SECRET_ACCESS_KEY: pmg-e2e-canary
          OP_SERVICE_ACCOUNT_TOKEN: pmg-e2e-canary
          CLOUDFLARE_API_TOKEN: pmg-e2e-canary
          TWINE_PASSWORD: pmg-e2e-canary
          NPM_TOKEN: pmg-e2e-keep
          NODE_AUTH_TOKEN: pmg-e2e-keep
        run: pmg --sandbox --sandbox-enforce npm exec -- node test/sandbox-e2e.js

      - name: Run Package Manager E2E Test
        run: pmg --sandbox --sandbox-enforce npm exec -- node test/pm-e2e.js
