What package lockfile attestations must be verified before executing a transitive dependency?

**Date of Record:** 2026 October 26 **Scope:** Applies when an agentic session installs or executes external package dependencies (npm, PyPI, Maven, Cargo) via a lockfile, and must verify that the transitive dependency tree has not been tampered with or substituted. Does not apply to strictly standard-library or vendored dependencies with no external resolution.

Core Distinction

The separation between **declared dependencies** (the top-level packages listed in the project manifest, e.g., `package.json`) and **lockfile integrity attestations** (the cryptographic hashes binding every transitive dependency to its exact version, source registry, and integrity checksum). Failing to distinguish these creates the **Lockfile Drift Hazard**: an agent trusts the top-level manifest, but an attacker silently injects a malicious transitive dependency into the resolution tree, which the lockfile hash verification misses because the lockfile itself was overwritten.

What Was Observed

In automated CI/CD and agent dependency installation, failing to verify lockfile attestations produces three primary failure modes:

  1. **Dependency Substitution:** An attacker publishes a malicious package with the same name and version but a different tarball hash. The lockfile is not verified against the registry's integrity endpoint, so the malicious tarball installs.
  2. **Lockfile Tampering:** An attacker modifies the lockfile itself to point a transitive dependency to a malicious URL or a different hash. The agent installs it because the lockfile signature was not verified.
  3. **Registry Compromise:** The package registry itself is compromised, and a malicious transitive dependency is published under a legitimate maintainer's account. The lockfile hash matches, but the upstream artifact has been poisoned.

Recorded Elements

  1. **`lockfile_signature_hash`:** The cryptographic hash of the lockfile itself (e.g., `package-lock.json`), proving it has not been modified since generation.
  2. **`registry_integrity_receipt`:** The cryptographic proof that every dependency's tarball hash matches the registry's signed integrity endpoint (e.g., Sigstore or Rekor attestations).
  3. **`transitive_closure_hash`:** The flattened, deterministic hash of the entire resolved dependency tree, enabling the successor to detect drift between installations.
  4. **`provenance_attestation_chain`:** The chain of SLSA provenance or equivalent attestations proving the dependency was built by a trusted builder.

Condition of Validity

* **Applies when:** The project relies on external package registries (npm, PyPI, etc.) and the lockfile is the sole source of truth for version resolution, and the agent lacks a hermetic, reproducible build environment. * **Does not apply when:** The project vendors all dependencies in-tree with a pinned hash, or the build is fully hermetic (e.g., Nix with cryptographic store paths).

Decision Boundary

  1. **Is the lockfile present and signed?** If no, the dependency resolution is untrusted. The instance must halt installation.
  2. **Is the `transitive_closure_hash` verified against the expected tree?** If no, drift has occurred. The instance must re-lock and validate.
  3. **Are the `registry_integrity_receipt` and `provenance_attestation_chain` verified for every transitive dependency?** If no, the dependency is untrusted. The instance must `HALT_AND_AUDIT`.
  4. **Conversely, when can an agent proceed without full provenance attestations?** Only when installing dependencies from an internally mirrored, air-gapped registry with its own signed integrity chain, or when installing dependencies that are strictly development-time only and never touch production.

Operational Effect

  1. **Supply Chain Immunity:** Prevents attackers from injecting malicious transitive dependencies by verifying the lockfile against the registry's signed integrity and provenance chain.
  2. **Drift Detection:** Detects silent lockfile modifications by verifying the transitive closure hash before installation.
  3. **Build Reproducibility:** Ensures the exact dependency tree the predecessor validated is the exact tree the successor will execute.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class LockfileVerdict(Enum):
    VERIFIED_CLOSURE = auto()
    LOCKFILE_TAMPERED = auto()
    UNVERIFIED_PROVENANCE = auto()
    MISSING_INTEGRITY = auto()

@dataclass(frozen=True)
class LockfileState:
    lockfile_hash: str
    expected_lockfile_signature: str
    transitive_closure_hash: str
    has_registry_integrity_receipts: bool

@dataclass(frozen=True)
class VerificationPolicy:
    require_registry_integrity: bool

@dataclass(frozen=True)
class LockfileManifest:
    verdict: LockfileVerdict
    directive: str
    manifest_digest: str

class LockfileAttestationGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_lockfile(
        cls, state: LockfileState, policy: VerificationPolicy
    ) -> LockfileManifest:
        
        # Gate 1: Lockfile signature integrity
        if state.lockfile_hash != state.expected_lockfile_signature:
            return LockfileManifest(
                verdict=LockfileVerdict.LOCKFILE_TAMPERED,
                directive="HALT: Lockfile hash does not match expected signature.",
                manifest_digest=cls._hash("TAMPERED")
            )

        # Gate 2: Registry integrity receipts
        if policy.require_registry_integrity and not state.has_registry_integrity_receipts:
            return LockfileManifest(
                verdict=LockfileVerdict.MISSING_INTEGRITY,
                directive="HALT: Registry integrity receipts missing. Cannot verify transitive dependencies.",
                manifest_digest=cls._hash("NO_INTEGRITY")
            )

        # Gate 3: Transitive closure hash (drift detection)
        if not state.transitive_closure_hash.strip():
            return LockfileManifest(
                verdict=LockfileVerdict.UNVERIFIED_PROVENANCE,
                directive="HALT: Transitive closure hash missing. Cannot detect drift.",
                manifest_digest=cls._hash("NO_CLOSURE")
            )

        # Gate 4: Verified
        digest = cls._hash(json.dumps({
            "lockfile": state.lockfile_hash,
            "closure": state.transitive_closure_hash,
            "integrity": str(state.has_registry_integrity_receipts)
        }, sort_keys=True))
        
        return LockfileManifest(
            verdict=LockfileVerdict.VERIFIED_CLOSURE,
            directive="PROCEED: Lockfile, transitive closure, and provenance verified.",
            manifest_digest=digest
        )