Security Alerts Review #121
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Security Alerts Review | |
# - ensures that all security alerts from olympix static analysis are properly handled before merging. | |
# - enforces a strict review policy where every alert must be either resolved or dismissed with a justification. | |
# - prevents merging a PR if any alerts are unresolved or dismissed without a comment. | |
# - helps maintain a transparent and collaborative security review process by generating a pr comment summarizing the status of all security alerts. | |
on: | |
pull_request: | |
types: | |
- synchronize | |
- reopened | |
- ready_for_review | |
pull_request_review: | |
types: | |
- submitted | |
workflow_run: | |
workflows: ["Olympix Static Analysis"] | |
types: | |
- completed | |
status: | |
- success | |
workflow_dispatch: | |
jobs: | |
check-security-alerts: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Check if Olympix Static Analysis has run at least once and was successful | |
id: check-analysis | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
BRANCH_NAME: ${{ github.ref_name }} | |
run: | | |
echo "Checking latest Olympix Static Analysis run for branch: $BRANCH_NAME" | |
# Fetch the latest run of the Olympix Static Analysis workflow for this branch | |
LATEST_RUN=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ | |
"https://api.github.com/repos/${{ github.repository }}/actions/workflows/olympixStaticAnalysis.yml/runs?status=completed&branch=${BRANCH_NAME}&per_page=1") | |
WORKFLOW_STATUS=$(echo "$LATEST_RUN" | jq -r '.workflow_runs[0].conclusion') | |
if [[ "$WORKFLOW_STATUS" != "success" ]]; then | |
echo "Olympix Static Analysis did not complete successfully for this branch. Exiting." | |
exit 1 | |
fi | |
- name: Fetch PR Number | |
id: fetch_pr | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
PR_NUMBER=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ | |
"https://api.github.com/repos/${{ github.repository }}/pulls?state=open" | jq -r '.[0].number') | |
if [[ -z "$PR_NUMBER" || "$PR_NUMBER" == "null" ]]; then | |
echo "No open PR found, skipping check." | |
exit 0 | |
fi | |
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV | |
echo "PR number: $PR_NUMBER" | |
- name: Fetch Security Alerts for PR | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
echo "Fetching security alerts for PR #${PR_NUMBER}..." | |
# Fetch security alerts via GitHub API | |
ALERTS=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ | |
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?pr=${PR_NUMBER}") | |
# Log raw API response for debugging | |
echo "Raw API Response:" | |
echo "$ALERTS" | |
# Extract unresolved alerts (open alerts) | |
UNRESOLVED_ALERTS=$(echo "$ALERTS" | jq -c '[.[] | select(.state == "open") ]' || echo "[]") | |
# Extract dismissed alerts without comments (empty dismissed_comment) | |
DISMISSED_ALERTS=$(echo "$ALERTS" | jq -c '[.[] | select(.state == "dismissed" and (.dismissed_comment == null or .dismissed_comment == ""))]' || echo "[]") | |
# Extract dismissed alerts with comments (successful dismissals) | |
COMMENTED_ALERTS=$(echo "$ALERTS" | jq -c '[.[] | select(.state == "dismissed" and (.dismissed_comment != null and .dismissed_comment != ""))]' || echo "[]") | |
UNRESOLVED_COUNT=$(echo "$UNRESOLVED_ALERTS" | jq -r 'length') | |
DISMISSED_COUNT=$(echo "$DISMISSED_ALERTS" | jq -r 'length') | |
COMMENTED_COUNT=$(echo "$COMMENTED_ALERTS" | jq -r 'length') | |
# Output for debugging | |
echo "UNRESOLVED_ALERTS: $UNRESOLVED_ALERTS" | |
echo "DISMISSED_ALERTS (without comments): $DISMISSED_ALERTS" | |
echo "COMMENTED_ALERTS (with comments): $COMMENTED_ALERTS" | |
echo "UNRESOLVED_COUNT: $UNRESOLVED_COUNT" | |
echo "DISMISSED_COUNT: $DISMISSED_COUNT" | |
echo "COMMENTED_COUNT: $COMMENTED_COUNT" | |
# Save values in the environment as single-line JSON | |
echo "UNRESOLVED_ALERTS=$UNRESOLVED_ALERTS" >> $GITHUB_ENV | |
echo "DISMISSED_ALERTS=$DISMISSED_ALERTS" >> $GITHUB_ENV | |
echo "COMMENTED_ALERTS=$COMMENTED_ALERTS" >> $GITHUB_ENV | |
echo "UNRESOLVED_COUNT=$UNRESOLVED_COUNT" >> $GITHUB_ENV | |
echo "DISMISSED_COUNT=$DISMISSED_COUNT" >> $GITHUB_ENV | |
echo "COMMENTED_COUNT=$COMMENTED_COUNT" >> $GITHUB_ENV | |
- name: Find Existing PR Comment | |
id: find_comment | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
echo "Searching for existing PR comment..." | |
COMMENT_ID=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ | |
"https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" | jq -r \ | |
'.[] | select(.body | startswith("### π€ GitHub Action: Security Alerts Review")) | .id') | |
if [[ -n "$COMMENT_ID" && "$COMMENT_ID" != "null" ]]; then | |
echo "EXISTING_COMMENT_ID=$COMMENT_ID" >> $GITHUB_ENV | |
fi | |
echo "Found comment ID: $COMMENT_ID" | |
- name: Post or Update PR Comment | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
COMMENT_BODY="### π€ GitHub Action: Security Alerts Review π\n\n" | |
# Add Unresolved Alerts | |
if [[ "$UNRESOLVED_COUNT" -gt 0 ]]; then | |
COMMENT_BODY+="π¨ **Unresolved Security Alerts Found!** π¨\n" | |
COMMENT_BODY+="The following security alerts must be **resolved** before merging:\n\n" | |
while IFS= read -r row; do | |
ALERT_URL=$(echo "$row" | jq -r '.html_url') | |
ALERT_FILE=$(echo "$row" | jq -r '.most_recent_instance.location.path') | |
ALERT_DESCRIPTION=$(echo "$row" | jq -r '.most_recent_instance.message.text') | |
COMMENT_BODY+="π΄ [View Alert]($ALERT_URL) - **File:** \`$ALERT_FILE\`\n" | |
COMMENT_BODY+=" πΉ $ALERT_DESCRIPTION\n\n" | |
done < <(echo "$UNRESOLVED_ALERTS" | jq -c '.[]') | |
fi | |
# Add Dismissed Alerts Without Comments | |
if [[ "$DISMISSED_COUNT" -gt 0 ]]; then | |
COMMENT_BODY+="The following alerts were dismissed but require a dismissal comment:\n\n" | |
while IFS= read -r row; do | |
ALERT_URL=$(echo "$row" | jq -r '.html_url') | |
ALERT_FILE=$(echo "$row" | jq -r '.most_recent_instance.location.path') | |
ALERT_DESCRIPTION=$(echo "$row" | jq -r '.most_recent_instance.message.text') | |
COMMENT_BODY+="π‘ [View Alert]($ALERT_URL) - **File:** \`$ALERT_FILE\`\n" | |
COMMENT_BODY+=" πΉ $ALERT_DESCRIPTION\n\n" | |
done < <(echo "$DISMISSED_ALERTS" | jq -c '.[]') | |
fi | |
if [[ "$UNRESOLVED_COUNT" -gt 0 || "$DISMISSED_COUNT" -gt 0 ]]; then | |
COMMENT_BODY+="β οΈ **Please resolve the above issues before merging.**\n\n" | |
fi | |
# Add Dismissed Alerts With Comments (Successful dismissals) | |
if [[ "$COMMENTED_COUNT" -gt 0 ]]; then | |
COMMENT_BODY+="π’ **Dismissed Security Alerts with Comments**\n" | |
COMMENT_BODY+="The following alerts were dismissed with proper comments:\n\n" | |
while IFS= read -r row; do | |
ALERT_URL=$(echo "$row" | jq -r '.html_url') | |
ALERT_FILE=$(echo "$row" | jq -r '.most_recent_instance.location.path') | |
ALERT_DESCRIPTION=$(echo "$row" | jq -r '.most_recent_instance.message.text') | |
DISMISS_REASON=$(echo "$row" | jq -r '.dismissed_reason') | |
DISMISS_COMMENT=$(echo "$row" | jq -r '.dismissed_comment') | |
CAPITALIZED_REASON=$(echo "$DISMISS_REASON" | sed 's/^\(.\)/\U\1/') | |
COMMENT_BODY+="π’ [View Alert]($ALERT_URL) - **File:** \`$ALERT_FILE\`\n" | |
COMMENT_BODY+=" πΉ $ALERT_DESCRIPTION\n" | |
COMMENT_BODY+=" πΉ Dismiss Reason: **$CAPITALIZED_REASON**\n" | |
COMMENT_BODY+=" πΉ Dismiss Comment: $DISMISS_COMMENT\n\n" | |
done < <(echo "$COMMENTED_ALERTS" | jq -c '.[]') | |
fi | |
# If no unresolved alerts and no dismissed alerts missing comments, add overall success message | |
if [[ "$UNRESOLVED_COUNT" -eq 0 && "$DISMISSED_COUNT" -eq 0 ]]; then | |
COMMENT_BODY+="β **No unresolved security alerts!** π\n\n" | |
fi | |
# Update existing comment if found; otherwise, post a new one. | |
if [[ -n "$EXISTING_COMMENT_ID" ]]; then | |
echo "Updating existing comment ID: $EXISTING_COMMENT_ID" | |
curl -s -X PATCH -H "Authorization: token ${GITHUB_TOKEN}" -H "Content-Type: application/json" \ | |
-d "{\"body\": \"$COMMENT_BODY\"}" \ | |
"https://api.github.com/repos/${{ github.repository }}/issues/comments/${EXISTING_COMMENT_ID}" | |
else | |
echo "Posting new comment to PR..." | |
curl -s -X POST -H "Authorization: token ${GITHUB_TOKEN}" -H "Content-Type: application/json" \ | |
-d "{\"body\": \"$COMMENT_BODY\"}" \ | |
"https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" | |
fi | |
- name: Check if Action Should Fail | |
run: | | |
echo "π Checking if the workflow should fail based on security alerts..." | |
echo "UNRESOLVED_COUNT: $UNRESOLVED_COUNT" | |
echo "DISMISSED_COUNT: $DISMISSED_COUNT" | |
# Fail the workflow if there are unresolved alerts. | |
if [[ "$UNRESOLVED_COUNT" -gt 0 ]]; then | |
echo "β ERROR: $UNRESOLVED_COUNT unresolved security alerts found!" | |
echo "β οΈ These alerts must be resolved before merging." | |
exit 1 | |
fi | |
# Fail the workflow if there are dismissed alerts without comments. | |
if [[ "$DISMISSED_COUNT" -gt 0 ]]; then | |
echo "β ERROR: $DISMISSED_COUNT security alerts were dismissed without comments!" | |
echo "β οΈ Please provide a dismissal reason for these alerts." | |
exit 1 | |
fi | |
echo "β No blocking security issues found. The workflow will pass successfully." |