Package Provenance Attestation
Pricing
from $15.00 / 1,000 run_starteds
Package Provenance Attestation
Prove that a published npm or PyPI artifact carries a signed provenance attestation, that the attestation subject digest equals the registry artifact digest, and that the public transparency log holds the entry.
Pricing
from $15.00 / 1,000 run_starteds
Rating
0.0
(0)
Developer
kingii98
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Package Provenance Attestation and Transparency-Log Verification Gate
Give this Actor the package addresses of a lockfile change. It reads the public npm and PyPI registries, and returns one row for each package: whether a signed provenance attestation exists, the digest the registry publishes for the artifact, the digest the attestation signs, whether the two are the same, the signer identity out of the signing certificate, the source repository and commit, and the public transparency-log entry that holds the record.
A package manifest flag that says provenance: true is a statement about
metadata. This Actor answers a different question: is the artifact in the
registry today the artifact that the named build produced?
Who this is for
A platform engineer or an application-security engineer who promotes third-party packages into a build, and who must show an auditor that each dependency comes from a named source repository and a named workflow. Run it on each lockfile change, and on a schedule, because a transparency-log entry or a registry dist record can change after the first install.
What the Actor does
- Reads 1 to 500 package addresses, each written as
ecosystem:name@version. - Reads the registry record of the published artifact, and takes the digest the registry publishes for it.
- Calls the registry attestation endpoint for the same artifact.
- Decodes the in-toto statement inside the attestation bundle, and takes the subject name and the subject digest.
- Compares the two digests. This is the gate.
- Reads the Sigstore (Fulcio) signing certificate in the bundle, and takes the OIDC issuer, the workflow path, the workflow ref, the runner environment, the source repository and the source commit.
- Resolves the transparency-log index of the bundle against the public Rekor log, and records the entry identifier and the log timestamp.
- Applies the gate rules, and writes one dataset row for each package address and one run-summary record.
The Actor makes HTTP GET calls to three fixed public hosts. It uses no account, no cookie, no key, no browser, no proxy, no language model and no paid API.
Endpoints
| Host | Purpose |
|---|---|
registry.npmjs.org | "/{name}/{version}" for the dist record, and /-/npm/v1/attestations/{name}@{version} for the attestation bundle. |
pypi.org | /pypi/{name}/{version}/json for the file record, and /integrity/{name}/{version}/{filename}/provenance for the attestation bundle. |
rekor.sigstore.dev | /api/v1/log/entries?logIndex={index} to resolve the transparency-log entry. |
No buyer-supplied URL is fetched. A package address is split and validated before it reaches a URL, so a package string cannot steer a request at another host.
Input
| Field | Type | Default | Meaning |
|---|---|---|---|
packages | array of strings | four-package sample set | 1 to 500 addresses, each ecosystem:name@version. |
requireAttestation | boolean | true | A package with no attestation counts as a gate failure. |
allowedSourceRepos | array of strings | [] | Optional owner/repo allow list. An attestation from a repository outside a non-empty list is a gate failure. |
pypiArtifact | string | sdist | Which PyPI release file to verify: sdist or wheel. |
Example:
{"packages": ["npm:sigstore@2.3.1","npm:@sigstore/bundle@2.3.2","pypi:sigstore@3.6.5"],"requireAttestation": true,"allowedSourceRepos": ["sigstore/sigstore-js", "sigstore/sigstore-python"],"pypiArtifact": "sdist"}
Package addresses
- npm:
npm:sigstore@2.3.1, and a scoped name asnpm:@sigstore/bundle@2.3.2. - PyPI:
pypi:sigstore@3.6.5. The project name is normalized as PEP 503 states, sopypi:Zope.Interface@5.5.2reads the same project aspypi:zope-interface@5.5.2. node,nodejs,py,pipandpythonare accepted as ecosystem names.- A malformed address is not an error that stops the run. It becomes a row
with the verdict
errorand a reason. - Two input rows that name the same package share one lookup, and share one
package_verifiedcharge. Both rows still appear in the dataset.
The PyPI file
PyPI attests each release file on its own, and a release can hold many wheels.
The run verifies one file, and the row names it in artifactFilename. The
pypiArtifact field chooses the kind; the other kind answers when the
preferred kind is absent.
Output
One dataset row for each package address, plus one run-summary record.
| Field | Meaning |
|---|---|
recordType | package or summary. |
inputSpec, ecosystem, package, version | The address as given, and its parts. |
artifactFilename, artifactUrl | The published file this row checked. |
attestationPresent | Whether the registry returned an attestation bundle. |
attestationDeclaredByRegistry | Whether the registry record itself claims an attestation. A true here with attestationPresent: false is the metadata-flag gap. The value is null for a PyPI file, because the PyPI file record does not always carry the provenance link even when a bundle exists. |
predicateType | The attestation predicate, for example https://slsa.dev/provenance/v1. |
registryDigestAlgorithm, registryArtifactDigest | The digest the registry publishes (npm: sha512 from the dist integrity; PyPI: sha256 from the file record). |
attestationSubjectName, attestationSubjectDigest | The subject the attestation signs. |
digestMatch | pass, fail or not_checked. |
signerIdentity, signerOidcIssuer, signerWorkflowPath, signerWorkflowRef, runnerEnvironment | Read out of the Fulcio signing certificate. |
builderId, publisher | The builder the statement names, and the publisher PyPI names. |
sourceRepo, sourceRepoUrl, sourceCommitSha | The build source. |
repoAllowed | true, false, or null when no allow list is set. |
transparencyLogEntryId, transparencyLogIndex, transparencyLogTimestamp, transparencyLogUrl, transparencyLogStatus | The public log record. |
verdict | pass, fail, not_attested or error. |
failureReason | Why the row is not a pass. Empty on a pass. |
gateFailure | Whether this row breaks the gate under the run's settings. |
The summary record carries gateVerdict (PASS or FAIL), checkedAt, and
the counts: packageCount, uniquePackageCount, verifiedCount,
passCount, failCount, notAttestedCount, errorCount, invalidCount,
gateFailureCount, digestMismatchCount, repoRejectedCount,
logEntriesResolved and registryCalls.
Verdict rules
| Verdict | When |
|---|---|
pass | An attestation exists, its subject digest equals the registry artifact digest, its subject names this artifact, the source repository is allowed, and the public log holds the entry. |
fail | The digest differs, the two digests cannot be compared, the subject names another artifact, the source repository is outside the allow list, or the public log holds no entry at the index the bundle states. |
not_attested | The registry publishes no attestation for the artifact. |
error | The address is malformed, or the registry record could not be read. |
gateFailure applies the run's policy to the verdict: a fail and an error
always break the gate; a not_attested breaks the gate only when
requireAttestation is true. A transparency-log lookup that fails on the
network (transparencyLogStatus: ERROR) is recorded, and does not change the
verdict; a log that answers and holds no entry does.
A failed gate is a result, not a malfunction. The run always ends with the status SUCCEEDED, and the verdict is in the dataset and in the run status message. Reserve a failed run for a real malfunction.
What this Actor does not do
- It does not check the signature arithmetic, and it does not walk the certificate chain to a trust root. It checks the binding the buyer cannot see from the registry page: attestation exists, subject digest equals the published digest, the named signer, and the public log entry.
- It does not download the artifact, so it does not recompute the digest from the file bytes. It compares the digest the registry publishes with the digest the attestation signs.
- It reads no private registry, and takes no credential.
Pricing (pay per event)
| Event | Unit | Price | Counted |
|---|---|---|---|
run_started | one run | $0.015 | Once, after the input is accepted. |
package_verified | one package | $0.008 | Once for each unique package address the Actor sent to a registry. A malformed address is never charged, and a duplicate address is charged once. |
transparency_log_entry_resolved | one attestation | $0.004 | Once for each package whose attestation carried a log index that the public log answered. A package with no attestation never reaches a log lookup. |
A 500-package lockfile in which every package is attested costs
0.015 + 500 × 0.008 + 500 × 0.004 = $6.02.
Bounds
| Bound | Value |
|---|---|
| Packages for each run | 500 |
| Package address length | 300 characters |
| Allowed repositories | 200 |
| HTTP requests in flight | 6 |
| Request timeout | 25 s |
| Response body | 8 MB |
| Certificate body | 64 KB |
Local use
uv syncuv run pytestuv run ruff check .apify run --input-file .actor/default_input.json
Default input
.actor/default_input.json holds four stable public packages: two that carry
a provenance attestation, and two that do not. The default run therefore ends
with the gate verdict FAIL and the status SUCCEEDED, which is what a gate
with requireAttestation: true is supposed to report for an unattested
dependency. The fixture completes well inside five minutes.


