From 52e10c0ee9c73afe8b7381108811f42fc1f1827a Mon Sep 17 00:00:00 2001 From: Ion Mincu Date: Wed, 13 May 2026 19:17:14 +0300 Subject: [PATCH 1/4] ci: add dependabot config and harden integration test scripts Mirror the dependabot.yml from uipath-python and uipath-langchain-python so version-update PRs are not opened. They could not pass integration tests because GitHub does not pass repository secrets to workflows triggered by dependabot, leaving uipath auth without credentials. Add timeout-minutes: 10 to the integration-tests job so hung jobs do not sit "in progress" indefinitely. Add set -e to each run.sh so the first failure is surfaced instead of cascading through init/pack/run and ending with a misleading "UIPATH_ACCESS_TOKEN is not set". Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/dependabot.yml | 9 +++++++++ .github/workflows/integration_tests.yml | 1 + testcases/datetime-server/run.sh | 7 +++---- testcases/ground-to-cloud/run.sh | 7 +++---- testcases/string-server/run.sh | 7 +++---- 5 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a2875d2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 0 + exclude-paths: + - "samples/**" diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index c89d8b8..b70f659 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -36,6 +36,7 @@ jobs: integration-tests: needs: [discover-testcases] runs-on: ubuntu-latest + timeout-minutes: 10 container: image: ghcr.io/astral-sh/uv:python3.12-bookworm strategy: diff --git a/testcases/datetime-server/run.sh b/testcases/datetime-server/run.sh index 2181018..2e46ee8 100644 --- a/testcases/datetime-server/run.sh +++ b/testcases/datetime-server/run.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e cleanup() { echo "Cleaning up..." @@ -49,10 +50,8 @@ echo "Waiting a moment for server to initialize..." sleep 20 echo "Running integration test..." -MCP_SERVER_NAME="$MCP_SERVER_NAME" uv run test.py - -# Capture test exit code -TEST_EXIT_CODE=$? +TEST_EXIT_CODE=0 +MCP_SERVER_NAME="$MCP_SERVER_NAME" uv run test.py || TEST_EXIT_CODE=$? echo "====== MCP Server Output ======" cat mcp_server_output.log diff --git a/testcases/ground-to-cloud/run.sh b/testcases/ground-to-cloud/run.sh index 6a12a55..176d76d 100644 --- a/testcases/ground-to-cloud/run.sh +++ b/testcases/ground-to-cloud/run.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e cleanup() { echo "Cleaning up..." @@ -49,10 +50,8 @@ echo "Waiting a moment for server to initialize..." sleep 20 echo "Running integration test..." -MCP_SERVER_NAME="$MCP_SERVER_NAME" uv run test.py - -# Capture test exit code -TEST_EXIT_CODE=$? +TEST_EXIT_CODE=0 +MCP_SERVER_NAME="$MCP_SERVER_NAME" uv run test.py || TEST_EXIT_CODE=$? echo "====== MCP Server Output ======" cat mcp_server_output.log diff --git a/testcases/string-server/run.sh b/testcases/string-server/run.sh index 3b798f3..ed414aa 100644 --- a/testcases/string-server/run.sh +++ b/testcases/string-server/run.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e cleanup() { echo "Cleaning up..." @@ -49,10 +50,8 @@ echo "Waiting a moment for server to initialize..." sleep 20 echo "Running integration test..." -MCP_SERVER_NAME="$MCP_SERVER_NAME" uv run test.py - -# Capture test exit code -TEST_EXIT_CODE=$? +TEST_EXIT_CODE=0 +MCP_SERVER_NAME="$MCP_SERVER_NAME" uv run test.py || TEST_EXIT_CODE=$? echo "====== MCP Server Output ======" cat mcp_server_output.log From b382fa2a279f9e722dcdbb03a5c12318ca16f053 Mon Sep 17 00:00:00 2001 From: Ion Mincu Date: Wed, 13 May 2026 20:06:21 +0300 Subject: [PATCH 2/4] ci: let Dependabot integration tests run via pull_request_target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supersedes the dependabot.yml disable from the previous commit: instead of blocking Dependabot from opening PRs, let them run integration tests with secrets in scope. Port the pattern from uipath-python PR #1628: - Add pull_request_target trigger alongside pull_request. Both gated by actor so each event fires only for its intended PR source — no double-runs and no secrets exposed to arbitrary fork PRs. - Restrict the Dependabot matrix to alpha only to minimize the blast radius of running PR code with credentials in scope. - actions/checkout uses ref: head.sha in both jobs so the PR's code is tested under pull_request_target (which defaults to base otherwise). - Add a Check secrets availability step that emits a ::warning:: annotation when CLIENT_ID/CLIENT_SECRET/BASE_URL arrive empty, so fork-PR failures have clear context instead of cascading into a misleading UIPATH_ACCESS_TOKEN error downstream. set -e in run.sh and timeout-minutes from the previous commit are kept as-is. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/dependabot.yml | 9 ----- .github/workflows/integration_tests.yml | 46 +++++++++++++++++++++---- 2 files changed, 40 insertions(+), 15 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index a2875d2..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "uv" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - exclude-paths: - - "samples/**" diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index b70f659..bb914b4 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -5,6 +5,14 @@ on: branches: [ main, develop ] pull_request: branches: [ main ] + # pull_request_target runs in the base-branch context and exposes secrets, + # which is required for Dependabot PRs (regular pull_request events do not + # receive repository secrets when triggered by Dependabot). The actor gate + # below restricts this trigger to Dependabot to avoid handing secrets to + # arbitrary fork PRs. + pull_request_target: + branches: [ main ] + types: [opened, synchronize, reopened] permissions: contents: read @@ -13,12 +21,18 @@ permissions: jobs: discover-testcases: + if: | + github.event_name == 'push' || + (github.event_name == 'pull_request' && github.actor != 'dependabot[bot]') || + (github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]') runs-on: ubuntu-latest outputs: testcases: ${{ steps.discover.outputs.testcases }} steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Discover testcases id: discover @@ -43,24 +57,44 @@ jobs: fail-fast: false matrix: testcase: ${{ fromJson(needs.discover-testcases.outputs.testcases) }} - environment: [alpha, cloud] # temporary disable staging + # Dependabot runs are restricted to alpha to minimize blast radius of + # exposing credentials to dependency-bump PRs. + environment: ${{ github.event_name == 'pull_request_target' && fromJson('["alpha"]') || fromJson('["alpha", "cloud"]') }} name: "${{ matrix.testcase }} / ${{ matrix.environment }}" steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Install dependencies run: uv sync + - name: Check secrets availability + env: + CLIENT_ID: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_ID || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_ID }} + CLIENT_SECRET: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_SECRET || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_SECRET }} + BASE_URL: ${{ matrix.environment == 'alpha' && secrets.ALPHA_BASE_URL || matrix.environment == 'cloud' && secrets.CLOUD_BASE_URL }} + PR_ACTOR: ${{ github.event.pull_request.user.login }} + run: | + missing=() + [ -z "$CLIENT_ID" ] && missing+=("CLIENT_ID") + [ -z "$CLIENT_SECRET" ] && missing+=("CLIENT_SECRET") + [ -z "$BASE_URL" ] && missing+=("BASE_URL") + + if [ ${#missing[@]} -gt 0 ]; then + echo "::warning::Missing or empty secrets for ${{ matrix.environment }}: ${missing[*]}. PRs from forks or Dependabot do not receive repository secrets unless configured in Settings → Secrets and variables → Dependabot — workflows triggered by '$PR_ACTOR' need those mirrored, or be re-run by a maintainer from a branch in this repo. Without them, the testcase will fail with a misleading auth error downstream." + fi + - name: Run testcase env: - UIPATH_TENANT_ID: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TENANT_ID || matrix.environment == 'staging' && secrets.STAGING_TENANT_ID || matrix.environment == 'cloud' && secrets.CLOUD_TENANT_ID }} - UIPATH_FOLDER_KEY: ${{ matrix.environment == 'alpha' && secrets.ALPHA_FOLDER_KEY || matrix.environment == 'staging' && secrets.STAGING_FOLDER_KEY || matrix.environment == 'cloud' && secrets.CLOUD_FOLDER_KEY }} - CLIENT_ID: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_ID || matrix.environment == 'staging' && secrets.STAGING_TEST_CLIENT_ID || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_ID }} - CLIENT_SECRET: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_SECRET || matrix.environment == 'staging' && secrets.STAGING_TEST_CLIENT_SECRET || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_SECRET }} - BASE_URL: ${{ matrix.environment == 'alpha' && secrets.ALPHA_BASE_URL || matrix.environment == 'staging' && secrets.STAGING_BASE_URL || matrix.environment == 'cloud' && secrets.CLOUD_BASE_URL }} + UIPATH_TENANT_ID: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TENANT_ID || matrix.environment == 'cloud' && secrets.CLOUD_TENANT_ID }} + UIPATH_FOLDER_KEY: ${{ matrix.environment == 'alpha' && secrets.ALPHA_FOLDER_KEY || matrix.environment == 'cloud' && secrets.CLOUD_FOLDER_KEY }} + CLIENT_ID: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_ID || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_ID }} + CLIENT_SECRET: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_SECRET || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_SECRET }} + BASE_URL: ${{ matrix.environment == 'alpha' && secrets.ALPHA_BASE_URL || matrix.environment == 'cloud' && secrets.CLOUD_BASE_URL }} GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} GITHUB_RUN_ID: ${{ github.run_number }} working-directory: testcases/${{ matrix.testcase }} From f83fd60a4b0e05e1d8ca90ad6023864dd683952c Mon Sep 17 00:00:00 2001 From: Ion Mincu Date: Thu, 14 May 2026 10:53:14 +0300 Subject: [PATCH 3/4] ci: force bash for the Check secrets availability step The container image used by the integration-tests job (ghcr.io/astral-sh/uv:python3.12-bookworm) does not put bash on the default shell path, so GitHub falls back to `sh -e {0}` for inline `run:` blocks. The Check secrets availability step uses bash array syntax (`missing=()`), which is a POSIX-sh parse error and fails the step before any check runs. Set shell: bash explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/integration_tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index bb914b4..ab3d220 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -73,6 +73,7 @@ jobs: run: uv sync - name: Check secrets availability + shell: bash env: CLIENT_ID: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_ID || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_ID }} CLIENT_SECRET: ${{ matrix.environment == 'alpha' && secrets.ALPHA_TEST_CLIENT_SECRET || matrix.environment == 'cloud' && secrets.CLOUD_TEST_CLIENT_SECRET }} From c3bf92d572301d3f99837b942566f11587e3479b Mon Sep 17 00:00:00 2001 From: Ion Mincu Date: Thu, 14 May 2026 11:11:19 +0300 Subject: [PATCH 4/4] ci: gate triggers on PR author instead of github.actor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SonarCloud flagged the use of github.actor in the actor gate as forgeable — on a re-run by a maintainer, github.actor becomes that maintainer rather than the original PR author, which would let a malicious PR bypass the gate. Switch to github.event.pull_request.user.login, which is the immutable original author set by GitHub from the authenticated session. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/integration_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index ab3d220..db853e7 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -23,8 +23,8 @@ jobs: discover-testcases: if: | github.event_name == 'push' || - (github.event_name == 'pull_request' && github.actor != 'dependabot[bot]') || - (github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]') + (github.event_name == 'pull_request' && github.event.pull_request.user.login != 'dependabot[bot]') || + (github.event_name == 'pull_request_target' && github.event.pull_request.user.login == 'dependabot[bot]') runs-on: ubuntu-latest outputs: testcases: ${{ steps.discover.outputs.testcases }}