Skip to content

Add circleci to pypi attestations#166

Merged
woodruffw merged 11 commits intopypi:mainfrom
meeech:add-circleci-to-pypi-attestations
Mar 3, 2026
Merged

Add circleci to pypi attestations#166
woodruffw merged 11 commits intopypi:mainfrom
meeech:add-circleci-to-pypi-attestations

Conversation

@meeech
Copy link
Copy Markdown
Contributor

@meeech meeech commented Jan 30, 2026

Summary

In an effort to add CircleCI as a Trusted Publisher for pypi/warehouse, we need to add a CCI Publisher for attestations.

  • note: to test properly in a live env requires id>=1.6.0. 1.6.0 was yanked (unrelated reason), but can still be installed if testing this before 1.6.1 is released. 1.6.1 is released so we good here. Do we need to bump up the required id package? Since the latest one will be required to work with cci once this is merged?

Help needed

I'm uncertain about some of the choices I've made here.
I will comment inline where these open questions I have are.
In general I would refer back to gh/gl patterns to help guide this work.

I would need someone who knows this specific project/domain a bit better to give some guidance.

Open to a regular PR review.
But I am also open to a sync review (on the pypa discord?) to walk through this together to speed things up (and allow quick follow up questions where its not clear to me).

related pr:
pypi/warehouse#19349 - warehouse - add circleci as a trusted publisher
meeech/warehouse#1 (stacked off of ☝🏼 )
di/id#438 - updated the oidc issuer when getting ambient id

Checklist

(sorry if I should not be bumping the version? lmk I'll remove it)

Making a release

  • Bump the version in src/pypi_attestations/__init__.py
  • Add a new subheading in the CHANGELOG for the new version
  • Add a link at the bottom of the CHANGELOG for the diff of the new version

f"Verification failed: provenance was signed by service account "
f'"{publisher.email}", expected "{args.gcp_service_account}"'
)
elif isinstance(publisher, CircleCIPublisher):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

tbh - not entirely clear how to test this properly. (help wanted)

In examining the payload from other packages, I can see the publisher comes as part of the call https://pypi.org/integrity/package/version/filename/provenance but not clear what I should be validating against? Would it be enough to just use one of the claims? eg: the project id?

note to self: this value thats being checked maps to the values of attestation_identity. eg: https://github.com/pypi/warehouse/blob/5b980b74b3eee5e79580dc4672abad3d93f14472/warehouse/oidc/models/github.py#L306

I guess what I am wondering about is whats needed here to verify? Like is just the project_id enough? or would i want to use the pipeline_id as well? Unless I misunderstand, after this stage there should be further certificate validation happening of the fulcio cert?

Any guidance here is welcome :D

Copy link
Copy Markdown
Contributor Author

@meeech meeech Feb 4, 2026

Choose a reason for hiding this comment

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

You can see what I did for warehouse here - meeech/warehouse#1

This is a pr stacked off my pr for adding circleci as a trusted publisher (it wouldnt let me open a pr on the parent against a pr)

@meeech meeech marked this pull request as ready for review February 4, 2026 03:09
@woodruffw
Copy link
Copy Markdown
Member

Thanks @meeech! I'll try and give this a review in the next day or so.

@meeech
Copy link
Copy Markdown
Contributor Author

meeech commented Feb 5, 2026

update:
Works locally (huzzah!)

  • Need to beef up the Provenance with some more links.
  • Unsure if thats a pypi-attestations or warehouse implementation detail (or both - will dig in)

@woodruffw lmk if you want me to move this back to draft, or hold till i've figured that bit out?

image

@meeech
Copy link
Copy Markdown
Contributor Author

meeech commented Feb 5, 2026

image

@meeech
Copy link
Copy Markdown
Contributor Author

meeech commented Feb 7, 2026

@woodruffw update: i beefed up the provenance display in warehouse. in the end, didnt have to touch this pr any further, so it's ready for review whenever you are

