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. | |
# Limitations: | |
# - currently, this action must be triggered manually after dismissing or resolving alerts. | |
# - in the future, we plan to automate this process so that the action is re-triggered automatically when a dismissal comment is added. | |
# This will eliminate the need for manual intervention and ensure a more seamless workflow. | |
on: | |
pull_request: | |
types: | |
- ready_for_review | |
workflow_dispatch: | |
jobs: | |
check-security-alerts: | |
runs-on: ubuntu-latest | |
steps: | |
# Ensure that the Olympix Static Analysis workflow has run successfully at least once before proceeding. | |
# This check is necessary because the Security Alerts Review workflow should not proceed unless | |
# a valid Olympix Static Analysis report is available for the current branch. | |
- 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.head_ref }} | |
run: | | |
# Fallback in case BRANCH_NAME is empty. | |
if [ -z "$BRANCH_NAME" ]; then | |
BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF##*/}}" | |
echo "BRANCH_NAME was empty, falling back to: $BRANCH_NAME" | |
fi | |
echo "Checking latest Olympix Static Analysis run for branch: $BRANCH_NAME" | |
# Fetch the latest completed runs of the Olympix Static Analysis workflow | |
LATEST_RUN=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ | |
"https://api.github.com/repos/${{ github.repository }}/actions/workflows/olympixStaticAnalysis.yml/runs?status=completed&per_page=10") | |
# Filter to find the first run with head_branch matching our branch name | |
WORKFLOW_STATUS=$(echo "$LATEST_RUN" | jq -r --arg branch "$BRANCH_NAME" '.workflow_runs[] | select(.head_branch == $branch) | .conclusion' | head -n1) | |
if [[ "$WORKFLOW_STATUS" != "success" ]]; then | |
echo "The Olympix Static Analysis workflow has not been successfully completed for branch: $BRANCH_NAME." | |
echo "The Security Alerts Review workflow cannot continue because a valid Olympix Static Analysis report is required." | |
exit 1 | |
fi | |
- uses: actions/checkout@v4 | |
- uses: jwalton/gh-find-current-pr@master | |
id: findPr | |
- name: Validate and set PR Number | |
id: fetch_pr | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
if [ -z "${{ steps.findPr.outputs.number }}" ]; then | |
echo "Error: No pull request found for this push." >&2 | |
exit 1 | |
fi | |
echo "Found PR number: ${{ steps.findPr.outputs.number }}" | |
PR_NUMBER=${{ steps.findPr.outputs.number }} | |
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV | |
echo "Pull Request Number is: $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) | |
RESOLVED_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 "$RESOLVED_ALERTS" | jq -r 'length') | |
# Output for debugging | |
echo "UNRESOLVED_ALERTS: $UNRESOLVED_ALERTS" | |
echo "DISMISSED_ALERTS (without comments): $DISMISSED_ALERTS" | |
echo "RESOLVED_ALERTS (with comments): $RESOLVED_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 "RESOLVED_ALERTS=$RESOLVED_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 "$RESOLVED_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 And Revert To Draft | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
PR_NUMBER: ${{ env.PR_NUMBER }} | |
run: | | |
echo "π Checking if the workflow should fail and revert PR to draft based on security alerts..." | |
# Check if there are any unresolved alerts or dismissed alerts without comments. | |
if [[ "$UNRESOLVED_COUNT" -gt 0 || "$DISMISSED_COUNT" -gt 0 ]]; then | |
COMMENT_BODY+="β οΈ **Please resolve the above issues before merging.**\n\n" | |
echo "β ERROR: Found issues in the PR:" | |
if [[ "$UNRESOLVED_COUNT" -gt 0 ]]; then | |
echo "- $UNRESOLVED_COUNT unresolved security alert(s) found!" | |
fi | |
if [[ "$DISMISSED_COUNT" -gt 0 ]]; then | |
echo "- $DISMISSED_COUNT security alert(s) were dismissed without comments!" | |
fi | |
echo "β οΈ These alerts must be resolved before merging." | |
# Revert the PR to draft. | |
echo "Reverting PR #${PR_NUMBER} to draft state due to blocking security issues..." | |
curl -X PATCH \ | |
-H "Authorization: token ${GITHUB_TOKEN}" \ | |
-H "Content-Type: application/json" \ | |
-d '{"draft": true}' \ | |
"https://api.github.com/repos/${{ github.repository }}/pulls/${PR_NUMBER}" | |
exit 1 | |
fi | |
echo "β No blocking security issues found. The workflow will pass successfully." |