Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions .github/workflows/post-coverage-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: Post Coverage Comment

on:
workflow_run:
workflows: ["PR Code Coverage"]
types:
- completed

jobs:
post-comment:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
permissions:
pull-requests: write
contents: read

steps:
- name: Download coverage data
uses: actions/download-artifact@v4
with:
name: coverage-comment-data
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}

- name: Read coverage data
id: coverage
run: |
if [[ ! -f pr-info.json ]]; then
echo "❌ pr-info.json not found"
exit 1
fi

cat pr-info.json

# Extract values from JSON
PR_NUMBER=$(jq -r '.pr_number' pr-info.json)
COVERAGE_PCT=$(jq -r '.coverage_percentage' pr-info.json)
COVERED_LINES=$(jq -r '.covered_lines' pr-info.json)
TOTAL_LINES=$(jq -r '.total_lines' pr-info.json)
PATCH_PCT=$(jq -r '.patch_coverage_pct' pr-info.json)
LOW_COV_FILES=$(jq -r '.low_coverage_files' pr-info.json)
PATCH_SUMMARY=$(jq -r '.patch_coverage_summary' pr-info.json)
ADO_URL=$(jq -r '.ado_url' pr-info.json)

# Export to env for next step
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
echo "COVERAGE_PERCENTAGE=$COVERAGE_PCT" >> $GITHUB_ENV
echo "COVERED_LINES=$COVERED_LINES" >> $GITHUB_ENV
echo "TOTAL_LINES=$TOTAL_LINES" >> $GITHUB_ENV
echo "PATCH_COVERAGE_PCT=$PATCH_PCT" >> $GITHUB_ENV
echo "ADO_URL=$ADO_URL" >> $GITHUB_ENV

# Handle multiline values
echo "LOW_COVERAGE_FILES<<EOF" >> $GITHUB_ENV
echo "$LOW_COV_FILES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV

echo "PATCH_COVERAGE_SUMMARY<<EOF" >> $GITHUB_ENV
echo "$PATCH_SUMMARY" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
Comment on lines +29 to +62

Check failure

Code scanning / CodeQL

Environment variable built from user-controlled sources Critical

Potential environment variable injection in [if \[\[ ! -f pr-info.json \]\]; then
echo "❌ pr-info.json not found"
exit 1
fi cat pr-info.json Extract values from JSON PR_NUMBER=$(jq -r '.pr_number' pr-info.json)
COVERAGE_PCT=$(jq -r '.coverage_percentage' pr-info.json)
COVERED_LINES=$(jq -r '.covered_lines' pr-info.json)
TOTAL_LINES=$(jq -r '.total_lines' pr-info.json)
PATCH_PCT=$(jq -r '.patch_coverage_pct' pr-info.json)
LOW_COV_FILES=$(jq -r '.low_coverage_files' pr-info.json)
PATCH_SUMMARY=$(jq -r '.patch_coverage_summary' pr-info.json)
ADO_URL=$(jq -r '.ado_url' pr-info.json) Export to env for next step echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
echo "COVERAGE_PERCENTAGE=$COVERAGE_PCT" >> $GITHUB_ENV
echo "COVERED_LINES=$COVERED_LINES" >> $GITHUB_ENV
echo "TOTAL_LINES=$TOTAL_LINES" >> $GITHUB_ENV
echo "PATCH_COVERAGE_PCT=$PATCH_PCT" >> $GITHUB_ENV
echo "ADO_URL=$ADO_URL" >> $GITHUB_ENV Handle multiline values echo "LOW_COVERAGE_FILES<<EOF" >> $GITHUB_ENV
echo "$LOW_COV_FILES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV echo "PATCH_COVERAGE_SUMMARY<<EOF" >> $GITHUB_ENV
echo "$PATCH_SUMMARY" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV](1), which may be controlled by an external user (
workflow_run
).

Copilot Autofix

AI 15 days ago

In general:
Scrub and validate all inputs taken from untrusted sources before writing them as environment variables via $GITHUB_ENV. This involves:

  • For single-line variables: Strip newlines and, optionally, enforce an allowlist pattern.
  • For multi-line values: Generate a unique delimiter to reduce the chance of injection, and if possible, also validate or escape user-provided values to prevent them containing the delimiter.

Detailed fix for this code:

  • For single-line variables (PR_NUMBER, COVERAGE_PCT, etc.), sanitize the values to remove any newlines or dangerous characters. Use tr -d '\n' or similar to strip newlines.
  • For multi-line environment variables (LOW_COV_FILES, PATCH_SUMMARY), use a random, unique delimiter for the heredoc (e.g., EOF_<uuid> or derived from uuidgen). Ensure that the delimiter is unique and unlikely to appear in the injected data. This prevents attackers from prematurely closing the heredoc via injected newlines.
  • Optional: For critical integer fields (PR_NUMBER and line counts), consider restricting to only digits via pattern matching.
  • The changes are all in the "Read coverage data" run block in the YAML. No new packages are needed.
  • Minimal posix utilities (e.g., tr, uuidgen) are available in ubuntu-latest runners.

