Skip to content

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 ​

bash
npx @extension.dev/artifact-integrity \
  --owner <workspace-slug> \
  --repo <project-slug> \
  --sha <short-sha> \
  --browser chrome \
  --require-digest

The 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:

bash
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:

yaml
- run: |
    npx @extension.dev/artifact-integrity \
      --owner "$WORKSPACE_SLUG" \
      --repo "$PROJECT_SLUG" \
      --sha "$BUILD_SHA" \
      --browser chrome \
      --require-digest

The 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 ​

FlagRequiredWhat it does
--owneryesThe workspace slug
--repoyesThe project slug
--shayesThe build's short sha
--browseryeschrome, firefox or edge
--require-digestnoFail when no digest is declared
--expected-sha256 <hex>noPin the digest the archive must match
--tokennoBearer token for a private project. EXTENSION_DEV_TOKEN does the same
--outnoReport path. Default artifact-integrity.json

Checks, in order ​

CheckLevelPasses when
download-packagefailThe archive is reachable and downloads
zip-structurefailThe bytes parse as a zip
manifest-presentfailmanifest.json sits at the root and declares manifest_version 2 or 3
download-metadatafailThe build's <browser>.json metadata is reachable and valid JSON
download-manifestwarnThe build's artifact-manifest/<browser>.json is reachable and valid JSON
package-integrityfailThe 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 ​

RunExit
Digest declared and matching0
Digest declared and different1
No digest, no --require-digest0, with package-integrity at info
No digest, --require-digest set1
Any of the first four checks failing1
Only download-manifest failing0, with the check at warn

The API route ​

AnswerMeaning
200 with the reportThe registry holds a report for that browser
200 with nullThe build produced no report for that browser
502 REGISTRY_READ_REFUSEDThe registry refused the read
502 REGISTRY_READ_FAILEDThe read failed for any other reason

When it fails ​

  • Missing required flag: --owner. The verifier takes --owner, --repo, --sha and --browser. It has no positional argument and does not accept an artifact URL.
  • Unsupported browser "safari". Chrome, Firefox and Edge only.
  • download-package fails on a project that used to verify. The registry answers 401 for a project that is not declared public. Pass --token, or declare the project public in Project settings, Visibility.
  • Exit 1 on package-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 0 and nothing feels checked. Read the package-integrity level in the report. info means no digest was declared. Add --require-digest.
  • integrity/{browser} returns null. The registry holds no report for that browser. A read the registry refused answers 502 instead. Check which browsers the build produced with GET /api/projects/{id}/builds/{buildId}. Each browser there carries an integrityUrl you can read with your token. The verifier reads the artifacts, not this route, so it answers either way.

Next ​