From be3b9de9608d6f3badbd00b024b3acd270c7971c Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:54:32 +0000 Subject: [PATCH 1/2] Use federated model credentials for pi --- .github/workflows/fix-ci.yaml | 21 +++++++++++++++++++-- .github/workflows/semgrep.yml | 2 ++ .github/workflows/vuln-remediation.yml | 2 ++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fix-ci.yaml b/.github/workflows/fix-ci.yaml index 83f0ddef..8b00d26a 100644 --- a/.github/workflows/fix-ci.yaml +++ b/.github/workflows/fix-ci.yaml @@ -22,6 +22,7 @@ jobs: contents: read actions: read pull-requests: read + id-token: write outputs: changed: ${{ steps.snapshot.outputs.changed }} pr_number: ${{ steps.pr.outputs.number }} @@ -100,12 +101,22 @@ jobs: npm install -g --ignore-scripts @earendil-works/pi-coding-agent pi --version + - name: Model credentials + if: steps.pr.outputs.number != '' + uses: kernel/security-workflows/.github/actions/llm-credentials@main + with: + model: ${{ vars.PI_PREFERRED_MODEL }} + anthropic-federation-rule-id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }} + anthropic-organization-id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }} + anthropic-service-account-id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }} + openai-identity-provider-id: ${{ vars.OPENAI_IDENTITY_PROVIDER_ID }} + openai-service-account-id: ${{ vars.OPENAI_SERVICE_ACCOUNT_ID }} + openai-audience: ${{ vars.OPENAI_WIF_AUDIENCE }} + - name: Fix CI failure if: steps.pr.outputs.number != '' working-directory: pr env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PI_PREFERRED_MODEL: ${{ vars.PI_PREFERRED_MODEL }} PI_SKIP_VERSION_CHECK: '1' PI_TELEMETRY: '0' @@ -123,6 +134,12 @@ jobs: --model "$PI_PREFERRED_MODEL" --thinking max \ @"$RUNNER_TEMP/fix-ci-prompt.md" + - name: Stop model credentials + if: always() + uses: kernel/security-workflows/.github/actions/llm-credentials@main + with: + stop: true + - name: Snapshot publishable changes id: snapshot if: steps.pr.outputs.number != '' diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 32fec528..75e5f834 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -7,6 +7,8 @@ on: permissions: contents: read pull-requests: write + # semgrep exchanges the job's OIDC token for short-lived model credentials. + id-token: write jobs: scan: diff --git a/.github/workflows/vuln-remediation.yml b/.github/workflows/vuln-remediation.yml index 22ee0653..35f30c82 100644 --- a/.github/workflows/vuln-remediation.yml +++ b/.github/workflows/vuln-remediation.yml @@ -8,6 +8,8 @@ on: permissions: contents: write pull-requests: write + # vuln-remediation exchanges the job's OIDC token for short-lived model credentials. + id-token: write jobs: remediate: From a1271f0aa6ac3ae8038884e34897db0b366c08fb Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:59:41 +0000 Subject: [PATCH 2/2] Pin llm-credentials action and update fix-ci security tests --- .github/tests/fix-ci-security.test.mjs | 6 ++++-- .github/workflows/fix-ci.yaml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/tests/fix-ci-security.test.mjs b/.github/tests/fix-ci-security.test.mjs index 09564cac..f5fe05d9 100644 --- a/.github/tests/fix-ci-security.test.mjs +++ b/.github/tests/fix-ci-security.test.mjs @@ -43,7 +43,7 @@ test('only failed runs of same-repository PRs are handled', () => { test('actions are pinned and checkouts do not persist credentials', () => { for (const entry of Object.keys(jobs).flatMap(steps).filter((entry) => entry.includes('uses: '))) { - assert.match(entry, /uses: [\w/-]+@[a-f0-9]{40}(?:\s|$)/); + assert.match(entry, /uses: [\w./-]+@[a-f0-9]{40}(?:\s|$)/); if (entry.includes('uses: actions/checkout@')) { assert.match(entry, /persist-credentials: false/); assert.doesNotMatch(entry, /token:/); @@ -60,7 +60,9 @@ test('expressions are not interpolated into shell scripts', () => { test('the generate job has no write credentials', () => { assert.match(workflow, /^permissions:\n contents: read\n\n/m); const generate = jobs.generate.join(''); - assert.doesNotMatch(generate, /: write\b/); + // id-token: write only lets the job mint an OIDC token for short-lived model credentials. + assert.equal(generate.match(/: write\b/g).length, 1); + assert.match(header('generate'), /^ permissions:\n(?: [\w-]+: \w+\n)* id-token: write\n/m); assert.doesNotMatch(generate, /create-github-app-token|ADMIN_APP/); assert.doesNotMatch(step('generate', 'Fix CI failure'), /GH_TOKEN:|GITHUB_TOKEN:|github\.token|outputs\.token/); // Agent and PR code run in this job, so caches it saves could reach other workflows. diff --git a/.github/workflows/fix-ci.yaml b/.github/workflows/fix-ci.yaml index 8b00d26a..8790c74f 100644 --- a/.github/workflows/fix-ci.yaml +++ b/.github/workflows/fix-ci.yaml @@ -103,7 +103,7 @@ jobs: - name: Model credentials if: steps.pr.outputs.number != '' - uses: kernel/security-workflows/.github/actions/llm-credentials@main + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main with: model: ${{ vars.PI_PREFERRED_MODEL }} anthropic-federation-rule-id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }} @@ -136,7 +136,7 @@ jobs: - name: Stop model credentials if: always() - uses: kernel/security-workflows/.github/actions/llm-credentials@main + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main with: stop: true