Suggested changeset 1
.github/workflows/post-coverage-comment.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/post-coverage-comment.yml b/.github/workflows/post-coverage-comment.yml
--- a/.github/workflows/post-coverage-comment.yml
+++ b/.github/workflows/post-coverage-comment.yml
@@ -35,16 +35,16 @@
           cat pr-info.json
           
           # Extract values from JSON
-          PR_NUMBER=$(jq -r '.pr_number' pr-info.json)
-          COVERAGE_PCT=$(jq -r '.coverage_percentage' pr-info.json)
-          COVERED_LINES=$(jq -r '.covered_lines' pr-info.json)
-          TOTAL_LINES=$(jq -r '.total_lines' pr-info.json)
-          PATCH_PCT=$(jq -r '.patch_coverage_pct' pr-info.json)
+          PR_NUMBER=$(jq -r '.pr_number' pr-info.json | tr -d '\n\r')
+          COVERAGE_PCT=$(jq -r '.coverage_percentage' pr-info.json | tr -d '\n\r')
+          COVERED_LINES=$(jq -r '.covered_lines' pr-info.json | tr -d '\n\r')
+          TOTAL_LINES=$(jq -r '.total_lines' pr-info.json | tr -d '\n\r')
+          PATCH_PCT=$(jq -r '.patch_coverage_pct' pr-info.json | tr -d '\n\r')
           LOW_COV_FILES=$(jq -r '.low_coverage_files' pr-info.json)
           PATCH_SUMMARY=$(jq -r '.patch_coverage_summary' pr-info.json)
-          ADO_URL=$(jq -r '.ado_url' pr-info.json)
+          ADO_URL=$(jq -r '.ado_url' pr-info.json | tr -d '\n\r')
           
-          # Export to env for next step
+          # Export to env for next step (sanitize values to avoid env var injection)
           echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
           echo "COVERAGE_PERCENTAGE=$COVERAGE_PCT" >> $GITHUB_ENV
           echo "COVERED_LINES=$COVERED_LINES" >> $GITHUB_ENV
@@ -52,14 +49,16 @@
           echo "PATCH_COVERAGE_PCT=$PATCH_PCT" >> $GITHUB_ENV
           echo "ADO_URL=$ADO_URL" >> $GITHUB_ENV
           
-          # Handle multiline values
-          echo "LOW_COVERAGE_FILES<<EOF" >> $GITHUB_ENV
+          # Handle multiline values SAFELY using unique delimiter
+          EOFF=$(uuidgen)
+          echo "LOW_COVERAGE_FILES<<EOF_$EOFF" >> $GITHUB_ENV
           echo "$LOW_COV_FILES" >> $GITHUB_ENV
-          echo "EOF" >> $GITHUB_ENV
+          echo "EOF_$EOFF" >> $GITHUB_ENV
           
-          echo "PATCH_COVERAGE_SUMMARY<<EOF" >> $GITHUB_ENV
+          EOFS=$(uuidgen)
+          echo "PATCH_COVERAGE_SUMMARY<<EOF_$EOFS" >> $GITHUB_ENV
           echo "$PATCH_SUMMARY" >> $GITHUB_ENV
-          echo "EOF" >> $GITHUB_ENV
+          echo "EOF_$EOFS" >> $GITHUB_ENV
 
       - name: Comment coverage summary on PR
         uses: marocchino/sticky-pull-request-comment@v2
EOF
@@ -35,16 +35,16 @@
cat pr-info.json

# Extract values from JSON
PR_NUMBER=$(jq -r '.pr_number' pr-info.json)
COVERAGE_PCT=$(jq -r '.coverage_percentage' pr-info.json)
COVERED_LINES=$(jq -r '.covered_lines' pr-info.json)
TOTAL_LINES=$(jq -r '.total_lines' pr-info.json)
PATCH_PCT=$(jq -r '.patch_coverage_pct' pr-info.json)
PR_NUMBER=$(jq -r '.pr_number' pr-info.json | tr -d '\n\r')
COVERAGE_PCT=$(jq -r '.coverage_percentage' pr-info.json | tr -d '\n\r')
COVERED_LINES=$(jq -r '.covered_lines' pr-info.json | tr -d '\n\r')
TOTAL_LINES=$(jq -r '.total_lines' pr-info.json | tr -d '\n\r')
PATCH_PCT=$(jq -r '.patch_coverage_pct' pr-info.json | tr -d '\n\r')
LOW_COV_FILES=$(jq -r '.low_coverage_files' pr-info.json)
PATCH_SUMMARY=$(jq -r '.patch_coverage_summary' pr-info.json)
ADO_URL=$(jq -r '.ado_url' pr-info.json)
ADO_URL=$(jq -r '.ado_url' pr-info.json | tr -d '\n\r')

