Skip to content

Conversation

@Yaminyam
Copy link
Member

Introduces a step to mint a PyPI API token using GitHub Actions OIDC authentication, replacing the use of static secrets for publishing. Updates permissions and environment variables accordingly to enhance security and automation in the deployment process.

Introduces a step to mint a PyPI API token using GitHub Actions OIDC authentication, replacing the use of static secrets for publishing. Updates permissions and environment variables accordingly to enhance security and automation in the deployment process.
Copilot AI review requested due to automatic review settings December 22, 2025 07:06
@github-actions github-actions bot added the size:S 10~30 LoC label Dec 22, 2025
@Yaminyam Yaminyam added skip:changelog Make the action workflow to skip towncrier check and removed size:S 10~30 LoC labels Dec 22, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR modernizes the CI/CD pipeline by introducing OIDC-based authentication for PyPI publishing, replacing static API credentials with dynamically minted tokens. This approach enhances security by eliminating long-lived secrets and leveraging GitHub Actions' native OIDC provider.

Key Changes:

  • Added id-token: write permission to enable OIDC token retrieval
  • Implemented a token minting step that exchanges GitHub OIDC tokens for PyPI API tokens
  • Updated Twine authentication to use dynamically generated tokens instead of stored secrets

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

oidc_token=$(jq -r '.value' <<< "${resp}")
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The curl command is missing the Content-Type header for the POST request. PyPI's OIDC mint-token endpoint expects 'Content-Type: application/json' when sending JSON data. Without this header, the request may fail or be rejected by the server.

Suggested change
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token \
-H "Content-Type: application/json" \
-d "{\"token\": \"${oidc_token}\"}")

Copilot uses AI. Check for mistakes.
Comment on lines +549 to +563
run: |
# retrieve the ambient OIDC token
resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")
oidc_token=$(jq -r '.value' <<< "${resp}")
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
api_token=$(jq -r '.token' <<< "${resp}")
# mask the newly minted API token, so that we don't accidentally leak it
echo "::add-mask::${api_token}"
# see the next step in the workflow for an example of using this step output
echo "api-token=${api_token}" >> "${GITHUB_OUTPUT}"
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The script doesn't set the shell to fail on error. Without 'set -euo pipefail' or similar error handling flags, the script will continue executing even if intermediate commands fail (like jq parsing or curl requests). This could lead to undefined behavior and make debugging difficult.

Copilot uses AI. Check for mistakes.
Comment on lines +551 to +556
resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")
oidc_token=$(jq -r '.value' <<< "${resp}")
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The curl commands don't include the '--fail' or '--fail-with-body' flag. Without this, curl will return 0 (success) even for HTTP error responses (4xx, 5xx), making it impossible to detect API failures. This means invalid tokens or authentication errors won't be caught.

Suggested change
resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")
oidc_token=$(jq -r '.value' <<< "${resp}")
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
resp=$(curl --fail-with-body -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")
oidc_token=$(jq -r '.value' <<< "${resp}")
# exchange the OIDC token for an API token
resp=$(curl --fail-with-body -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")

Copilot uses AI. Check for mistakes.
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ steps.mint-token.outputs.api-token }}
# We don't use `pants publish ::` because we manually rename the
# wheels after buildling them to add arch-specific tags.
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

There's a typo in the comment. 'buildling' should be 'building'.

Suggested change
# wheels after buildling them to add arch-specific tags.
# wheels after building them to add arch-specific tags.

Copilot uses AI. Check for mistakes.
Comment on lines +554 to +558
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
api_token=$(jq -r '.token' <<< "${resp}")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The jq command extracts the token without validating that the field exists or contains a non-empty value. If the API response doesn't contain the expected field or returns an error structure, jq will output 'null' or an empty string, which will then be used as the API token. Consider adding validation to check that the extracted values are non-empty before proceeding.

Suggested change
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
api_token=$(jq -r '.token' <<< "${resp}")
# validate that we successfully obtained an OIDC token
if [ -z "${oidc_token}" ] || [ "${oidc_token}" = "null" ]; then
echo "Error: failed to obtain OIDC token from GitHub OIDC endpoint." >&2
echo "Response was: ${resp}" >&2
exit 1
fi
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
api_token=$(jq -r '.token' <<< "${resp}")
# validate that we successfully obtained a PyPI API token
if [ -z "${api_token}" ] || [ "${api_token}" = "null" ]; then
echo "Error: failed to obtain PyPI API token from mint-token endpoint." >&2
echo "Response was: ${resp}" >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
Comment on lines +551 to +563
resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")
oidc_token=$(jq -r '.value' <<< "${resp}")
# exchange the OIDC token for an API token
resp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
api_token=$(jq -r '.token' <<< "${resp}")
# mask the newly minted API token, so that we don't accidentally leak it
echo "::add-mask::${api_token}"
# see the next step in the workflow for an example of using this step output
echo "api-token=${api_token}" >> "${GITHUB_OUTPUT}"
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The token minting script lacks error handling. If either curl request fails or returns an error response, the script will continue and potentially set an empty or invalid token. This could lead to silent failures or exposure of error messages in subsequent steps. Consider adding error checking after each curl command to verify the response is valid before proceeding.

Copilot uses AI. Check for mistakes.
@HyeockJinKim HyeockJinKim force-pushed the main branch 2 times, most recently from 9552aac to 4af738e Compare December 31, 2025 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip:changelog Make the action workflow to skip towncrier check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants