5.26 Verifying a Package

A consumer MUST perform the following steps, in this order, before installing anything from a package:

  1. Compute the SHA-256 of the downloaded .peipkg file.
  2. Compare it against the hash recorded in the repository index (§5.33). If they differ, the package is corrupted or substituted; abort.
  3. Verify the inline signature (§5.30). If verification fails, the package's authenticity is unproven; abort.
  4. Decompress and parse the tar archive, enforcing the layout rules of §5.12 and the determinism rules of §5.11.
  5. Read .peipkg/manifest.json and verify it against §5.18.
  6. Read .peipkg/files.json and verify it against §5.25, including the two-way coverage check and the size_installed equality of §5.18.
  7. For each payload entry, compute its content hash and compare it against the files manifest. If any file's hash does not match, abort.
  8. Compare the manifest against the index entry that led here (§5.32). If any field disagrees, abort.

A consumer MUST NOT install any payload before all eight steps complete successfully. Partial installation after a verification failure leaves the system indeterminate and is forbidden.

5.26.1 Ordering is logical, not temporal #

A consumer MAY compute the hashes for steps 1, 3, and 7 in a single streaming pass: feeding the compressed bytes simultaneously through a hasher and a decompressor, piping the decompressed bytes through a second hasher up to the signature entry, and hashing each file's content as the tar walk reaches it.

What the ordering requires is that no payload is committed to its final install path, and no decompressed byte is made visible outside the consumer's own private state, until every step has completed.

5.26.2 Nothing observable before step 3 #

A consumer MUST NOT make any decompressed payload byte visible to another process — including through a staging directory reachable from outside the consumer's own process tree — before signature verification has succeeded.

Streaming decompression and hashing are permitted. Observable filesystem effects are not.

5.26.3 Verifying the whole transaction first #

When a consumer installs several packages together, it MUST complete steps 1 through 8 for every package before extracting any package's payload, and it MUST do so across every installation root the operation touches.

5.26.4 Committing a payload #

A consumer MUST resolve every path component of an install location relative to a verified parent-directory file descriptor, without traversing any symbolic link — including one the consumer itself created earlier in the same operation. A resolution that would traverse a symlink MUST abort the operation.

A pre-existing symlink at an install path MUST be removed atomically before the write, and MUST NOT be followed.

A well-formed package never contains a payload entry whose ancestor component is a symlink. A resolution failure therefore indicates a malformed or hostile package, or hostile filesystem state.

On Linux this is achieved with openat2(..., RESOLVE_NO_SYMLINKS) anchored at the relevant permitted top-level destination (§5.14), or with equivalent semantics using O_NOFOLLOW on every component against a carried directory descriptor. A consumer SHOULD additionally apply RESOLVE_BENEATH, RESOLVE_NO_XDEV, and RESOLVE_NO_MAGICLINKS as defence in depth, and SHOULD commit a staged file with renameat2 against the same pinned parent descriptor rather than a re-walked path string.

Edit this page