# Export to env for next step
# Export to env for next step (sanitize values to avoid env var injection)
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
echo "COVERAGE_PERCENTAGE=$COVERAGE_PCT" >> $GITHUB_ENV
echo "COVERED_LINES=$COVERED_LINES" >> $GITHUB_ENV
@@ -52,14 +49,16 @@
echo "PATCH_COVERAGE_PCT=$PATCH_PCT" >> $GITHUB_ENV
echo "ADO_URL=$ADO_URL" >> $GITHUB_ENV

# Handle multiline values
echo "LOW_COVERAGE_FILES<<EOF" >> $GITHUB_ENV
# Handle multiline values SAFELY using unique delimiter
EOFF=$(uuidgen)
echo "LOW_COVERAGE_FILES<<EOF_$EOFF" >> $GITHUB_ENV
echo "$LOW_COV_FILES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
echo "EOF_$EOFF" >> $GITHUB_ENV

echo "PATCH_COVERAGE_SUMMARY<<EOF" >> $GITHUB_ENV
EOFS=$(uuidgen)
echo "PATCH_COVERAGE_SUMMARY<<EOF_$EOFS" >> $GITHUB_ENV
echo "$PATCH_SUMMARY" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
echo "EOF_$EOFS" >> $GITHUB_ENV

- name: Comment coverage summary on PR
uses: marocchino/sticky-pull-request-comment@v2
Copilot is powered by AI and may make mistakes. Always verify output.

- name: Comment coverage summary on PR
uses: marocchino/sticky-pull-request-comment@v2
with:
number: ${{ env.PR_NUMBER }}
header: Code Coverage Report
message: |
# 📊 Code Coverage Report

<table>
<tr>
<td align="center" width="200">

### 🔥 Diff Coverage
### **${{ env.PATCH_COVERAGE_PCT }}**
<br>
</td>
<td align="center" width="200">

### 🎯 Overall Coverage
### **${{ env.COVERAGE_PERCENTAGE }}**
<br>
</td>
<td>

**📈 Total Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}`
**📁 Project:** `mssql-python`

</td>
</tr>
</table>

---

${{ env.PATCH_COVERAGE_SUMMARY }}

---
### 📋 Files Needing Attention

<details>
<summary>📉 <strong>Files with overall lowest coverage</strong> (click to expand)</summary>
<br>

```diff
${{ env.LOW_COVERAGE_FILES }}
```

</details>

---
### 🔗 Quick Links

<table>
<tr>
<td align="left" width="200">
<b>⚙️ Build Summary</b>
</td>
<td align="left">
<b>📋 Coverage Details</b>
</td>
</tr>
<tr>
<td align="left" width="200">

[View Azure DevOps Build](${{ env.ADO_URL }})

</td>
<td align="left">

[Browse Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab)

</td>
</tr>
</table>
26 changes: 26 additions & 0 deletions .github/workflows/pr-code-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,33 @@ jobs:
echo "PATCH_COVERAGE_SUMMARY=Patch coverage report could not be generated." >> $GITHUB_ENV
fi

- name: Save coverage data for comment
run: |
mkdir -p coverage-comment-data
cat > coverage-comment-data/pr-info.json << EOF
{
"pr_number": "${{ github.event.pull_request.number }}",
"coverage_percentage": "${{ env.COVERAGE_PERCENTAGE }}",
"covered_lines": "${{ env.COVERED_LINES }}",
"total_lines": "${{ env.TOTAL_LINES }}",
"patch_coverage_pct": "${{ env.PATCH_COVERAGE_PCT }}",
"low_coverage_files": $(jq -Rs . <<< "${{ env.LOW_COVERAGE_FILES }}"),
"patch_coverage_summary": $(jq -Rs . <<< "${{ env.PATCH_COVERAGE_SUMMARY }}"),
"ado_url": "${{ env.ADO_URL }}"
}
EOF
cat coverage-comment-data/pr-info.json

- name: Upload coverage comment data
uses: actions/upload-artifact@v4
with:
name: coverage-comment-data
path: coverage-comment-data/
retention-days: 1

- name: Comment coverage summary on PR
# Skip for forked PRs due to token permission restrictions
if: github.event.pull_request.head.repo.full_name == github.repository
uses: marocchino/sticky-pull-request-comment@v2
with:
header: Code Coverage Report
Expand Down
5 changes: 3 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

# Clean one-liner: set level and output mode together
setup_logging(output="both")

print("Logging is set up.")
print("This is a test PR for mssql-python.")
conn_str = os.getenv("DB_CONNECTION_STRING")
conn = connect(conn_str)
cursor = conn.cursor()
Expand All @@ -15,4 +16,4 @@
print(f"Database ID: {row[0]}, Name: {row[1]}")

cursor.close()
conn.close()
conn.close()
Loading