From 5575fbef5a30929bc9927d49c5429534362aa245 Mon Sep 17 00:00:00 2001 From: Glenn Ellis Date: Wed, 6 May 2026 15:55:33 -0600 Subject: [PATCH 1/2] Add VeraCrypt shell plugin for 1Password CLI --- plugins/veracrypt/plugin.go | 22 +++++++ plugins/veracrypt/veracrypt.go | 25 ++++++++ plugins/veracrypt/volume_password.go | 70 +++++++++++++++++++++++ plugins/veracrypt/volume_password_test.go | 69 ++++++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 plugins/veracrypt/plugin.go create mode 100644 plugins/veracrypt/veracrypt.go create mode 100644 plugins/veracrypt/volume_password.go create mode 100644 plugins/veracrypt/volume_password_test.go diff --git a/plugins/veracrypt/plugin.go b/plugins/veracrypt/plugin.go new file mode 100644 index 00000000..1a683071 --- /dev/null +++ b/plugins/veracrypt/plugin.go @@ -0,0 +1,22 @@ +package veracrypt + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "veracrypt", + Platform: schema.PlatformInfo{ + Name: "VeraCrypt", + Homepage: sdk.URL("https://www.veracrypt.fr"), + }, + Credentials: []schema.CredentialType{ + VolumePassword(), + }, + Executables: []schema.Executable{ + VeraCryptCLI(), + }, + } +} \ No newline at end of file diff --git a/plugins/veracrypt/veracrypt.go b/plugins/veracrypt/veracrypt.go new file mode 100644 index 00000000..2f7cf764 --- /dev/null +++ b/plugins/veracrypt/veracrypt.go @@ -0,0 +1,25 @@ +package veracrypt + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + +) + +func VeraCryptCLI() schema.Executable { + return schema.Executable{ + Name: "VeraCrypt CLI", + Runs: []string{"veracrypt"}, + DocsURL: sdk.URL("https://www.veracrypt.fr/en/Documentation.html"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + { + Name: sdk.CredentialName("Volume Password"), + }, + }, + } +} \ No newline at end of file diff --git a/plugins/veracrypt/volume_password.go b/plugins/veracrypt/volume_password.go new file mode 100644 index 00000000..5b651804 --- /dev/null +++ b/plugins/veracrypt/volume_password.go @@ -0,0 +1,70 @@ +package veracrypt + +import ( + "context" + "fmt" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/importer" + "github.com/1Password/shell-plugins/sdk/schema" + + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func VolumePassword() schema.CredentialType { + return schema.CredentialType{ + Name: sdk.CredentialName("Volume Password"), + DocsURL: sdk.URL("https://www.veracrypt.fr/en/Documentation.html"), + ManagementURL: sdk.URL("https://www.veracrypt.fr/en/Main.html"), + Fields: []schema.CredentialField{ + { + Name: fieldname.Password, + MarkdownDescription: "Password used to mount a VeraCrypt volume.", + Secret: true, + }, + { + Name: sdk.FieldName("Volume"), + MarkdownDescription: "Path to the VeraCrypt volume file.", + Secret: false, + Optional: true, + }, + }, + DefaultProvisioner: volumePasswordProvisioner(), + Importer: importer.TryAll( + importer.TryEnvVarPair(defaultEnvVarMapping), + TryVeraCryptConfigFile(), + ), + } +} + +type volumePasswordProv struct{} + +func volumePasswordProvisioner() sdk.Provisioner { + return volumePasswordProv{} +} + +func (p volumePasswordProv) Description() string { + return "Provision password as command-line arguments" +} + +func (p volumePasswordProv) Provision(ctx context.Context, in sdk.ProvisionInput, out *sdk.ProvisionOutput) { + password, ok := in.ItemFields[fieldname.Password] + if !ok || password == "" { + out.AddError(fmt.Errorf("password is required")) + out.CommandLine = []string{} + return + } + out.AddArgs("-p", password, "--non-interactive") +} + +func (p volumePasswordProv) Deprovision(ctx context.Context, in sdk.DeprovisionInput, out *sdk.DeprovisionOutput) { +} + +var defaultEnvVarMapping = map[string]sdk.FieldName{ + "VERACRYPT_PASSWORD": fieldname.Password, +} + +func TryVeraCryptConfigFile() sdk.Importer { + return importer.TryFile("~/.VeraCrypt/Config", func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { + }) +} \ No newline at end of file diff --git a/plugins/veracrypt/volume_password_test.go b/plugins/veracrypt/volume_password_test.go new file mode 100644 index 00000000..4f45f0c7 --- /dev/null +++ b/plugins/veracrypt/volume_password_test.go @@ -0,0 +1,69 @@ +package veracrypt + +import ( + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestVolumePasswordProvisioner(t *testing.T) { + plugintest.TestProvisioner(t, VolumePassword().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "password flag injection": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Password: "TestPassword123!", + }, + ExpectedOutput: sdk.ProvisionOutput{ + CommandLine: []string{"-p", "TestPassword123!", "--non-interactive"}, + }, + }, + "includes non-interactive flag": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Password: "Secret456!", + }, + ExpectedOutput: sdk.ProvisionOutput{ + CommandLine: []string{"-p", "Secret456!", "--non-interactive"}, + }, + }, + "empty password returns error": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Password: "", + }, + ExpectedOutput: sdk.ProvisionOutput{ + CommandLine: []string{}, + Diagnostics: sdk.Diagnostics{ + Errors: []sdk.Error{ + {Message: "password is required"}, + }, + }, + }, + }, + "volume field stored but not in command line": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Password: "VolumePass789!", + "Volume": "/path/to/volume.tc", + }, + ExpectedOutput: sdk.ProvisionOutput{ + CommandLine: []string{"-p", "VolumePass789!", "--non-interactive"}, + }, + }, + }) +} + +func TestVolumePasswordImporter(t *testing.T) { + plugintest.TestImporter(t, VolumePassword().Importer, map[string]plugintest.ImportCase{ + "environment": { + Environment: map[string]string{ + "VERACRYPT_PASSWORD": "TestPassword123!", + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Password: "TestPassword123!", + }, + }, + }, + }, + }) +} From db7f0df700a1da0ea004a2f32c6960bfd8accb1c Mon Sep 17 00:00:00 2001 From: Glenn Ellis Date: Wed, 6 May 2026 18:03:59 -0600 Subject: [PATCH 2/2] fix(veracrypt): Insert password flags before positional arguments VeraCrypt CLI expects: veracrypt -t -p --non-interactive [Volume path] [Mount point] The provisioner was appending flags AFTER positional args, causing 'Unexpected parameter' error. Fix: Insert -p --non-interactive before first positional argument (arg not starting with '-'). --- plugins/veracrypt/volume_password.go | 18 +++++++++++++++++- plugins/veracrypt/volume_password_test.go | 21 ++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/plugins/veracrypt/volume_password.go b/plugins/veracrypt/volume_password.go index 5b651804..10d6c7b1 100644 --- a/plugins/veracrypt/volume_password.go +++ b/plugins/veracrypt/volume_password.go @@ -54,7 +54,23 @@ func (p volumePasswordProv) Provision(ctx context.Context, in sdk.ProvisionInput out.CommandLine = []string{} return } - out.AddArgs("-p", password, "--non-interactive") + args := []string{"-p", password, "--non-interactive"} + if len(out.CommandLine) == 0 { + out.CommandLine = args + return + } + insertAt := len(out.CommandLine) + for i, arg := range out.CommandLine { + if len(arg) > 0 && arg[0] != '-' { + insertAt = i + break + } + } + newCmd := make([]string, 0, len(out.CommandLine)+len(args)) + newCmd = append(newCmd, out.CommandLine[:insertAt]...) + newCmd = append(newCmd, args...) + newCmd = append(newCmd, out.CommandLine[insertAt:]...) + out.CommandLine = newCmd } func (p volumePasswordProv) Deprovision(ctx context.Context, in sdk.DeprovisionInput, out *sdk.DeprovisionOutput) { diff --git a/plugins/veracrypt/volume_password_test.go b/plugins/veracrypt/volume_password_test.go index 4f45f0c7..b4ada03d 100644 --- a/plugins/veracrypt/volume_password_test.go +++ b/plugins/veracrypt/volume_password_test.go @@ -10,20 +10,22 @@ import ( func TestVolumePasswordProvisioner(t *testing.T) { plugintest.TestProvisioner(t, VolumePassword().DefaultProvisioner, map[string]plugintest.ProvisionCase{ - "password flag injection": { + "password flag inserted before positional args": { ItemFields: map[sdk.FieldName]string{ fieldname.Password: "TestPassword123!", }, + CommandLine: []string{"-t", "--mount", "/tmp/vol", "/mnt/point"}, ExpectedOutput: sdk.ProvisionOutput{ - CommandLine: []string{"-p", "TestPassword123!", "--non-interactive"}, + CommandLine: []string{"-t", "--mount", "-p", "TestPassword123!", "--non-interactive", "/tmp/vol", "/mnt/point"}, }, }, - "includes non-interactive flag": { + "flags inserted before mount point": { ItemFields: map[sdk.FieldName]string{ fieldname.Password: "Secret456!", }, + CommandLine: []string{"--dismount", "/mnt/point"}, ExpectedOutput: sdk.ProvisionOutput{ - CommandLine: []string{"-p", "Secret456!", "--non-interactive"}, + CommandLine: []string{"--dismount", "-p", "Secret456!", "--non-interactive", "/mnt/point"}, }, }, "empty password returns error": { @@ -44,8 +46,17 @@ func TestVolumePasswordProvisioner(t *testing.T) { fieldname.Password: "VolumePass789!", "Volume": "/path/to/volume.tc", }, + CommandLine: []string{"-t", "--mount"}, ExpectedOutput: sdk.ProvisionOutput{ - CommandLine: []string{"-p", "VolumePass789!", "--non-interactive"}, + CommandLine: []string{"-t", "--mount", "-p", "VolumePass789!", "--non-interactive"}, + }, + }, + "no command line uses flags as provided": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Password: "MySecret123!", + }, + ExpectedOutput: sdk.ProvisionOutput{ + CommandLine: []string{"-p", "MySecret123!", "--non-interactive"}, }, }, })