Integrity
The integrity verifier checks that a downloaded artifact holds the bytes the build produced. Reach for it before you ship an archive you did not build yourself, or as a release gate in CI.
Quick start
npx @extension.dev/artifact-integrity \
--owner <workspace-slug> \
--repo <project-slug> \
--sha <short-sha> \
--browser chrome \
--require-digestThe verifier downloads the archive, runs six checks, writes artifact-integrity.json and exits 0 only when the bytes match a declared digest.
How it works
The verifier takes flags, not a URL. It covers Chrome, Firefox and Edge, and refuses Safari with Unsupported browser "safari".
Private projects need a token
The registry serves a project only when the project is declared public. A private project answers 401 to a caller with no token. Pass one with --token, or set EXTENSION_DEV_TOKEN in the environment:
npx @extension.dev/artifact-integrity \
--owner <workspace-slug> \
--repo <project-slug> \
--sha <short-sha> \
--browser chrome \
--require-digest \
--token "$EXTENSION_DEV_TOKEN"The token travels as a bearer header. The verifier refuses to send it over a URL that is not HTTPS. See Access tokens for minting one, and Public project pages for what "declared public" means.
The digest is the check that matters
package-integrity is the only check that compares bytes to a promise. The digest comes from --expected-sha256 first, then from the build's artifact manifest, then from the build metadata.
When none of the three carries a digest, the run still exits 0. It records package-integrity at level info and reports the SHA-256 it computed. That proves a downloadable, well-formed archive, not a matched one. Pass --require-digest to turn a missing digest into a failure, or --expected-sha256 <hex> to pin the bytes you expect. A digest you carry yourself is the only form that does not read the promise from the same place it reads the bytes.
The stored report
Each browser of a successful build writes one report. The console lists it as <browser>.integrity.json beside <browser>-extension.zip. The same record returns from the API for a caller with access to the project:
GET /api/projects/{id}/builds/{buildId}/integrity/{browser}The platform run is advisory. It records the result and never fails the build, so a release gate is yours to add.
As a CI release gate
The exit code is the gate. Run it with a digest requirement, or the gate passes on an archive nobody compared:
- run: |
npx @extension.dev/artifact-integrity \
--owner "$WORKSPACE_SLUG" \
--repo "$PROJECT_SLUG" \
--sha "$BUILD_SHA" \
--browser chrome \
--require-digestThe report holds no timestamps, so two runs over the same artifact write byte-identical JSON. You can commit it and diff it across runs.
Reference
Flags
| Flag | Required | What it does |
|---|---|---|
--owner | yes | The workspace slug |
--repo | yes | The project slug |
--sha | yes | The build's short sha |
--browser | yes | chrome, firefox or edge |
--require-digest | no | Fail when no digest is declared |
--expected-sha256 <hex> | no | Pin the digest the archive must match |
--token | no | Bearer token for a private project. EXTENSION_DEV_TOKEN does the same |
--out | no | Report path. Default artifact-integrity.json |
Checks, in order
| Check | Level | Passes when |
|---|---|---|
download-package | fail | The archive is reachable and downloads |
zip-structure | fail | The bytes parse as a zip |
manifest-present | fail | manifest.json sits at the root and declares manifest_version 2 or 3 |
download-metadata | fail | The build's <browser>.json metadata is reachable and valid JSON |
download-manifest | warn | The build's artifact-manifest/<browser>.json is reachable and valid JSON |
package-integrity | fail | The archive's SHA-256 matches a declared digest |
When download-manifest fails, the digest falls back to the build metadata and the exit code does not change. Firefox packages are .xpi, the others are .zip.
Exit codes
| Run | Exit |
|---|---|
| Digest declared and matching | 0 |
| Digest declared and different | 1 |
No digest, no --require-digest | 0, with package-integrity at info |
No digest, --require-digest set | 1 |
| Any of the first four checks failing | 1 |
Only download-manifest failing | 0, with the check at warn |
The API route
| Answer | Meaning |
|---|---|
200 with the report | The registry holds a report for that browser |
200 with null | The build produced no report for that browser |
502 REGISTRY_READ_REFUSED | The registry refused the read |
502 REGISTRY_READ_FAILED | The read failed for any other reason |
When it fails
Missing required flag: --owner. The verifier takes--owner,--repo,--shaand--browser. It has no positional argument and does not accept an artifact URL.Unsupported browser "safari". Chrome, Firefox and Edge only.download-packagefails on a project that used to verify. The registry answers401for a project that is not declared public. Pass--token, or declare the project public in Project settings, Visibility.- Exit
1onpackage-integrity. The bytes do not match the declared digest. Download once more to rule out truncation. Treat a second failure as an artifact that does not ship. - Exit
0and nothing feels checked. Read thepackage-integritylevel in the report.infomeans no digest was declared. Add--require-digest. integrity/{browser}returnsnull. The registry holds no report for that browser. A read the registry refused answers502instead. Check which browsers the build produced withGET /api/projects/{id}/builds/{buildId}. Each browser there carries anintegrityUrlyou can read with your token. The verifier reads the artifacts, not this route, so it answers either way.