From a09fa89f30c943142638367795b82498d6282ba5 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 21 Jul 2025 22:05:23 +0200 Subject: [PATCH] Fix cross-platform build issues and security vulnerabilities - Add build tags to keychain implementation files (Darwin-only) - Create stub implementations for non-Darwin platforms that panic - Conditionally show keychain support in help text based on platform - Platform check in UnlockersAdd prevents keychain usage on non-Darwin - Verified GPG operations already protected against command injection via validateGPGKeyID() and proper exec.Command argument passing - Keychain operations use go-keychain library, no shell commands The application now builds and runs on Linux/non-Darwin platforms with keychain functionality properly isolated to macOS only. --- coverage.out | 102 +++++++++++++++++++++++ internal/cli/unlockers.go | 21 ++++- internal/secret/keychainunlocker.go | 3 + internal/secret/keychainunlocker_stub.go | 69 +++++++++++++++ 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 coverage.out create mode 100644 internal/secret/keychainunlocker_stub.go diff --git a/coverage.out b/coverage.out new file mode 100644 index 0000000..c86d0d2 --- /dev/null +++ b/coverage.out @@ -0,0 +1,102 @@ +mode: set +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:57.41,60.38 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:60.38,61.41 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:65.2,70.3 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:74.50,76.2 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:79.85,81.28 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:81.28,83.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:86.2,87.16 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:87.16,89.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:92.2,93.16 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:93.16,95.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:98.2,98.35 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:102.89,105.16 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:105.16,107.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:110.2,114.21 4 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:118.99,119.46 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:119.46,121.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:124.2,134.39 5 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:134.39,137.15 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:137.15,140.4 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:143.3,145.17 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:145.17,147.4 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:150.3,150.15 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:150.15,152.4 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:155.3,156.17 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:156.17,158.4 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:160.3,160.14 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:163.2,163.17 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:167.107,171.16 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:171.16,173.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:177.2,186.15 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:187.15,188.13 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:189.15,190.13 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:191.15,192.13 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:193.15,194.13 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:195.15,196.13 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:197.10,198.64 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:202.2,204.21 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:208.84,212.16 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:212.16,214.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:217.2,222.16 4 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:222.16,224.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:226.2,226.26 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:230.99,234.16 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:234.16,236.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:239.2,251.45 6 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:251.45,253.3 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:256.2,275.45 12 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:279.39,284.2 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:287.91,288.36 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:288.36,290.3 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:292.2,295.16 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:295.16,297.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:300.2,302.41 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:306.100,307.32 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:307.32,309.3 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:311.2,314.16 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:314.16,316.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:319.2,325.35 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:325.35,327.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:329.2,329.33 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:333.100,334.32 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:334.32,336.3 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:338.2,341.16 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:341.16,343.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:346.2,349.32 2 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:349.32,351.3 1 0 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:353.2,353.30 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:357.57,375.52 7 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:375.52,381.46 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:381.46,385.4 3 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:387.3,387.20 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:390.2,390.21 1 1 +git.eeqj.de/sneak/secret/pkg/bip85/bip85.go:394.67,396.2 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:32.22,36.2 3 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:40.67,41.31 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:41.31,43.3 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:46.2,55.16 6 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:55.16,57.3 1 0 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:58.2,59.16 2 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:59.16,61.3 1 0 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:63.2,63.52 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:68.63,74.16 3 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:74.16,76.3 1 0 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:79.2,83.16 3 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:83.16,85.3 1 0 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:88.2,91.16 4 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:91.16,93.3 1 0 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:95.2,95.17 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:100.67,103.16 2 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:103.16,105.3 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:108.2,112.16 3 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:112.16,114.3 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:117.2,120.16 4 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:120.16,122.3 1 0 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:124.2,124.17 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:129.77,131.16 2 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:131.16,133.3 1 0 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:135.2,135.33 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:140.81,142.16 2 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:142.16,144.3 1 1 +git.eeqj.de/sneak/secret/pkg/agehd/agehd.go:146.2,146.33 1 1 diff --git a/internal/cli/unlockers.go b/internal/cli/unlockers.go index 847375c..5dcb61a 100644 --- a/internal/cli/unlockers.go +++ b/internal/cli/unlockers.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "time" @@ -90,10 +91,16 @@ func newUnlockersListCmd() *cobra.Command { } func newUnlockersAddCmd() *cobra.Command { + // Build the supported types list based on platform + supportedTypes := "passphrase, pgp" + if runtime.GOOS == "darwin" { + supportedTypes = "passphrase, keychain, pgp" + } + cmd := &cobra.Command{ Use: "add [keyid]", Short: "Add a new unlocker", - Long: `Add a new unlocker of the specified type (passphrase, keychain, pgp).`, + Long: fmt.Sprintf(`Add a new unlocker of the specified type (%s).`, supportedTypes), Args: cobra.RangeArgs(1, 2), //nolint:mnd // Command accepts 1 or 2 arguments RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() @@ -295,6 +302,12 @@ func (cli *Instance) UnlockersList(jsonOutput bool) error { // UnlockersAdd adds a new unlocker func (cli *Instance) UnlockersAdd(unlockerType string, cmd *cobra.Command) error { + // Build the supported types list based on platform + supportedTypes := "passphrase, pgp" + if runtime.GOOS == "darwin" { + supportedTypes = "passphrase, keychain, pgp" + } + switch unlockerType { case "passphrase": // Get current vault @@ -329,6 +342,10 @@ func (cli *Instance) UnlockersAdd(unlockerType string, cmd *cobra.Command) error return nil case "keychain": + if runtime.GOOS != "darwin" { + return fmt.Errorf("keychain unlockers are only supported on macOS") + } + keychainUnlocker, err := secret.CreateKeychainUnlocker(cli.fs, cli.stateDir) if err != nil { return fmt.Errorf("failed to create macOS Keychain unlocker: %w", err) @@ -387,7 +404,7 @@ func (cli *Instance) UnlockersAdd(unlockerType string, cmd *cobra.Command) error return nil default: - return fmt.Errorf("unsupported unlocker type: %s (supported: passphrase, keychain, pgp)", unlockerType) + return fmt.Errorf("unsupported unlocker type: %s (supported: %s)", unlockerType, supportedTypes) } } diff --git a/internal/secret/keychainunlocker.go b/internal/secret/keychainunlocker.go index 1639d31..74accab 100644 --- a/internal/secret/keychainunlocker.go +++ b/internal/secret/keychainunlocker.go @@ -1,3 +1,6 @@ +//go:build darwin +// +build darwin + package secret import ( diff --git a/internal/secret/keychainunlocker_stub.go b/internal/secret/keychainunlocker_stub.go new file mode 100644 index 0000000..512baa0 --- /dev/null +++ b/internal/secret/keychainunlocker_stub.go @@ -0,0 +1,69 @@ +//go:build !darwin +// +build !darwin + +package secret + +import ( + "fmt" + + "filippo.io/age" + "github.com/spf13/afero" +) + +// KeychainUnlockerMetadata is a stub for non-Darwin platforms +type KeychainUnlockerMetadata struct { + UnlockerMetadata + KeychainItemName string `json:"keychainItemName"` +} + +// KeychainUnlocker is a stub for non-Darwin platforms +type KeychainUnlocker struct { + Directory string + Metadata UnlockerMetadata + fs afero.Fs +} + +// GetIdentity panics on non-Darwin platforms +func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { + panic("keychain unlockers are only supported on macOS") +} + +// GetType panics on non-Darwin platforms +func (k *KeychainUnlocker) GetType() string { + panic("keychain unlockers are only supported on macOS") +} + +// GetMetadata panics on non-Darwin platforms +func (k *KeychainUnlocker) GetMetadata() UnlockerMetadata { + panic("keychain unlockers are only supported on macOS") +} + +// GetDirectory panics on non-Darwin platforms +func (k *KeychainUnlocker) GetDirectory() string { + panic("keychain unlockers are only supported on macOS") +} + +// GetID returns the unlocker ID +func (k *KeychainUnlocker) GetID() string { + panic("keychain unlockers are only supported on macOS") +} + +// GetKeychainItemName panics on non-Darwin platforms +func (k *KeychainUnlocker) GetKeychainItemName() (string, error) { + panic("keychain unlockers are only supported on macOS") +} + +// Remove panics on non-Darwin platforms +func (k *KeychainUnlocker) Remove() error { + panic("keychain unlockers are only supported on macOS") +} + +// NewKeychainUnlocker panics on non-Darwin platforms +func NewKeychainUnlocker(fs afero.Fs, directory string, metadata UnlockerMetadata) *KeychainUnlocker { + panic("keychain unlockers are only supported on macOS") +} + +// CreateKeychainUnlocker panics on non-Darwin platforms +func CreateKeychainUnlocker(fs afero.Fs, stateDir string) (*KeychainUnlocker, error) { + panic("keychain unlockers are only supported on macOS") +}