@@ -143,10 +143,37 @@ func validateNoneProvider(result *SetupResult) *SetupResult {
143
143
return result
144
144
}
145
145
146
+ // ErrKeyringNotAvailable is returned when the OS keyring is not available for the encrypted provider.
147
+ var ErrKeyringNotAvailable = errors .New ("OS keyring is not available. " +
148
+ "The encrypted provider requires an OS keyring to securely store passwords. " +
149
+ "Please use a different secrets provider (e.g., 'none' for API/server environments) " +
150
+ "or ensure your system has a keyring service available" )
151
+
152
+ // IsKeyringAvailable tests if the OS keyring is available by attempting to set and delete a test value.
153
+ func IsKeyringAvailable () bool {
154
+ testKey := "toolhive-keyring-test"
155
+ testValue := "test"
156
+
157
+ // Try to set a test value
158
+ if err := keyring .Set (keyringService , testKey , testValue ); err != nil {
159
+ return false
160
+ }
161
+
162
+ // Clean up the test value
163
+ _ = keyring .Delete (keyringService , testKey )
164
+
165
+ return true
166
+ }
167
+
146
168
// CreateSecretProvider creates the specified type of secrets provider.
147
169
func CreateSecretProvider (managerType ProviderType ) (Provider , error ) {
148
170
switch managerType {
149
171
case EncryptedType :
172
+ // Enforce keyring availability for encrypted provider
173
+ if ! IsKeyringAvailable () {
174
+ return nil , ErrKeyringNotAvailable
175
+ }
176
+
150
177
password , err := GetSecretsPassword ()
151
178
if err != nil {
152
179
return nil , fmt .Errorf ("failed to get secrets password: %w" , err )
@@ -168,47 +195,45 @@ func CreateSecretProvider(managerType ProviderType) (Provider, error) {
168
195
}
169
196
170
197
// GetSecretsPassword returns the password to use for encrypting and decrypting secrets.
171
- // It will attempt to retrieve it from the environment variable TOOLHIVE_SECRETS_PASSWORD .
172
- // If the environment variable is not set , it will prompt the user to enter a password .
198
+ // It will attempt to retrieve from the OS keyring .
199
+ // If not available , it will fail with an error .
173
200
func GetSecretsPassword () ([]byte , error ) {
174
- // First, attempt to load the password from the environment variable.
175
- password := []byte (os .Getenv (PasswordEnvVar ))
176
- if len (password ) > 0 {
177
- return password , nil
178
- }
179
-
180
- // If not present, attempt to load the password from the OS keyring.
201
+ // Attempt to load the password from the OS keyring.
181
202
keyringSecret , err := keyring .Get (keyringService , keyringService )
182
203
if err == nil {
183
204
return []byte (keyringSecret ), nil
184
205
}
185
206
186
207
// We need to determine if the error is due to a lack of keyring on the
187
208
// system or if the keyring is available but nothing was stored.
188
- keyringAvailable := errors .Is (err , keyring .ErrNotFound )
189
-
190
- // We cannot ask for a password in a detached process.
191
- // We should never trigger this, but this ensures that if there's a bug
192
- // then it's easier to find.
193
- if process .IsDetached () {
194
- return nil , fmt .Errorf ("detached process detected, cannot ask for password" )
195
- }
209
+ if errors .Is (err , keyring .ErrNotFound ) {
210
+
211
+ // Keyring is available but no password stored - this should only happen during setup
212
+ // We cannot ask for a password in a detached process.
213
+ // We should never trigger this, but this ensures that if there's a bug
214
+ // then it's easier to find.
215
+ if process .IsDetached () {
216
+ return nil , fmt .Errorf ("detached process detected, cannot ask for password" )
217
+ }
196
218
197
- // If the keyring is not available, prompt the user for a password.
198
- password , err = readPasswordStdin ()
199
- if err != nil {
200
- return nil , fmt .Errorf ("failed to read password: %w" , err )
201
- }
219
+ // Prompt for password during setup
220
+ password , err : = readPasswordStdin ()
221
+ if err != nil {
222
+ return nil , fmt .Errorf ("failed to read password: %w" , err )
223
+ }
202
224
203
- // If the keyring is available, we can store the password for future use.
204
- if keyringAvailable {
225
+ // Store the password in the keyring for future use
205
226
logger .Info ("writing password to os keyring" )
206
227
err = keyring .Set (keyringService , keyringService , string (password ))
207
228
if err != nil {
208
229
return nil , fmt .Errorf ("failed to store password in keyring: %w" , err )
209
230
}
231
+
232
+ return password , nil
210
233
}
211
- return password , nil
234
+
235
+ // Assume any other keyring error means keyring is not available
236
+ return nil , fmt .Errorf ("OS keyring is not available: %w" , err )
212
237
}
213
238
214
239
func readPasswordStdin () ([]byte , error ) {
0 commit comments