Check the -o path before looking for the mnemonic (closes #87)
check / check (push) Waiting to run

age encrypt and age decrypt looked at the -o path only after the
mnemonic had been obtained, so a path that could never be written
still prompted for the mnemonic or ran the mnemonic command. Both now
refuse, before the key is derived, an -o path whose directory is
missing or is not a directory, or a symlink that points at nothing.
The check creates nothing; output still looks at the path when it
writes, so the file beside the target is made when it was before. The
README names the -o path among what is checked first.

Model: opus-5-5
This commit is contained in:
2026-10-14 19:42:35 +00:00
parent 44cb5215b0
commit bb8994790b
3 changed files with 91 additions and 9 deletions
+31
View File
@@ -108,6 +108,37 @@ func TestAnInputFileThatCannotBeOpenedIsRefused(t *testing.T) {
}
}
// The mnemonic command fails in these runs, so the refusal of the -o
// path shows that it was looked at before the mnemonic was looked for.
func TestAnOutputPathThatCannotBeWrittenIsRefused(t *testing.T) {
t.Setenv(mnemonic.CommandVariable, failingMnemonicCommand)
plain := written(t, "notes.txt", "the secret\n")
directory := t.TempDir()
missing := filepath.Join(directory, "missing")
dangling := filepath.Join(directory, "dangling")
require.NoError(t, os.Symlink(missing, dangling))
refusals := map[string]error{
filepath.Join(missing, "notes.age"): fs.ErrNotExist,
filepath.Join(plain, "notes.age"): syscall.ENOTDIR,
dangling: fs.ErrNotExist,
}
for name, refusal := range refusals {
for _, operation := range []string{"encrypt", "decrypt"} {
out, err := execute(t, "age", operation, "-o", name, plain)
require.ErrorIs(t, err, refusal, operation+" "+name)
require.Empty(t, out, operation+" "+name)
}
}
// Nothing was made beside the symlink.
left, err := os.ReadDir(directory)
require.NoError(t, err)
require.Len(t, left, 1)
}
func TestAFileForAnotherKeyIsRefused(t *testing.T) {
t.Setenv(mnemonic.Variable, example())