Skip to content

Conalh/cpan-integ

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cpan-integ

ci

Consumer-side, install-time artifact-hash verification for CPAN.

CPAN workflows can pin versions without pinning bytes. Carton's cpanfile.snapshot records the resolved dependency tree and exact versions but no artifact digests; cpm consumes a snapshot but does not appear to provide a lockfile-integrity mode equivalent to pip's hash-checking or npm's lockfile integrity field; cpanm --verify is off by default and does not verify the integrity of the CHECKSUMS file itself. cpan-integ fills the consumer side: you already resolved your dependency graph — this records the SHA-256 of each distribution's bytes in a committed lockfile and fails the build if a fetched artifact differs from what was pinned.

Status: independent experimental prototype, not a CPANSec-endorsed tool.

Threat model

cpan-integ is designed to catch drift and tampering of artifact bytes between pin time and install time:

  • a mirror or CDN serving different bytes than were originally resolved
  • a re-uploaded / mutated distribution at the same version
  • accidental corruption in transit or in a local cache
  • an unexpected distribution being introduced into the install set (with verify --snapshot)

It is not a registry-compromise defense and not an identity/provenance system. If an attacker controls the source at pin time, they control the hash you trust on first pin. That is the same trust boundary as every lockfile.

Trust model

Trust-on-first-pin, cryptographically verified thereafter — the same class of control as pip's hash mode and npm's lockfile integrity field.

  • pin downloads each artifact, computes its SHA-256 from the actual bytes, and cross-checks that against MetaCPAN's published checksum_sha256 when available. It aborts the pin on disagreement. The locally computed hash is recorded as authoritative. MetaCPAN is raw material and a cross-check, not an independent trusted third party — it provides both the artifact location and the checksum.
  • The committed lockfile becomes the trust source after first pin. Every verify afterward is a pure cryptographic comparison.

Quickstart

# Record hashes for everything in a Carton snapshot:
cpan-integ pin --snapshot cpanfile.snapshot --out cpanfile.integrity

# Commit cpanfile.integrity, then in CI / before deploy:
cpan-integ verify --integrity cpanfile.integrity

# Stronger: also assert the lock describes exactly the snapshot's artifacts:
cpan-integ verify --integrity cpanfile.integrity --snapshot cpanfile.snapshot

# Build a complete verified local mirror (artifacts + 02packages index):
cpan-integ fetch --integrity cpanfile.integrity --snapshot cpanfile.snapshot --cache cpan-integ-cache

# Install from the verified mirror only (cpanm resolves names via the generated index):
cpanm --mirror "$PWD/cpan-integ-cache" --mirror-only --installdeps .

verify exits non-zero on any mismatch or inconsistency, so it drops straight into a CI step.

Sample lockfile

Line-based and diff-friendly; each distribution is identified by its Package URL (ECMA-427) CPAN form, with the distfile URL recorded as artifact metadata:

# cpanfile.integrity - generated by cpan-integ 0.002
# format: <purl> sha256:<hex of locally-hashed bytes> <download_url>
pkg:cpan/ETHER/Try-Tiny@0.32 sha256:ef2d6cab...fa7fc0 https://cpan.metacpan.org/authors/id/E/ET/ETHER/Try-Tiny-0.32.tar.gz

Tamper test transcript

$ cpan-integ verify --integrity cpanfile.tampered.integrity
FAIL  pkg:cpan/ETHER/Try-Tiny@0.32
        expected 000000000bad18e3ab1c4e6125cc5f695c7e459899f512451c8fa3ef83fa7fc0
        got      ef2d6cab0bad18e3ab1c4e6125cc5f695c7e459899f512451c8fa3ef83fa7fc0
ok    pkg:cpan/DAGOLDEN/Capture-Tiny@0.50
ok    pkg:cpan/DAGOLDEN/Path-Tiny@0.150

cpan-integ: 2 verified, 1 failed, 3 total
$ echo $?
1

Non-goals

  • Not a replacement for cpm, Carton, cpanm, or CPAN.pm.
  • Not a dependency resolver — it constrains only artifacts already present in your resolved snapshot, and never introduces new dependencies.
  • Not a signing/provenance system. Sigstore/transparency-log verification is a possible future cross-check, not the core.

Limitations

  • Install path: verified mirror validated in CI. fetch --snapshot materializes a complete local mirror with verified authors/id/... artifacts and a PAUSE-format modules/02packages.details.txt.gz index. The CI e2e job installs from that mirror with cpanm --mirror-only and asserts the installed module loads. This proves the core path: install the exact bytes that were pinned and verified.
  • CPAN-only. Non-CPAN snapshot sources (git/url/darkpan) are rejected unless --allow-nonstandard is given, in which case they are skipped, not verified.
  • Network required for pin and verify.

Roadmap

  • Phase 0 (done): sidecar lockfile, local-bytes hashing + MetaCPAN cross-check, snapshot/lock reconciliation, tests.
  • Phase 1 (done): richer verify constraints (missing/extra/nonstandard).
  • Phase 2 (done): preflight→install gap closed. fetch --snapshot materializes a verified authors/id/... mirror + 02packages index; the CI e2e job installs from it with cpanm --mirror-only and asserts the module loads.
  • Phase 3 (optional, deferred): verify Sigstore bundles as a stronger provenance signal, subject to an explicit identity/issuer policy.

Dependencies

Minimal dependency footprint: parsing, hashing, and lockfile handling use core modules where possible. Live HTTPS fetching requires IO::Socket::SSL and Net::SSLeay, matching HTTP::Tiny's TLS requirements — the goal is to avoid nonessential dependencies, not to pretend TLS transport is dependency-free. The PURL identity is format-compatible with URI::PackageURL (not a dependency).

Tests

# offline unit tests (parse / lockfile / reconcile / index):
prove -lv t/01-snapshot.t t/02-lockfile.t t/03-reconcile.t t/04-index.t

# live network test (pin / verify / fetch / tamper against real CPAN):
CPAN_INTEG_LIVE=1 perl -Ilib t/99-live.t

End-to-end — build a verified mirror, install from it, and confirm the module loads (the shape the CI e2e job runs):

perl -Ilib bin/cpan-integ pin \
  --snapshot examples/cpanfile.snapshot \
  --out cpanfile.integrity

perl -Ilib bin/cpan-integ fetch \
  --integrity cpanfile.integrity \
  --snapshot examples/cpanfile.snapshot \
  --cache mirror

cpanm --mirror "$PWD/mirror" --mirror-only --notest \
  --local-lib-contained "$PWD/local-lib" Try::Tiny

perl -I"$PWD/local-lib/lib/perl5" -MTry::Tiny -e 'print Try::Tiny->VERSION, "\n"'

License

Same terms as Perl itself (Artistic 1.0 / GPL 1.0+); see LICENSE.

About

Consumer-side install-time artifact-hash verification for CPAN distributions (experimental)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages