Skip to content

FIX: forked-pr-coverage.yml env-injection via untrusted artifact - #714

Open
Sumit Sarabhai (sumitmsft) wants to merge 4 commits into
mainfrom
sumitmsft-argus-ado46466
Open

FIX: forked-pr-coverage.yml env-injection via untrusted artifact#714
Sumit Sarabhai (sumitmsft) wants to merge 4 commits into
mainfrom
sumitmsft-argus-ado46466

Conversation

@sumitmsft

@sumitmsftSumit Sarabhai (sumitmsft) commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#46466

Summary

  • Treat fork-produced coverage artifacts as untrusted input and validate their schema and values before use.
  • Bind coverage comments to the triggering pull request instead of accepting an artifact-supplied PR number.
  • Remove untrusted artifact propagation through GITHUB_ENV and reject unexpected files, unsafe URLs, control characters, and oversized values.
  • Pin GitHub Actions to immutable commit SHAs and disable persisted checkout credentials.
  • Add regression coverage for environment injection, cross-PR targeting, executable artifacts, and Markdown/URL injection.

Validation

  • 12 targeted security regression tests passed.
  • Changed workflow YAML parsed successfully.
  • Black formatting checks passed.
  • Native extension build completed successfully.

Validate untrusted coverage artifacts before posting comments, bind comments to the triggering pull request, remove privileged environment propagation, and pin actions to immutable revisions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 11, 2026 11:16
@github-actionsgithub-actionsBot added the pr-size: large Substantial code update label Aug 11, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the forked PR coverage-comment pipeline by treating coverage artifacts as untrusted input, validating/normalizing the data used to generate PR comments, and reducing the privileged workflow’s exposure to artifact-driven environment injection.

Changes:

  • Introduces a Python validator/comment builder (prepare_fork_coverage_comment.py) and a security-focused regression test suite.
  • Refactors forked-pr-coverage.yml to validate artifacts + resolve the target PR from the triggering workflow context (not artifact-supplied data), and post/update a single sticky comment.
  • Tightens the producer workflow (pr-code-coverage.yml) by removing artifact-supplied PR targeting fields, hardening multiline GITHUB_ENV usage, and pinning actions to SHAs.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
tests/test_fork_coverage_security.pyAdds regression tests for artifact schema validation, URL/markup injection, PR resolution, and workflow env-injection checks.
.github/workflows/pr-code-coverage.ymlPins actions, disables persisted credentials, hardens multiline env export, and removes untrusted fields from the uploaded artifact payload.
.github/workflows/forked-pr-coverage.ymlMoves privileged workflow to validate downloaded artifacts + resolve PR from the event/commit association before commenting.
.github/scripts/prepare_fork_coverage_comment.pyImplements schema/value validation and safe comment construction for fork coverage artifacts.
.github/actions/post-coverage-comment/action.ymlPins the sticky-comment action to an immutable SHA.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread.github/scripts/prepare_fork_coverage_comment.py
Comment threadtests/test_fork_coverage_security.py
@github-actions

github-actionsBot commented Aug 11, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

82%


📈 Total Lines Covered:7368 out of 8963
📁 Project:mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

No lines with coverage information in this diff.


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 59.9%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 76.6%
mssql_python.__init__.py: 77.6%
mssql_python.row.py: 77.6%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 81.4%
mssql_python.pybind.connection.connection.cpp: 84.3%
mssql_python.logging.py: 85.5%

🔗 Quick Links

⚙️ Build Summary📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

Enforce the one-file artifact schema without recursively traversing attacker-controlled directory trees.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requesting changes - some issues wrt forked pr comments and refactoring suggestions to cut the PR short

Comment thread.github/scripts/prepare_fork_coverage_comment.py
Comment thread.github/scripts/prepare_fork_coverage_comment.py Outdated
Comment thread.github/workflows/forked-pr-coverage.yml Outdated
Comment thread.github/scripts/prepare_fork_coverage_comment.py Outdated
Comment thread.github/workflows/forked-pr-coverage.yml Outdated
Comment thread.github/scripts/prepare_fork_coverage_comment.py
Comment thread.github/workflows/forked-pr-coverage.yml Outdated
Comment thread.github/scripts/prepare_fork_coverage_comment.py
Comment thread.github/scripts/prepare_fork_coverage_comment.py Outdated
Comment thread.github/workflows/forked-pr-coverage.yml Outdated
- forked-pr-coverage.yml: query /pulls?state=open (paginated) instead of
/commits/{sha}/pulls, which never returns a fork's head commit, so fork
PRs never resolved and no coverage comment was ever posted.
- prepare_fork_coverage_comment.py: remove the workflow_run.pull_requests
fast-path; GitHub always sends it empty for fork runs, so it was dead
code that only the happy-path test exercised.
- tests: point the happy-path test at the real head-SHA resolution and add
a regression that an attacker-supplied event pull_requests entry is ignored.
- Revert action SHA pins (checkout, upload-artifact, sticky-comment) to tags
to avoid colliding with #716, which owns SHA pinning.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- prepare_fork_coverage_comment.py: require the six expected fields to be
present rather than an exact set, so adding a field to the producer no
longer silently disables fork coverage comments. All six values are still
strictly validated and only those six are propagated.
- tests: assert an artifact-supplied pr_number is tolerated but never
propagated, and that a missing required field is still rejected.
- forked-pr-coverage.yml: drop the RUN_ID, HEAD_SHA and PR_NUMBER shell
guards. RUN_ID/HEAD_SHA come straight from the trusted workflow_run event
(HEAD_SHA is re-validated in the script), and PR_NUMBER only re-checks the
script's own validated output. HEAD_SHA env removed as it is now unused.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requesting changes - for a different blocker this time, added a couple of suggestions as well

echo "$PATCH_SUMMARY"
echo "EOF"
} >> $GITHUB_ENV
gh api --paginate --slurp \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocker: gh won't run this. --slurp and --jq can't be used together:

$ gh api --paginate --slurp "repos/.../pulls?state=open&per_page=100" --jq 'add'
the `--slurp` option is not supported with `--jq` or `--template`
exit 1

fails on every gh version, and the whole step goes with it
same combination at line 75 (commented there as well - with a better suggestion)

piping instead works:

gh api --paginate --slurp "repos/${GITHUB_REPOSITORY}/pulls?state=open&per_page=100" \
| jq 'add' > "$PULLS_FILE"

checked against the live api and all three fork PRs here resolve correctly through it

matching_pulls = [
pull
for pull in associated_pulls
if pull.get("head", {}).get("sha") == head_sha

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: match the fork name too, not just the commit.

two forks sitting on the same commit would both match today, and then nobody gets a comment. harmless, just noticed it


PR_NUMBER="$(cat "$PR_NUMBER_FILE")"

COMMENT_ID="$(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has the same blocker as the line 60 one, I have a better suggestion which includes fixing that as well:

gh pr comment does find-or-create on its own, so this whole block collapses to one call.

gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
--body-file "$COMMENT_FILE" --edit-last --create-if-none

18 lines down to 2. the script writes the body as markdown instead of wrapping it in json, same line either way. the argus writeup named this one directly, a plain gh pr comment call, so it stays on the no-node-action path you took.

if you do take it, the line 75 call goes with the block, so that one needs no fix.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: largeSubstantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@sumitmsft@bewithgaurav@gargsaumya