Package Provenance Attestation avatar

Package Provenance Attestation

Pricing

from $15.00 / 1,000 run_starteds

Go to Apify Store
Package Provenance Attestation

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

kingii98

Maintained by Community

Actor 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

  1. Reads 1 to 500 package addresses, each written as ecosystem:name@version.
  2. Reads the registry record of the published artifact, and takes the digest the registry publishes for it.
  3. Calls the registry attestation endpoint for the same artifact.
  4. Decodes the in-toto statement inside the attestation bundle, and takes the subject name and the subject digest.
  5. Compares the two digests. This is the gate.
  6. 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.
  7. Resolves the transparency-log index of the bundle against the public Rekor log, and records the entry identifier and the log timestamp.
  8. 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

HostPurpose
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

FieldTypeDefaultMeaning
packagesarray of stringsfour-package sample set1 to 500 addresses, each ecosystem:name@version.
requireAttestationbooleantrueA package with no attestation counts as a gate failure.
allowedSourceReposarray of strings[]Optional owner/repo allow list. An attestation from a repository outside a non-empty list is a gate failure.
pypiArtifactstringsdistWhich 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 as npm:@sigstore/bundle@2.3.2.
  • PyPI: pypi:sigstore@3.6.5. The project name is normalized as PEP 503 states, so pypi:Zope.Interface@5.5.2 reads the same project as pypi:zope-interface@5.5.2.
  • node, nodejs, py, pip and python are accepted as ecosystem names.
  • A malformed address is not an error that stops the run. It becomes a row with the verdict error and a reason.
  • Two input rows that name the same package share one lookup, and share one package_verified charge. 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.

FieldMeaning
recordTypepackage or summary.
inputSpec, ecosystem, package, versionThe address as given, and its parts.
artifactFilename, artifactUrlThe published file this row checked.
attestationPresentWhether the registry returned an attestation bundle.
attestationDeclaredByRegistryWhether 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.
predicateTypeThe attestation predicate, for example https://slsa.dev/provenance/v1.
registryDigestAlgorithm, registryArtifactDigestThe digest the registry publishes (npm: sha512 from the dist integrity; PyPI: sha256 from the file record).
attestationSubjectName, attestationSubjectDigestThe subject the attestation signs.
digestMatchpass, fail or not_checked.
signerIdentity, signerOidcIssuer, signerWorkflowPath, signerWorkflowRef, runnerEnvironmentRead out of the Fulcio signing certificate.
builderId, publisherThe builder the statement names, and the publisher PyPI names.
sourceRepo, sourceRepoUrl, sourceCommitShaThe build source.
repoAllowedtrue, false, or null when no allow list is set.
transparencyLogEntryId, transparencyLogIndex, transparencyLogTimestamp, transparencyLogUrl, transparencyLogStatusThe public log record.
verdictpass, fail, not_attested or error.
failureReasonWhy the row is not a pass. Empty on a pass.
gateFailureWhether 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

VerdictWhen
passAn 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.
failThe 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_attestedThe registry publishes no attestation for the artifact.
errorThe 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)

EventUnitPriceCounted
run_startedone run$0.015Once, after the input is accepted.
package_verifiedone package$0.008Once 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_resolvedone attestation$0.004Once 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

BoundValue
Packages for each run500
Package address length300 characters
Allowed repositories200
HTTP requests in flight6
Request timeout25 s
Response body8 MB
Certificate body64 KB

Local use

uv sync
uv run pytest
uv 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.