Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/enforce-owner-prs.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
name: Enforce Owner PRs

on:
# zizmor: ignore[dangerous-triggers] This workflow does not checkout or execute PR code; it only closes unauthorized PRs via metadata.
pull_request_target:
types:
- opened
- reopened
- synchronize

permissions:
pull-requests: write
contents: read
permissions: {}

jobs:
owner_gate:
if: ${{ github.event.pull_request.user.login != 'Mehdi-Bl' }}
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Close unauthorized PR
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const number = context.payload.pull_request.number;
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/required-poutine.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Required Workflow - Poutine

on:
pull_request:
merge_group:
workflow_call:

permissions: {}

jobs:
poutine:
name: poutine
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write

steps:
- name: Checkout caller repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Run poutine scan
uses: boostsecurityio/poutine-action@84c0a0d32e8d57ae12651222be1eb15351429228 # v0.15.2
with:
format: sarif
output: results.sarif

- name: Normalize poutine SARIF for GitHub upload
run: |
jq 'del(.runs[]?.tool.driver.supportedTaxonomies)' results.sarif > results.cleaned.sarif
mv results.cleaned.sarif results.sarif
Comment on lines +30 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Harden the SARIF normalization step against missing or invalid SARIF output to produce clearer failure signals.

If results.sarif is missing or malformed (e.g., boostsecurityio/poutine-action fails earlier), this step will fail with a generic jq or mv: cannot stat 'results.cleaned.sarif' error, which hides the real issue. Consider adding set -euo pipefail and an explicit test -f results.sarif with a clear error message before invoking jq so logs clearly show that the SARIF file was never produced.

Suggested change
- name: Normalize poutine SARIF for GitHub upload
run: |
jq 'del(.runs[]?.tool.driver.supportedTaxonomies)' results.sarif > results.cleaned.sarif
mv results.cleaned.sarif results.sarif
- name: Normalize poutine SARIF for GitHub upload
run: |
set -euo pipefail
if [ ! -f results.sarif ]; then
echo "Error: results.sarif was not produced by boostsecurityio/poutine-action. Check the poutine scan step for failures or configuration issues." >&2
exit 1
fi
jq 'del(.runs[]?.tool.driver.supportedTaxonomies)' results.sarif > results.cleaned.sarif
mv results.cleaned.sarif results.sarif


- name: Upload poutine SARIF
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
sarif_file: results.sarif
category: /tool:poutine

- name: Upload poutine artifact
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: poutine-sarif
path: results.sarif
if-no-files-found: error
retention-days: 14
232 changes: 232 additions & 0 deletions .github/workflows/reusable-claude-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
name: Reusable Workflow - Claude Manual PR Review

on:
workflow_call:
inputs:
pr_number:
description: Pull request number to review.
required: true
type: number
force_review:
description: Run review even when the PR is below size thresholds.
required: false
default: false
type: boolean
allowed_actors:
description: Comma-separated dispatcher allowlist.
required: true
type: string
azure_client_id:
description: Azure OIDC application client ID.
required: true
type: string
azure_tenant_id:
description: Azure tenant ID.
required: true
type: string
azure_subscription_id:
description: Azure subscription ID.
required: true
type: string
azure_key_vault_name:
description: Azure Key Vault name.
required: true
type: string
claude_secret_name:
description: Key Vault secret name that stores Claude OAuth token.
required: true
type: string
min_changed_files:
description: Minimum changed files threshold before auto-skip.
required: false
default: 5
type: number
min_total_changes:
description: Minimum total additions+deletions threshold before auto-skip.
required: false
default: 20
type: number

permissions:
contents: read

concurrency:
group: claude-manual-review-${{ inputs.pr_number }}
cancel-in-progress: true

jobs:
claude-review:
name: Claude Manual Review
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write

steps:
- name: Enforce default branch dispatch
env:
REF_NAME: ${{ github.ref_name }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
if [ "${REF_NAME}" != "${DEFAULT_BRANCH}" ]; then
echo "Manual reviews are only allowed from ${DEFAULT_BRANCH}. Current ref: ${REF_NAME}"
exit 1
fi

- name: Authorize dispatcher (allowlist)
env:
ACTOR: ${{ github.actor }}
ALLOWED_ACTORS: ${{ inputs.allowed_actors }}
run: |
set -euo pipefail

if [ -z "${ALLOWED_ACTORS}" ]; then
echo "Missing required allowlist input: allowed_actors"
exit 1
fi

allowed="false"
IFS=',' read -r -a actors <<< "${ALLOWED_ACTORS}"
for raw_actor in "${actors[@]}"; do
candidate="$(echo "${raw_actor}" | xargs)"
if [ -n "${candidate}" ] && [ "${candidate}" = "${ACTOR}" ]; then
allowed="true"
break
fi
done

if [ "${allowed}" != "true" ]; then
echo "Actor '${ACTOR}' is not authorized to run this workflow."
exit 1
fi

- name: Login to Azure with OIDC
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ inputs.azure_client_id }}
tenant-id: ${{ inputs.azure_tenant_id }}
subscription-id: ${{ inputs.azure_subscription_id }}

