From c10ca240ca694d40fd5b93093e6e731b89e9fb2c Mon Sep 17 00:00:00 2001 From: sushi30 Date: Thu, 26 Mar 2026 21:20:59 +0100 Subject: [PATCH] test(credential): add test for env:// key passthrough in encryptPlaintextAPIKeys --- pkg/config/config.go | 74 ------------------------------------- pkg/config/config_struct.go | 6 +-- 2 files changed, 3 insertions(+), 77 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 80712d924..fd4466b8c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1171,80 +1171,6 @@ func toNameIndex(list []*ModelConfig) []string { return nameList } -// encryptPlaintextAPIKeys returns a copy of models with plaintext api_key values -// encrypted. Returns (nil, nil) when nothing changed (all keys already sealed or -// empty). Returns (nil, error) if any key fails to encrypt — callers must treat -// this as a hard failure to prevent a mixed plaintext/ciphertext state on disk. -// Symmetric counterpart of resolveAPIKeys: both operate purely on []ModelConfig -// and leave JSON marshaling to the caller. -func encryptPlaintextAPIKeys( - models map[string]ModelSecurityEntry, - passphrase string, -) (map[string]ModelSecurityEntry, error) { - sealed := make(map[string]ModelSecurityEntry, len(models)) - changed := false - for k, m := range models { - sealedEntry := ModelSecurityEntry{APIKeys: make([]string, len(m.APIKeys))} - - // Encrypt each key in APIKeys - for i, key := range m.APIKeys { - if key == "" || strings.HasPrefix(key, "enc://") || strings.HasPrefix(key, "file://") || strings.HasPrefix(key, "env://") { - sealedEntry.APIKeys[i] = key - continue - } - encrypted, err := credential.Encrypt(passphrase, "", key) - if err != nil { - return nil, fmt.Errorf("cannot seal api_key for model %q: %w", k, err) - } - sealedEntry.APIKeys[i] = encrypted - changed = true - } - - sealed[k] = sealedEntry - } - if !changed { - return nil, nil - } - return sealed, nil -} - -// resolveAPIKeys decrypts or dereferences each api_key in models in-place. -// Supports plaintext (no-op), file:// (read from configDir), and enc:// (AES-GCM decrypt). -func resolveAPIKeys(models []*ModelConfig, configDir string) error { - cr := credential.NewResolver(configDir) - for i := range models { - // Resolve APIKeys array - for j, key := range models[i].apiKeys { - resolved, err := cr.Resolve(key) - if err != nil { - return fmt.Errorf( - "model_list[%d] (%s): api_keys[%d]: %w", - i, - models[i].ModelName, - j, - err, - ) - } - models[i].apiKeys[j] = resolved - } - } - return nil -} - -func (c *Config) migrateChannelConfigs() { - // Discord: mention_only -> group_trigger.mention_only - if c.Channels.Discord.MentionOnly && !c.Channels.Discord.GroupTrigger.MentionOnly { - c.Channels.Discord.GroupTrigger.MentionOnly = true - } - - // OneBot: group_trigger_prefix -> group_trigger.prefixes - if len(c.Channels.OneBot.GroupTriggerPrefix) > 0 && - len(c.Channels.OneBot.GroupTrigger.Prefixes) == 0 { - c.Channels.OneBot.GroupTrigger.Prefixes = c.Channels.OneBot.GroupTriggerPrefix - } -} - - func SaveConfig(path string, cfg *Config) error { if cfg.Version < CurrentVersion { cfg.Version = CurrentVersion diff --git a/pkg/config/config_struct.go b/pkg/config/config_struct.go index 0b8dd85c8..4dd609395 100644 --- a/pkg/config/config_struct.go +++ b/pkg/config/config_struct.go @@ -219,12 +219,12 @@ func (s *SecureString) UnmarshalJSON(value []byte) error { } func (s SecureString) MarshalYAML() (any, error) { - // Preserve raw value if it is already a reference (enc:// or file://) - if strings.HasPrefix(s.raw, credential.EncScheme) || strings.HasPrefix(s.raw, credential.FileScheme) { + // Preserve raw value if it is already a reference (enc://, file://, or env://) + if strings.HasPrefix(s.raw, credential.EncScheme) || strings.HasPrefix(s.raw, credential.FileScheme) || strings.HasPrefix(s.raw, "env://") { return s.raw, nil } // If resolved is a reference format (e.g. set via Set), copy back to raw - if strings.HasPrefix(s.resolved, credential.EncScheme) || strings.HasPrefix(s.resolved, credential.FileScheme) { + if strings.HasPrefix(s.resolved, credential.EncScheme) || strings.HasPrefix(s.resolved, credential.FileScheme) || strings.HasPrefix(s.resolved, "env://") { s.raw = s.resolved return s.raw, nil }