name: Contributor Reputation Check on: pull_request: types: [opened, synchronize, reopened, edited, ready_for_review] issues: types: [opened, reopened, edited] permissions: contents: read jobs: issue-check: runs-on: ubuntu-latest if: >- github.event_name == 'issues' && github.actor != 'dependabot[bot]' && github.actor != 'github-actions[bot]' && github.actor != 'copilot-swe-agent[bot]' permissions: contents: read issues: write steps: - name: Setup Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Fetch AGT check scripts env: AGT_REF: v4.1.0 run: | mkdir -p "$RUNNER_TEMP/agt" curl -fsSL "https://raw.githubusercontent.com/microsoft/agent-governance-toolkit/${AGT_REF}/scripts/contributor_check.py" \ -o "$RUNNER_TEMP/agt/contributor_check.py" curl -fsSL "https://raw.githubusercontent.com/microsoft/agent-governance-toolkit/${AGT_REF}/scripts/credential_audit.py" \ -o "$RUNNER_TEMP/agt/credential_audit.py" - name: Run profile check env: GITHUB_TOKEN: ${{ github.token }} USERNAME: ${{ github.event.issue.user.login }} run: | mkdir -p "$RUNNER_TEMP/contributor-check" set +e python3 "$RUNNER_TEMP/agt/contributor_check.py" \ --username "$USERNAME" \ --repo "${{ github.repository }}" \ --json > "$RUNNER_TEMP/contributor-check/profile.json" 2>"$RUNNER_TEMP/contributor-check/profile.log" status=$? set -e if [ "$status" -ne 0 ] && [ ! -s "$RUNNER_TEMP/contributor-check/profile.json" ]; then echo "::warning::Profile check failed" if [ -s "$RUNNER_TEMP/contributor-check/profile.log" ]; then sed -n '1,120p' "$RUNNER_TEMP/contributor-check/profile.log" fi fi - name: Run credential audit env: GITHUB_TOKEN: ${{ github.token }} USERNAME: ${{ github.event.issue.user.login }} run: | set +e python3 "$RUNNER_TEMP/agt/credential_audit.py" \ --username "$USERNAME" \ --repo "${{ github.repository }}" \ --json > "$RUNNER_TEMP/contributor-check/cred.json" 2>"$RUNNER_TEMP/contributor-check/cred.log" status=$? set -e if [ "$status" -ne 0 ] && [ ! -s "$RUNNER_TEMP/contributor-check/cred.json" ]; then echo "::warning::Credential audit failed" if [ -s "$RUNNER_TEMP/contributor-check/cred.log" ]; then sed -n '1,120p' "$RUNNER_TEMP/contributor-check/cred.log" fi fi - name: Dump check outputs if: always() run: | dump_json() { label="$1" file="$2" log_file="$3" echo "::group::${label} JSON" if [ -s "$file" ]; then if jq . "$file"; then : else cat "$file" fi else echo "" fi echo "::endgroup::" if [ -s "$log_file" ]; then echo "::group::${label} stderr" sed -n '1,120p' "$log_file" echo "::endgroup::" fi } dump_json "Profile check" "$RUNNER_TEMP/contributor-check/profile.json" "$RUNNER_TEMP/contributor-check/profile.log" dump_json "Credential audit" "$RUNNER_TEMP/contributor-check/cred.json" "$RUNNER_TEMP/contributor-check/cred.log" - name: Resolve check risks id: results run: | extract_risk() { file="$1" fallback="$2" if [ ! -s "$file" ]; then echo "$fallback" return fi risk=$( jq -r ' [ .risk, .overall_risk, .overallRisk, .result.risk, .result.overall_risk, .result.overallRisk ] | map(select(. != null and . != "")) | .[0] // empty ' "$file" 2>/dev/null \ | tr "[:lower:]" "[:upper:]" \ | tr -d "\r" ) case "$risk" in HIGH|MEDIUM|LOW|NONE|UNKNOWN) echo "$risk" ;; "") echo "$fallback" ;; *) echo "$fallback" ;; esac } profile_risk=$(extract_risk "$RUNNER_TEMP/contributor-check/profile.json" UNKNOWN) credential_risk=$(extract_risk "$RUNNER_TEMP/contributor-check/cred.json" UNKNOWN) echo "profile=$profile_risk" >> "$GITHUB_OUTPUT" echo "credential=$credential_risk" >> "$GITHUB_OUTPUT" - name: Compute overall risk id: overall run: | risk_to_num() { case "$1" in HIGH) echo 3 ;; MEDIUM) echo 2 ;; LOW|NONE) echo 1 ;; UNKNOWN|"") echo 0 ;; *) echo 0 ;; esac } p=$(risk_to_num "${{ steps.results.outputs.profile }}") c=$(risk_to_num "${{ steps.results.outputs.credential }}") max=$p; [ "$c" -gt "$max" ] && max=$c case "$max" in 3) r="HIGH" ;; 2) r="MEDIUM" ;; 1) r="LOW" ;; *) r="UNKNOWN" ;; esac echo "risk=$r" >> "$GITHUB_OUTPUT" - name: Sync risk comment env: GH_TOKEN: ${{ github.token }} NUMBER: ${{ github.event.issue.number }} RISK: ${{ steps.overall.outputs.risk }} PROFILE_RISK: ${{ steps.results.outputs.profile }} CREDENTIAL_RISK: ${{ steps.results.outputs.credential }} run: | marker="" comment_ids=$( gh api "repos/${{ github.repository }}/issues/$NUMBER/comments" --paginate \ | jq -r --arg marker "$marker" '.[] | select((.user.login // "") == "github-actions[bot]" and ((.body // "") | contains($marker))) | .id' ) comment_id=$(printf "%s\n" "$comment_ids" | sed -n '1p') if [ "$RISK" != "MEDIUM" ] && [ "$RISK" != "HIGH" ]; then if [ -n "$comment_id" ]; then while IFS= read -r id; do [ -z "$id" ] && continue gh api --method DELETE "repos/${{ github.repository }}/issues/comments/$id" \ || echo "Comment $id could not be deleted; continuing because the comment may have already been removed or changed." done <<< "$comment_ids" fi exit 0 fi if [ "$RISK" = "HIGH" ]; then icon="🔴"; else icon="🟡"; fi body=$(cat </dev/null 2>&1 || true done else gh api --method POST "repos/${{ github.repository }}/issues/$NUMBER/comments" -f body="$body" fi - name: Sync risk label env: GH_TOKEN: ${{ github.token }} NUMBER: ${{ github.event.issue.number }} RISK: ${{ steps.overall.outputs.risk }} run: | for label in needs-review:MEDIUM needs-review:HIGH; do if [ "$label" != "needs-review:$RISK" ]; then gh api --method DELETE "repos/${{ github.repository }}/issues/$NUMBER/labels/$label" >/dev/null 2>&1 || true fi done if [ "$RISK" != "MEDIUM" ] && [ "$RISK" != "HIGH" ]; then exit 0 fi gh label create "needs-review:$RISK" \ --description "Contributor reputation check flagged $RISK risk" \ --color "FFA500" --force 2>/dev/null || true gh api --method POST "repos/${{ github.repository }}/issues/$NUMBER/labels" \ -f labels[]="needs-review:$RISK" >/dev/null - name: Job summary if: always() run: | risk="${{ steps.overall.outputs.risk }}" case "$risk" in HIGH) icon="🔴" ;; MEDIUM) icon="🟡" ;; LOW) icon="✅" ;; *) icon="❓" ;; esac { echo "## $icon Contributor Check: \`${{ github.event.issue.user.login }}\`" echo "| Check | Risk |" echo "|-------|------|" echo "| Profile | ${{ steps.results.outputs.profile }} |" echo "| Credential | ${{ steps.results.outputs.credential }} |" echo "| **Overall** | **$risk** |" } >> "$GITHUB_STEP_SUMMARY" pr-check: runs-on: ubuntu-latest if: >- github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' && github.actor != 'github-actions[bot]' && github.actor != 'copilot-swe-agent[bot]' permissions: contents: read pull-requests: read steps: - name: Setup Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Fetch AGT check scripts env: AGT_REF: v4.1.0 run: | mkdir -p "$RUNNER_TEMP/agt" curl -fsSL "https://raw.githubusercontent.com/microsoft/agent-governance-toolkit/${AGT_REF}/scripts/contributor_check.py" \ -o "$RUNNER_TEMP/agt/contributor_check.py" curl -fsSL "https://raw.githubusercontent.com/microsoft/agent-governance-toolkit/${AGT_REF}/scripts/credential_audit.py" \ -o "$RUNNER_TEMP/agt/credential_audit.py" - name: Run profile check env: GITHUB_TOKEN: ${{ github.token }} USERNAME: ${{ github.event.pull_request.user.login }} run: | mkdir -p "$RUNNER_TEMP/contributor-check" set +e python3 "$RUNNER_TEMP/agt/contributor_check.py" \ --username "$USERNAME" \ --repo "${{ github.repository }}" \ --json > "$RUNNER_TEMP/contributor-check/profile.json" 2>"$RUNNER_TEMP/contributor-check/profile.log" status=$? set -e if [ "$status" -ne 0 ] && [ ! -s "$RUNNER_TEMP/contributor-check/profile.json" ]; then echo "::warning::Profile check failed" if [ -s "$RUNNER_TEMP/contributor-check/profile.log" ]; then sed -n '1,120p' "$RUNNER_TEMP/contributor-check/profile.log" fi fi - name: Run credential audit env: GITHUB_TOKEN: ${{ github.token }} USERNAME: ${{ github.event.pull_request.user.login }} run: | set +e python3 "$RUNNER_TEMP/agt/credential_audit.py" \ --username "$USERNAME" \ --repo "${{ github.repository }}" \ --json > "$RUNNER_TEMP/contributor-check/cred.json" 2>"$RUNNER_TEMP/contributor-check/cred.log" status=$? set -e if [ "$status" -ne 0 ] && [ ! -s "$RUNNER_TEMP/contributor-check/cred.json" ]; then echo "::warning::Credential audit failed" if [ -s "$RUNNER_TEMP/contributor-check/cred.log" ]; then sed -n '1,120p' "$RUNNER_TEMP/contributor-check/cred.log" fi fi - name: Resolve check risks id: results run: | extract_risk() { file="$1" fallback="$2" if [ ! -s "$file" ]; then echo "$fallback" return fi risk=$( jq -r ' [ .risk, .overall_risk, .overallRisk, .result.risk, .result.overall_risk, .result.overallRisk ] | map(select(. != null and . != "")) | .[0] // empty ' "$file" 2>/dev/null \ | tr "[:lower:]" "[:upper:]" \ | tr -d "\r" ) case "$risk" in HIGH|MEDIUM|LOW|NONE|UNKNOWN) echo "$risk" ;; "") echo "$fallback" ;; *) echo "$fallback" ;; esac } profile_risk=$(extract_risk "$RUNNER_TEMP/contributor-check/profile.json" UNKNOWN) credential_risk=$(extract_risk "$RUNNER_TEMP/contributor-check/cred.json" UNKNOWN) echo "profile=$profile_risk" >> "$GITHUB_OUTPUT" echo "credential=$credential_risk" >> "$GITHUB_OUTPUT" - name: Compute overall risk id: overall run: | risk_to_num() { case "$1" in HIGH) echo 3 ;; MEDIUM) echo 2 ;; LOW|NONE) echo 1 ;; UNKNOWN|"") echo 0 ;; *) echo 0 ;; esac } p=$(risk_to_num "${{ steps.results.outputs.profile }}") c=$(risk_to_num "${{ steps.results.outputs.credential }}") max=$p; [ "$c" -gt "$max" ] && max=$c case "$max" in 3) r="HIGH" ;; 2) r="MEDIUM" ;; 1) r="LOW" ;; *) r="UNKNOWN" ;; esac echo "risk=$r" >> "$GITHUB_OUTPUT" - name: Write PR result artifact env: PR_NUMBER: ${{ github.event.pull_request.number }} PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} USERNAME: ${{ github.event.pull_request.user.login }} PROFILE_RISK: ${{ steps.results.outputs.profile }} CREDENTIAL_RISK: ${{ steps.results.outputs.credential }} OVERALL_RISK: ${{ steps.overall.outputs.risk }} run: | mkdir -p "$RUNNER_TEMP/contributor-check-result" jq -n \ --arg schema_version "contributor-check-result/v1" \ --arg event "pull_request" \ --argjson pr_number "$PR_NUMBER" \ --arg head_sha "$PR_HEAD_SHA" \ --arg username "$USERNAME" \ --arg profile_risk "$PROFILE_RISK" \ --arg credential_risk "$CREDENTIAL_RISK" \ --arg overall_risk "$OVERALL_RISK" \ --arg run_id "$GITHUB_RUN_ID" \ '{schema_version:$schema_version,event:$event,pr_number:$pr_number,head_sha:$head_sha,username:$username,profile_risk:$profile_risk,credential_risk:$credential_risk,overall_risk:$overall_risk,run_id:$run_id}' \ > "$RUNNER_TEMP/contributor-check-result/result.json" - name: Upload PR result artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: contributor-check-result path: ${{ runner.temp }}/contributor-check-result/result.json if-no-files-found: error retention-days: 3 - name: Job summary if: always() run: | risk="${{ steps.overall.outputs.risk }}" case "$risk" in HIGH) icon="🔴" ;; MEDIUM) icon="🟡" ;; LOW) icon="✅" ;; *) icon="❓" ;; esac { echo "## $icon Contributor Check: \`${{ github.event.pull_request.user.login }}\`" echo "| Check | Risk |" echo "|-------|------|" echo "| Profile | ${{ steps.results.outputs.profile }} |" echo "| Credential | ${{ steps.results.outputs.credential }} |" echo "| **Overall** | **$risk** |" echo "" echo "PR label/comment synchronization is handled by the workflow_run writer after PR state is re-fetched." } >> "$GITHUB_STEP_SUMMARY"