From a642d16f89b205eb21789c15e6f73dbfcd072c73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 20:59:18 +0000 Subject: [PATCH 1/2] Add security advisory email notification workflow Agent-Logs-Url: https://github.com/th30d4y/OpenLearnX/sessions/0933e001-4da8-4d13-882d-0a845ec776e5 Co-authored-by: 0x5t4l1n <161853795+0x5t4l1n@users.noreply.github.com> --- .github/workflows/advisory-email-notify.yml | 93 +++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .github/workflows/advisory-email-notify.yml diff --git a/.github/workflows/advisory-email-notify.yml b/.github/workflows/advisory-email-notify.yml new file mode 100644 index 0000000..037daf3 --- /dev/null +++ b/.github/workflows/advisory-email-notify.yml @@ -0,0 +1,93 @@ +name: Security Advisory Email Notification + +on: + repository_advisory: + types: [reported, created, published, submitted, reopened] + +permissions: + contents: read + +jobs: + notify-owners: + runs-on: ubuntu-latest + + steps: + - name: Ensure SMTP secrets are configured + id: check_secrets + env: + MAIL_SERVER: ${{ secrets.ADVISORY_MAIL_SERVER }} + MAIL_USERNAME: ${{ secrets.ADVISORY_MAIL_USERNAME }} + MAIL_PASSWORD: ${{ secrets.ADVISORY_MAIL_PASSWORD }} + MAIL_TO: ${{ secrets.ADVISORY_MAIL_TO }} + run: | + missing=() + [ -z "$MAIL_SERVER" ] && missing+=("ADVISORY_MAIL_SERVER") + [ -z "$MAIL_USERNAME" ] && missing+=("ADVISORY_MAIL_USERNAME") + [ -z "$MAIL_PASSWORD" ] && missing+=("ADVISORY_MAIL_PASSWORD") + [ -z "$MAIL_TO" ] && missing+=("ADVISORY_MAIL_TO") + + if [ ${#missing[@]} -gt 0 ]; then + echo "::warning::Missing required secrets: ${missing[*]}. Skipping advisory email notification. Configure these secrets in your repository or organization settings." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Build email body + if: steps.check_secrets.outputs.skip == 'false' + id: build_body + env: + ADVISORY_TITLE: ${{ github.event.repository_advisory.summary }} + ADVISORY_SEVERITY: ${{ github.event.repository_advisory.severity }} + ADVISORY_STATE: ${{ github.event.repository_advisory.state }} + ADVISORY_CVE: ${{ github.event.repository_advisory.cve_id }} + ADVISORY_URL: ${{ github.event.repository_advisory.html_url }} + ADVISORY_DESCRIPTION: ${{ github.event.repository_advisory.description }} + ADVISORY_ACTION: ${{ github.event.action }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + CVE_LINE="" + if [ -n "$ADVISORY_CVE" ]; then + CVE_LINE="CVE ID : $ADVISORY_CVE"$'\n' + fi + + BODY="A security advisory has been ${ADVISORY_ACTION} on the repository ${REPO}. + +Repository : https://github.com/${REPO} +Action : ${ADVISORY_ACTION} +Title : ${ADVISORY_TITLE} +Severity : ${ADVISORY_SEVERITY} +State : ${ADVISORY_STATE} +${CVE_LINE}Advisory : ${ADVISORY_URL} +Reported by: ${ACTOR} + +--- Description --- +${ADVISORY_DESCRIPTION} + +--- +This notification was sent automatically by GitHub Actions. +Repository: https://github.com/${REPO} +" + + # Write to file to avoid multiline env issues + printf '%s' "$BODY" > /tmp/advisory_body.txt + echo "body_file=/tmp/advisory_body.txt" >> "$GITHUB_OUTPUT" + + - name: Send email to org owners + if: steps.check_secrets.outputs.skip == 'false' + # dawidd6/action-send-mail v3.12.0 โ€” pinned to exact commit for supply-chain security. + # Verify: https://github.com/dawidd6/action-send-mail/commit/2cea9617b09d83a84e7015b8b4c893f01b028020 + uses: dawidd6/action-send-mail@2cea9617b09d83a84e7015b8b4c893f01b028020 + with: + server_address: ${{ secrets.ADVISORY_MAIL_SERVER }} + # ADVISORY_MAIL_PORT defaults to 465 (SMTPS / implicit TLS). + # Set the secret to 587 and secure to false if your SMTP server requires STARTTLS instead. + server_port: ${{ secrets.ADVISORY_MAIL_PORT || 465 }} + secure: ${{ secrets.ADVISORY_MAIL_SECURE || true }} + username: ${{ secrets.ADVISORY_MAIL_USERNAME }} + password: ${{ secrets.ADVISORY_MAIL_PASSWORD }} + subject: "[Security Advisory] [${{ github.event.repository_advisory.severity }}] ${{ github.event.repository_advisory.summary }} โ€” ${{ github.repository }}" + to: ${{ secrets.ADVISORY_MAIL_TO }} + from: "GitHub Advisory Bot <${{ secrets.ADVISORY_MAIL_USERNAME }}>" + body: file:///tmp/advisory_body.txt From 6213bc68f77343ac6446d2418c25a6e5a2884da1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 21:02:29 +0000 Subject: [PATCH 2/2] Replace SMTP email with Discord webhook for advisory notifications Agent-Logs-Url: https://github.com/th30d4y/OpenLearnX/sessions/c1dfb007-d13e-4526-b2aa-1abb3365dd5a Co-authored-by: 0x5t4l1n <161853795+0x5t4l1n@users.noreply.github.com> --- .github/workflows/advisory-discord-notify.yml | 102 ++++++++++++++++++ .github/workflows/advisory-email-notify.yml | 93 ---------------- 2 files changed, 102 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/advisory-discord-notify.yml delete mode 100644 .github/workflows/advisory-email-notify.yml diff --git a/.github/workflows/advisory-discord-notify.yml b/.github/workflows/advisory-discord-notify.yml new file mode 100644 index 0000000..a52f670 --- /dev/null +++ b/.github/workflows/advisory-discord-notify.yml @@ -0,0 +1,102 @@ +name: Security Advisory Discord Notification + +on: + repository_advisory: + types: [reported, created, published, submitted, reopened] + +permissions: + contents: read + +jobs: + notify-owners: + runs-on: ubuntu-latest + + steps: + - name: Ensure Discord webhook is configured + id: check_secret + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + run: | + if [ -z "$DISCORD_WEBHOOK" ]; then + echo "::warning::Missing required secret: DISCORD_WEBHOOK. Skipping advisory notification. Add a Discord webhook URL as the DISCORD_WEBHOOK secret in your repository or organization settings." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Send Discord notification + if: steps.check_secret.outputs.skip == 'false' + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + ADVISORY_TITLE: ${{ github.event.repository_advisory.summary }} + ADVISORY_SEVERITY: ${{ github.event.repository_advisory.severity }} + ADVISORY_STATE: ${{ github.event.repository_advisory.state }} + ADVISORY_CVE: ${{ github.event.repository_advisory.cve_id }} + ADVISORY_URL: ${{ github.event.repository_advisory.html_url }} + ADVISORY_DESCRIPTION: ${{ github.event.repository_advisory.description }} + ADVISORY_ACTION: ${{ github.event.action }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + # Map severity to a Discord embed colour (decimal RGB) + case "${ADVISORY_SEVERITY,,}" in + critical) COLOR=9831467 ;; # #960B0B dark red + high) COLOR=15548997 ;; # #ED4245 red + medium) COLOR=15105570 ;; # #E67E22 orange + low) COLOR=16776960 ;; # #FFFF00 yellow + *) COLOR=8421504 ;; # #808080 grey + esac + + # Build optional CVE field entry + CVE_FIELD="" + if [ -n "$ADVISORY_CVE" ]; then + CVE_FIELD=$(jq -n --arg cve "$ADVISORY_CVE" \ + '{name: "CVE ID", value: $cve, inline: true}') + CVE_FIELD=",${CVE_FIELD}" + fi + + # Truncate description to Discord's 1024-char field limit + DESCRIPTION="${ADVISORY_DESCRIPTION:0:1024}" + + PAYLOAD=$(jq -n \ + --arg title "๐Ÿ”’ Security Advisory ${ADVISORY_ACTION^} in \`${REPO}\`" \ + --arg footer_text "GitHub Advisory ยท ${REPO}" \ + --arg url "$ADVISORY_URL" \ + --arg adv_title "$ADVISORY_TITLE" \ + --arg severity "${ADVISORY_SEVERITY^}" \ + --arg state "${ADVISORY_STATE^}" \ + --arg actor "$ACTOR" \ + --arg description "$DESCRIPTION" \ + --argjson color "$COLOR" \ + '{ + embeds: [{ + title: $title, + url: $url, + color: $color, + fields: [ + {name: "Title", value: $adv_title, inline: false}, + {name: "Severity", value: $severity, inline: true}, + {name: "State", value: $state, inline: true}, + {name: "Reported by", value: $actor, inline: true}, + {name: "Description", value: $description, inline: false} + ], + footer: {text: $footer_text} + }] + }') + + # Inject optional CVE field before the description field + if [ -n "$CVE_FIELD" ]; then + PAYLOAD=$(echo "$PAYLOAD" | jq \ + --argjson cve_field "$CVE_FIELD" \ + '.embeds[0].fields |= .[:3] + [$cve_field] + .[3:]') + fi + + RESPONSE=$(curl -sS -o /tmp/discord_response.txt -w "%{http_code}" \ + -X POST "$DISCORD_WEBHOOK" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + + if [ "$RESPONSE" -lt 200 ] || [ "$RESPONSE" -ge 300 ]; then + echo "::error::Discord webhook failed (HTTP $RESPONSE): $(cat /tmp/discord_response.txt)" + exit 1 + fi diff --git a/.github/workflows/advisory-email-notify.yml b/.github/workflows/advisory-email-notify.yml deleted file mode 100644 index 037daf3..0000000 --- a/.github/workflows/advisory-email-notify.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Security Advisory Email Notification - -on: - repository_advisory: - types: [reported, created, published, submitted, reopened] - -permissions: - contents: read - -jobs: - notify-owners: - runs-on: ubuntu-latest - - steps: - - name: Ensure SMTP secrets are configured - id: check_secrets - env: - MAIL_SERVER: ${{ secrets.ADVISORY_MAIL_SERVER }} - MAIL_USERNAME: ${{ secrets.ADVISORY_MAIL_USERNAME }} - MAIL_PASSWORD: ${{ secrets.ADVISORY_MAIL_PASSWORD }} - MAIL_TO: ${{ secrets.ADVISORY_MAIL_TO }} - run: | - missing=() - [ -z "$MAIL_SERVER" ] && missing+=("ADVISORY_MAIL_SERVER") - [ -z "$MAIL_USERNAME" ] && missing+=("ADVISORY_MAIL_USERNAME") - [ -z "$MAIL_PASSWORD" ] && missing+=("ADVISORY_MAIL_PASSWORD") - [ -z "$MAIL_TO" ] && missing+=("ADVISORY_MAIL_TO") - - if [ ${#missing[@]} -gt 0 ]; then - echo "::warning::Missing required secrets: ${missing[*]}. Skipping advisory email notification. Configure these secrets in your repository or organization settings." - echo "skip=true" >> "$GITHUB_OUTPUT" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Build email body - if: steps.check_secrets.outputs.skip == 'false' - id: build_body - env: - ADVISORY_TITLE: ${{ github.event.repository_advisory.summary }} - ADVISORY_SEVERITY: ${{ github.event.repository_advisory.severity }} - ADVISORY_STATE: ${{ github.event.repository_advisory.state }} - ADVISORY_CVE: ${{ github.event.repository_advisory.cve_id }} - ADVISORY_URL: ${{ github.event.repository_advisory.html_url }} - ADVISORY_DESCRIPTION: ${{ github.event.repository_advisory.description }} - ADVISORY_ACTION: ${{ github.event.action }} - REPO: ${{ github.repository }} - ACTOR: ${{ github.actor }} - run: | - CVE_LINE="" - if [ -n "$ADVISORY_CVE" ]; then - CVE_LINE="CVE ID : $ADVISORY_CVE"$'\n' - fi - - BODY="A security advisory has been ${ADVISORY_ACTION} on the repository ${REPO}. - -Repository : https://github.com/${REPO} -Action : ${ADVISORY_ACTION} -Title : ${ADVISORY_TITLE} -Severity : ${ADVISORY_SEVERITY} -State : ${ADVISORY_STATE} -${CVE_LINE}Advisory : ${ADVISORY_URL} -Reported by: ${ACTOR} - ---- Description --- -${ADVISORY_DESCRIPTION} - ---- -This notification was sent automatically by GitHub Actions. -Repository: https://github.com/${REPO} -" - - # Write to file to avoid multiline env issues - printf '%s' "$BODY" > /tmp/advisory_body.txt - echo "body_file=/tmp/advisory_body.txt" >> "$GITHUB_OUTPUT" - - - name: Send email to org owners - if: steps.check_secrets.outputs.skip == 'false' - # dawidd6/action-send-mail v3.12.0 โ€” pinned to exact commit for supply-chain security. - # Verify: https://github.com/dawidd6/action-send-mail/commit/2cea9617b09d83a84e7015b8b4c893f01b028020 - uses: dawidd6/action-send-mail@2cea9617b09d83a84e7015b8b4c893f01b028020 - with: - server_address: ${{ secrets.ADVISORY_MAIL_SERVER }} - # ADVISORY_MAIL_PORT defaults to 465 (SMTPS / implicit TLS). - # Set the secret to 587 and secure to false if your SMTP server requires STARTTLS instead. - server_port: ${{ secrets.ADVISORY_MAIL_PORT || 465 }} - secure: ${{ secrets.ADVISORY_MAIL_SECURE || true }} - username: ${{ secrets.ADVISORY_MAIL_USERNAME }} - password: ${{ secrets.ADVISORY_MAIL_PASSWORD }} - subject: "[Security Advisory] [${{ github.event.repository_advisory.severity }}] ${{ github.event.repository_advisory.summary }} โ€” ${{ github.repository }}" - to: ${{ secrets.ADVISORY_MAIL_TO }} - from: "GitHub Advisory Bot <${{ secrets.ADVISORY_MAIL_USERNAME }}>" - body: file:///tmp/advisory_body.txt