Skip to content

fix: preserve nested .gpg-id scope during key selection - #187

Merged
pando85 merged 11 commits into
mainfrom
fix/184-gpg-id-scope
Sep 24, 2026
Merged

pando85 merged 11 commits into
mainfrom
fix/184-gpg-id-scope

Conversation

@pando85

@pando85 pando85 commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Fixes #184

An empty/missing subDir can collapse to the physical password-store root because File(repoRoot, "") resolves to repoRoot. That makes both recipient lookup and the key-selection callback capable of consulting or creating a root .gpg-id even when the operation originated in a nested subtree.

This change keeps the operation scope separate from the repository root:

  • preserve every explicit subDir exactly as supplied
  • when scope is lost (subDir == ""), recover it from the immutable operation path (EXTRA_FILE_PATH)
  • use the recovered scope consistently for encryption/decryption recipient lookup and for SUB_PATH passed through the key picker
  • recover the scope again in the activity-result callback if SUB_PATH is unexpectedly absent
  • refuse recovered scopes outside the password repository
  • add regression coverage for nested directories, existing password files, explicit root operations, and out-of-repository paths

Backward compatibility

The compatibility baseline for this fix is v1.21.0, before the full Pass-Secrets integration released in v1.22.0 (the version used in #184).

Before Pass-Secrets:

  • password creation passed the directory field directly to requireEncryptionKeysExist; root was represented as / and nested directories as values such as /ID-Pessoal/HG
  • password decryption passed relativeParentPath directly to requireDecryptionKeysExist, producing scopes such as /ID-Pessoal/HG/
  • an empty string was not the normal representation of repository root

The v1.22 Pass-Secrets preflight kept those same PGP scope inputs. This patch therefore preserves every non-empty explicit scope byte-for-byte, including the legacy leading/trailing separator forms, and changes behavior only for the lost/invalid empty scope that can otherwise be mistaken for the physical repository root.

Regression tests lock down:

  • legacy root scope /
  • legacy creation scope /ID-Pessoal/HG
  • legacy decryption scope /ID-Pessoal/HG/
  • exact passthrough without normalization
  • recovery of an empty scope from a nested directory or password path
  • intentional root recovery
  • repository-boundary enforcement
  • a pre-Pass-Secrets store with multiple independent nested .gpg-id trust boundaries and no Pass-Secrets metadata is not claimed by Pass-Secrets and does not gain a root .gpg-id

This keeps pre-Pass-Secrets .gpg-id lookup semantics intact while preventing the new failure mode from widening a nested operation to repository scope.

@forkline-bot

forkline-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown

✅ Fix pushed:

Fixed the failing test by handling empty relative paths in resolveGpgIdScope. When operationDirectory equals root, relativeTo can return an empty string instead of ".", so the function now checks for both cases and returns "/" appropriately.

@forkline-bot

forkline-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown

⏸️ CI Auto-Fix paused

A user commit was detected after an automated fix. CI auto-fix is now paused
to prevent conflicts with your changes.

To resume CI auto-fix, use the /reset or /reset-ci-auto-fix command.

@pando85
pando85 merged commit 3f293b1 into main Sep 24, 2026
5 checks passed
@pando85
pando85 deleted the fix/184-gpg-id-scope branch September 24, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Spurious root .gpg-id created when resolving a nested folder's key, even though the folder has its own .gpg-id

1 participant