diff --git a/README.md b/README.md index 469f330a..c8d98ff3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,34 @@ This repository contains Github Actions (GHA) maintained by the Infra team. Thos They are **publicly accessible** and thus must not contain any secrets. +## Available Actions + +### Security & Compliance +- [assert-camunda-git-emails](./assert-camunda-git-emails/README.md) - Ensures only Camunda email addresses are used in git commits +- [assert-no-ai-commits](./assert-no-ai-commits/README.md) - Prevents AI-authored commits from being merged (enforces AI Policy) + +### CI/CD & Build +- [actionlint](./actionlint/README.md) - Lints GitHub Actions workflows +- [build-docker-image](./build-docker-image/README.md) - Builds and pushes Docker images +- [common-tooling](./common-tooling/README.md) - Sets up common development tools +- [setup-yarn-cache](./setup-yarn-cache/README.md) - Sets up Yarn caching + +### Pull Request Management +- [configure-pull-request](./configure-pull-request/README.md) - Configures PR labels, reviewers, and projects +- [preview-env](./preview-env/README.md) - Manages preview environments for PRs + +### Monitoring & Analytics +- [submit-build-status](./submit-build-status/README.md) - Submits build status to CI Analytics +- [submit-test-status](./submit-test-status/README.md) - Submits test status to CI Analytics +- [submit-aborted-gha-status](./submit-aborted-gha-status/README.md) - Submits aborted workflow status + +### Utilities +- [generate-github-app-token-from-vault-secrets](./generate-github-app-token-from-vault-secrets/README.md) - Generates GitHub App tokens from Vault +- [sanitize-branch-name](./sanitize-branch-name/README.md) - Sanitizes branch names for use in environments +- [yq-yaml-processor](./yq-yaml-processor/README.md) - Processes YAML files with yq + +For team-specific actions, see the [teams](./teams/) directory. + ## Contributing Before contributing, please make sure to activate `pre-commit` in this repository: diff --git a/assert-no-ai-commits/README.md b/assert-no-ai-commits/README.md new file mode 100644 index 00000000..4629e8fa --- /dev/null +++ b/assert-no-ai-commits/README.md @@ -0,0 +1,90 @@ +# assert-no-ai-commits + +This composite GitHub Action (GHA) is designed to prevent AI-authored commits from being merged into the main branch, in accordance with [Camunda's AI Policy](https://confluence.camunda.com/spaces/HAN/pages/245401394/Usage+of+Copilot+AI+tools+within+Engineering). The policy states that commits created by AI should be attributed to people first. + +## Purpose + +This action helps enforce the policy by: +- Detecting commits that are directly authored by AI tools (GitHub Copilot, ChatGPT, etc.) +- Preventing PRs containing AI-authored commits from being merged +- Ensuring proper human attribution of AI-assisted code + +## Usage + +This composite GHA should be run on Pull Requests to prevent AI-authored commits from reaching the default branch of your repository. + +Place the following GitHub Action workflow in your repository as `.github/workflows/assert-no-ai-commits.yml`: + +```yaml +--- +name: assert-no-ai-commits + +on: [pull_request] + +jobs: + check-ai-commits: + runs-on: ubuntu-latest + steps: + - uses: camunda/infra-global-github-actions/assert-no-ai-commits@main +``` + +## What it Detects + +The action checks for various AI-related patterns in commits: + +### Co-authored-by Trailers +- `Co-authored-by: GitHub Copilot <...>` +- `Co-authored-by: Copilot <...>` +- `Co-authored-by: ChatGPT <...>` +- `Co-authored-by: OpenAI <...>` +- `Co-authored-by: Claude <...>` +- And other AI assistant patterns + +### Author/Committer Names +- Names containing: "Copilot", "GPT", "ChatGPT", "OpenAI", "Claude", "AI Assistant", etc. + +### Author/Committer Emails +- Emails containing: "copilot@", "gpt@", "ai@", "chatgpt@", "openai@", "claude@", etc. + +## Policy Compliance + +This action helps ensure compliance with Camunda's AI Policy by: + +1. **Blocking direct AI authorship**: Prevents commits directly authored by AI tools +2. **Encouraging proper attribution**: Ensures humans are credited as commit authors +3. **Maintaining transparency**: Makes AI usage visible through proper attribution + +## Best Practices + +When using AI tools for code assistance: + +1. **Human authorship**: Always ensure the human developer is the commit author +2. **Proper review**: Review AI-generated code before committing +3. **Attribution in commit messages**: If desired, mention AI assistance in commit messages (but not as co-author) +4. **Example of proper usage**: + ``` + Author: John Doe + + feat: implement new feature + + Added new authentication logic with assistance from GitHub Copilot + ``` + +## Error Messages + +When AI-authored commits are detected, the action will: +- List the specific patterns that were found +- Show the commit hash and details +- Provide guidance on how to fix the issue +- Reference the AI Policy for context + +## False Positives + +If you believe the action is incorrectly flagging legitimate commits, please: +1. Check if the commit author/committer contains AI-related terms +2. Verify there are no AI co-author trailers + +## Related Actions + +- [assert-camunda-git-emails](../assert-camunda-git-emails/README.md) - Ensures only Camunda email addresses are used +- Both actions work together to ensure proper commit attribution and compliance diff --git a/assert-no-ai-commits/action.yml b/assert-no-ai-commits/action.yml new file mode 100644 index 00000000..9b6cede6 --- /dev/null +++ b/assert-no-ai-commits/action.yml @@ -0,0 +1,20 @@ +--- +name: Assert No AI Commits + +description: Asserts that no AI-authored commits are present in the PR to prevent AI-generated code from being merged without proper human attribution. + +runs: + using: composite + steps: + - uses: actions/checkout@v4 + if: ${{ github.event.pull_request }} + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Check for AI-authored commits + if: ${{ github.event.pull_request }} + shell: bash + env: + GIT_RANGE: "origin/${{ github.base_ref }}..${{ github.event.pull_request.head.sha }}" + run: ${{ github.action_path }}/check.sh diff --git a/assert-no-ai-commits/check.sh b/assert-no-ai-commits/check.sh new file mode 100755 index 00000000..16fe3732 --- /dev/null +++ b/assert-no-ai-commits/check.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +set -eu + +# Set default range if not provided (for testing) +if [[ -z "${GIT_RANGE:-}" ]]; then + echo "No GIT_RANGE provided" + exit 1 +fi + +echo "Checking for AI-authored commits in range: ${GIT_RANGE}" +echo "As per Camunda AI Policy, commits created by AI should be attributed to people first." +echo "" + +# Get all commits in the range +commits=$(git rev-list "${GIT_RANGE}" 2>/dev/null || true) + +if [[ -z "${commits}" ]]; then + echo "No commits found in range ${GIT_RANGE}" + exit 0 +fi + +echo "Commits to check:" +git log --oneline "${GIT_RANGE}" || true +echo "" + +ai_violations=0 + +# Check each commit for AI patterns +for commit in ${commits}; do + echo "Checking commit: ${commit}" + + # Get commit metadata only (not diff content) + commit_metadata=$(git show --format=fuller --no-patch "${commit}" 2>/dev/null) + + # Skip if commit metadata couldn't be retrieved + if [[ -z "${commit_metadata}" ]]; then + echo "⚠️ Could not retrieve commit metadata for ${commit}, skipping" + continue + fi + + # Check for GitHub Copilot patterns in the commit metadata + # Using multiple simple checks instead of one complex regex for better readability + ai_detected=false + + # Check 1: Co-authored-by fields containing "copilot" + if echo "${commit_metadata}" | grep -i -q "Co-authored-by:.*copilot"; then + ai_detected=true + fi + + # Check 2: Author fields containing "copilot" + if echo "${commit_metadata}" | grep -i -q "Author:.*copilot"; then + ai_detected=true + fi + + # Check 3: Committer fields containing "copilot" + if echo "${commit_metadata}" | grep -i -q "Committer:.*copilot"; then + ai_detected=true + fi + + # Check 4: Any field with "copilot" and "[bot]" (e.g., copilot-swe-agent[bot]) + if echo "${commit_metadata}" | grep -i -q "copilot.*\[bot\]"; then + ai_detected=true + fi + + # Check 5: GitHub Copilot specific email pattern (e.g., 198982749+Copilot@users.noreply.github.com) + if echo "${commit_metadata}" | grep -i -E -q "[0-9]+\+copilot@users\.noreply\.github\.com"; then + ai_detected=true + fi + + # To add more AI tools in the future, add similar checks here: + # if echo "${commit_metadata}" | grep -i -q "pattern-for-other-ai-tool"; then + # ai_detected=true + # fi + + if [[ "${ai_detected}" == "true" ]]; then + echo "❌ AI-authored commit detected!" + echo "Commit: ${commit}" + + # Get commit subject safely + commit_subject=$(git show --format="%s" -s "${commit}" 2>/dev/null || true) + echo "Subject: ${commit_subject:-N/A}" + echo "" + ai_violations=$((ai_violations + 1)) + else + echo "✓ Commit ${commit} appears to be human-authored" + fi +done + +echo "" + +if [[ "${ai_violations}" -eq 0 ]]; then + echo "✅ Success! No AI-authored commits found." + echo "All commits appear to be properly attributed to human authors." + exit 0 +else + echo "❌ Failure! Found ${ai_violations} AI-authored commit(s)." + echo "" + echo "According to Camunda's AI Policy, commits created by AI should be attributed to people first." + echo "Please ensure that:" + echo "1. AI-generated code is properly reviewed and attributed to human authors" + echo "2. All commits are authored by humans, not AI tools, even if they are created by AI" + exit 1 +fi diff --git a/assert-no-ai-commits/test.sh b/assert-no-ai-commits/test.sh new file mode 100755 index 00000000..f510b9a9 --- /dev/null +++ b/assert-no-ai-commits/test.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +# Comprehensive test script for assert-no-ai-commits action +# This script tests various AI patterns to ensure they are correctly detected + +set -e + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CHECK_SCRIPT="${SCRIPT_DIR}/check.sh" + +echo "🧪 Running comprehensive tests for assert-no-ai-commits action..." +echo "Using check script: ${CHECK_SCRIPT}" +echo "" + +# Test cases to verify GitHub Copilot patterns +test_cases=( + "Co-authored-by: GitHub Copilot " + "Co-authored-by: Copilot " +) + +# Test GitHub Copilot author/committer patterns +copilot_author_tests=( + "GitHub Copilot:copilot@github.com" + "copilot-swe-agent[bot]:198982749+Copilot@users.noreply.github.com" +) + +# Function to run a single test +run_test() { + local test_name="$1" + local commit_message="$2" + local author_name="$3" + local author_email="$4" + + echo "📋 Testing: ${test_name}" + + # Create temporary test repository + TEST_DIR=$(mktemp -d) + cd "${TEST_DIR}" + + git init -q + git config user.email "${author_email:-test@camunda.com}" + git config user.name "${author_name:-Test User}" + + # Create initial commit + echo "test" > test.txt + git add test.txt + git commit -q -m "Initial commit" + + # Create commit with AI pattern + echo "content" >> test.txt + git add test.txt + git commit -q -m "${commit_message}" + + # Test the action + export GIT_RANGE="HEAD~1..HEAD" + + if ${CHECK_SCRIPT} > /tmp/test_output.log 2>&1; then + echo "❌ FAILED: Should have detected AI pattern but didn't" + echo "Output was:" + cat /tmp/test_output.log + rm -rf "${TEST_DIR}" + return 1 + else + echo "✅ PASSED: Correctly detected AI pattern" + rm -rf "${TEST_DIR}" + return 0 + fi +} + +# Function to run positive test (should pass) +run_positive_test() { + local test_name="$1" + + echo "📋 Testing: ${test_name}" + + # Create temporary test repository + TEST_DIR=$(mktemp -d) + cd "${TEST_DIR}" + + git init -q + git config user.email "test@camunda.com" + git config user.name "Test User" + + # Create normal commit + echo "test" > test.txt + git add test.txt + git commit -q -m "feat: add new feature + +This is a normal human commit with proper attribution." + + # Test the action + export GIT_RANGE="HEAD~1..HEAD" + + if ${CHECK_SCRIPT} > /dev/null 2>&1; then + echo "✅ PASSED: Correctly allowed human commit" + rm -rf "${TEST_DIR}" + return 0 + else + echo "❌ FAILED: Should have allowed human commit but didn't" + rm -rf "${TEST_DIR}" + return 1 + fi +} + +# Test co-authored-by patterns +echo "🔍 Testing AI Co-authored-by patterns..." +for test_case in "${test_cases[@]}"; do + commit_msg="feat: add new feature + +${test_case}" + run_test "Co-authored-by pattern" "${commit_msg}" +done + +# Test author/committer patterns +echo "🔍 Testing AI author/committer patterns..." +for test_case in "${copilot_author_tests[@]}"; do + IFS=':' read -r name email <<< "${test_case}" + run_test "AI Author: ${name} <${email}>" "feat: add new feature" "${name}" "${email}" +done + +# Test normal human commits (should pass) +echo "🔍 Testing normal human commits..." +run_positive_test "Normal human commit" + +echo "🎉 All tests completed!" +echo "If all tests show ✅ PASSED, the AI commit detection is working correctly."