- name: Fetch Claude OAuth token from Azure Key Vault
id: keyvault
env:
AZURE_KEY_VAULT_NAME: ${{ inputs.azure_key_vault_name }}
CLAUDE_SECRET_NAME: ${{ inputs.claude_secret_name }}
run: |
set -euo pipefail

claude_token="$(az keyvault secret show --vault-name "${AZURE_KEY_VAULT_NAME}" --name "${CLAUDE_SECRET_NAME}" --query value -o tsv)"
if [ -z "${claude_token}" ]; then
echo "Failed to read Claude token from Azure Key Vault secret '${CLAUDE_SECRET_NAME}'."
exit 1
fi

echo "::add-mask::${claude_token}"
echo "claude_code_oauth_token=${claude_token}" >> "${GITHUB_OUTPUT}"

- name: Resolve pull request metadata
id: pr
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ inputs.pr_number }}
run: |
set -euo pipefail

if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then
echo "Invalid pr_number input: '${PR_NUMBER}'. Expected a numeric pull request number."
exit 1
fi

pr_json="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}")"
head_sha="$(echo "${pr_json}" | jq -r '.head.sha')"
title="$(echo "${pr_json}" | jq -r '.title')"
body="$(echo "${pr_json}" | jq -r '.body // ""')"
changed_files="$(echo "${pr_json}" | jq -r '.changed_files')"
additions="$(echo "${pr_json}" | jq -r '.additions')"
deletions="$(echo "${pr_json}" | jq -r '.deletions')"
total_changes="$((additions + deletions))"

echo "head_sha=${head_sha}" >> "${GITHUB_OUTPUT}"
echo "changed_files=${changed_files}" >> "${GITHUB_OUTPUT}"
echo "total_changes=${total_changes}" >> "${GITHUB_OUTPUT}"

title_delim="TITLE_$(cat /proc/sys/kernel/random/uuid)"
{
echo "title<<${title_delim}"
echo "${title}"
echo "${title_delim}"
} >> "${GITHUB_OUTPUT}"

body_delim="BODY_$(cat /proc/sys/kernel/random/uuid)"
{
echo "body<<${body_delim}"
echo "${body}"
echo "${body_delim}"
} >> "${GITHUB_OUTPUT}"

- name: Skip tiny pull requests unless forced
if: ${{ !inputs.force_review && fromJSON(steps.pr.outputs.changed_files) < inputs.min_changed_files && fromJSON(steps.pr.outputs.total_changes) < inputs.min_total_changes }}
run: |
echo "Skipping Claude review because PR is below size thresholds."
echo "Re-run with force_review=true to review this PR."

- name: Checkout pull request head
if: ${{ inputs.force_review || fromJSON(steps.pr.outputs.changed_files) >= inputs.min_changed_files || fromJSON(steps.pr.outputs.total_changes) >= inputs.min_total_changes }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ steps.pr.outputs.head_sha }}
fetch-depth: 1
persist-credentials: false

- name: Run Claude manual pull request review
id: claude_review
if: ${{ inputs.force_review || fromJSON(steps.pr.outputs.changed_files) >= inputs.min_changed_files || fromJSON(steps.pr.outputs.total_changes) >= inputs.min_total_changes }}
continue-on-error: true
uses: anthropics/claude-code-action@c22f7c3f9dbdf2faa98a4c3139f7ec9eb5a691dc
with:
claude_code_oauth_token: ${{ steps.keyvault.outputs.claude_code_oauth_token }}
github_token: ${{ github.token }}
allowed_non_write_users: ${{ github.actor }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ inputs.pr_number }}
PR TITLE: ${{ steps.pr.outputs.title }}
PR BODY:
${{ steps.pr.outputs.body }}

You are performing a strict pull request review. Review only changed files in this PR.
Post exactly one consolidated `gh pr comment`.

Required comment format:
- first line: `Model used: anthropics/claude-code-action`
- section `### Executive Summary`
- section `### Findings`
- section `### Test Coverage Gaps`
- section `### Final Verdict`

Findings requirements:
- prioritize by severity: Critical, High, Medium, Low
- include concrete evidence with `path:line` references from the diff
- classify each finding as one of: Bug, Security, Performance, Testing, Maintainability
- explain why it matters and provide a concrete fix
- avoid speculative findings without evidence

If no material issues exist, state exactly `No material issues found` and list any residual risks.
Review only; do not modify files, push commits, or open additional PRs.
Keep comments factual, specific, and action-oriented.

- name: Warn when Claude review fails (non-blocking)
if: ${{ always() && (inputs.force_review || fromJSON(steps.pr.outputs.changed_files) >= inputs.min_changed_files || fromJSON(steps.pr.outputs.total_changes) >= inputs.min_total_changes) && steps.claude_review.outcome == 'failure' }}
env:
PR_NUMBER: ${{ inputs.pr_number }}
run: |
echo "::warning::Claude manual review failed but is configured as non-blocking. Check the previous step logs."
{
echo "### Claude Manual Review Warning"
echo "- PR: #${PR_NUMBER}"
echo "- Model action: anthropics/claude-code-action"
echo "- Status: failed (non-blocking via continue-on-error)"
} >> "${GITHUB_STEP_SUMMARY}"
Loading