3.1 KiB
Recipient policies and selective reencryption
RecipientPolicyManager implements pass init inside crates/storage. It
accepts the storage-owned InitRequest, so root initialization and --path /
-p initialization share the same validated relative-path rules as every
other repository operation. One empty identity removes the policy at that
exact directory; any other identity list is written one item per line with a
final newline, matching upstream pass.
Hierarchy
An entry inherits the nearest .gpg-id in its directory or an ancestor.
Nested files override their parent for the complete nested subtree. Exact
fingerprints, key IDs, and user IDs are resolved by KeyStore; comments,
whitespace, duplicates, missing identities, and ambiguous identities use the
same rules as encryption elsewhere in storage.
Changing a policy considers only entries below that directory which are not shielded by a deeper override. Removing an override makes those entries inherit the nearest policy above it. Removal is rejected before mutation if an affected entry would have no policy. Existing public-key session packets are compared to the resolved certificate set, so entries already encrypted for exactly the requested recipients are not needlessly rewritten.
As upstream does, removing .gpg-id does not remove a pre-existing detached
.gpg-id.sig; without a configured signing-key policy the orphan is ignored.
Reinitializing with signing enabled replaces it.
Authentication
When PASSWORD_STORE_SIGNING_KEY behavior is configured, SigningPolicy
contains the key used to create a detached signature and the explicit set of
trusted primary fingerprints. A new .gpg-id is signed and immediately
verified against that set before filesystem mutation. Inherited policies must
have a signature which verifies against the same trust set. Missing, malformed,
untrusted, and cryptographically invalid signatures are distinct pre-mutation
failures.
Transaction and Git boundary
Resolution, signature work, decryption, and replacement encryption all finish in memory before the first repository write. Every affected entry retains its original encrypted bytes. Repository writes use private, synced temporary files and atomic per-file replacement. If any write or the storage commit hook fails, the policy, signature, and every entry are restored in reverse order; new empty policy directories are removed. A rollback failure is surfaced separately from the original operation error.
PolicyCommitter is the integration boundary for the embedded Git engine. It
receives the exact changed paths and compatible commit intent after repository
bytes have been installed. It is called once for a real change, never for a
no-op, and must restore any staging state before returning an error. The policy
manager then restores repository bytes. NoGitCommitter represents a path not
contained in a Git work tree; it is not used for a discovered repository.
The later embedded-Git implementation owns concrete staging and commits, while this module owns the all-or-nothing storage mutation contract it invokes.