meeech and others added 10 commits February 15, 2026 11:14
- Add _CircleCITrustedPublisherPolicy to verify certificates against CircleCI OIDC issuer
- Verify organization_id via OIDC issuer (https://oidc.circleci.com/org/<org_id>)
- Verify project_id via Build Signer URI extension
- Add optional repository field to verify source repository URI (oidc.circleci.com/vcs-origin)
- Update CLI to handle CircleCIPublisher verification
- Export CircleCIPublisher from package __init__.py
- Add tests for discriminator and wrong kind validation

Amp-Thread-ID: https://ampcode.com/threads/T-019c0ccd-f336-72fb-ae44-77b840fb92aa
Co-authored-by: Amp <amp@ampcode.com>
…ields

Required fields:
- organization_id: verified via OIDC issuer
- project_id: verified via Build Signer URI
- pipeline_definition_id: verified via Build Signer URI (exact match)

Optional fields:
- context_id: CircleCI context ID from oidc.circleci.com/context-ids
- vcs_origin: source repository from oidc.circleci.com/vcs-origin
- vcs_ref: git ref from oidc.circleci.com/vcs-ref

These fields align with PyPI's trusted publisher requirements for CircleCI.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0ccd-f336-72fb-ae44-77b840fb92aa
Co-authored-by: Amp <amp@ampcode.com>
context_id cannot be verified via attestation since Fulcio does not map
oidc.circleci.com/context-ids to a certificate extension. This field
belongs in warehouse's OIDCPublisher model for trusted publishing
verification, not in pypi-attestations for attestation verification.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0ccd-f336-72fb-ae44-77b840fb92aa
Co-authored-by: Amp <amp@ampcode.com>
CircleCI is updating their OIDC issuer to no longer include the
organization ID in the path. Since org-id is not mapped to a Fulcio
certificate extension, it cannot be verified via attestation and
is removed from CircleCIPublisher.

Required fields now:
- project_id
- pipeline_definition_id

Optional fields:
- vcs_origin
- vcs_ref

Amp-Thread-ID: https://ampcode.com/threads/T-019c0ccd-f336-72fb-ae44-77b840fb92aa
Co-authored-by: Amp <amp@ampcode.com>
When oidc.circleci.com/ssh-rerun is true, the runner_environment cert
extension is set to 'ssh-rerun'. These jobs are not trusted for
publishing as they indicate manual SSH access to the CI environment.

This provides defense-in-depth alongside PyPI's trusted publishing
check, ensuring attestations from SSH rerun jobs are rejected during
both upload and downstream verification.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0ccd-f336-72fb-ae44-77b840fb92aa
Co-authored-by: Amp <amp@ampcode.com>
Add --circleci-project-id and --circleci-pipeline-definition-id flags
to verify provenance from CircleCI publishers. The CLI now validates
that the provenance publisher's project_id and pipeline_definition_id
match the expected values provided via flags.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0ccd-f336-72fb-ae44-77b840fb92aa
Co-authored-by: Amp <amp@ampcode.com>
leave that as a concern of pypi warehouse to reject any tp with this value
(and remove a useless comment)
@meeech meeech force-pushed the add-circleci-to-pypi-attestations branch from 164f43c to 236682c Compare February 15, 2026 16:15
@meeech
Copy link
Copy Markdown
Contributor Author

meeech commented Feb 15, 2026

sorry just noticed the fail on the lint and tests. will fix that, also will enable the actions in my repo

Add tests for _CircleCITrustedPublisherPolicy and CircleCIPublisher in
test_impl.py covering policy verification, optional vcs claims, and
failure paths for wrong build signer URI and issuer.

Add CLI tests in test_cli.py for the verify pypi CircleCI branch
covering missing project ID, missing pipeline definition ID, wrong
project ID, wrong pipeline definition ID, and the happy path.

Include real CircleCI-signed test assets: wheel, attestation,
provenance, and extracted certificate PEM.

Amp-Thread-ID: https://ampcode.com/threads/T-019c6230-33ea-767c-aa18-424c9eebb577
Co-authored-by: Amp <amp@ampcode.com>
@meeech
Copy link
Copy Markdown
Contributor Author

meeech commented Feb 15, 2026

Ok, so the test cov issue should be resolved now. Sorry about the confusion. I got tripped up on the online tests. Tested with a fork local pr, and now looked good.

@meeech meeech requested a review from di February 17, 2026 14:40
@meeech
Copy link
Copy Markdown
Contributor Author

meeech commented Mar 2, 2026

@di @woodruffw I think this is ready for review? Or would you rather wait till first pr for warehouse is done?

Copy link
Copy Markdown
Member

@di di left a comment

Choose a reason for hiding this comment

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

Seems fine to me, will let @woodruffw review as well.

@di di requested a review from woodruffw March 3, 2026 15:11
Copy link
Copy Markdown
Member

@woodruffw woodruffw left a comment

Choose a reason for hiding this comment

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

Thanks @meeech, LGTM!

@woodruffw woodruffw merged commit b292e0e into pypi:main Mar 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants