fix(config): encryptPlaintextAPIKeys: struct-based encryption, fail-fast, remove raw []byte
This commit is contained in:
parent
23c0ca1c77
commit
4fdff3e5c9
1 changed files with 27 additions and 21 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -864,30 +863,32 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// encryptPlaintextAPIKeys rewrites plaintext api_key values in raw JSON using
|
// encryptPlaintextAPIKeys returns a copy of models with plaintext api_key values
|
||||||
// the entries already parsed into models. Returns the rewritten bytes when at
|
// encrypted. Returns (nil, nil) when nothing changed (all keys already sealed or
|
||||||
// least one key was sealed, or nil when nothing changed.
|
// empty). Returns (nil, error) if any key fails to encrypt — callers must treat
|
||||||
func encryptPlaintextAPIKeys(models []ModelConfig, passphrase string, raw []byte) []byte {
|
// this as a hard failure to prevent a mixed plaintext/ciphertext state on disk.
|
||||||
result := raw
|
// Symmetric counterpart of resolveAPIKeys: both operate purely on []ModelConfig
|
||||||
|
// and leave JSON marshaling to the caller.
|
||||||
|
func encryptPlaintextAPIKeys(models []ModelConfig, passphrase string) ([]ModelConfig, error) {
|
||||||
|
sealed := make([]ModelConfig, len(models))
|
||||||
|
copy(sealed, models)
|
||||||
changed := false
|
changed := false
|
||||||
for _, m := range models {
|
for i := range sealed {
|
||||||
|
m := &sealed[i]
|
||||||
if m.APIKey == "" || strings.HasPrefix(m.APIKey, "enc://") || strings.HasPrefix(m.APIKey, "file://") {
|
if m.APIKey == "" || strings.HasPrefix(m.APIKey, "enc://") || strings.HasPrefix(m.APIKey, "file://") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
encrypted, err := credential.Encrypt(passphrase, "", m.APIKey)
|
encrypted, err := credential.Encrypt(passphrase, "", m.APIKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "picoclaw: warning: cannot seal api_key in config: %v\n", err)
|
return nil, fmt.Errorf("cannot seal api_key for model %q: %w", m.ModelName, err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
oldJSON, _ := json.Marshal(m.APIKey)
|
m.APIKey = encrypted
|
||||||
newJSON, _ := json.Marshal(encrypted)
|
|
||||||
result = bytes.Replace(result, oldJSON, newJSON, 1)
|
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
if !changed {
|
if !changed {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return result
|
return sealed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveAPIKeys decrypts or dereferences each api_key in models in-place.
|
// resolveAPIKeys decrypts or dereferences each api_key in models in-place.
|
||||||
|
|
@ -918,17 +919,22 @@ func (c *Config) migrateChannelConfigs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveConfig(path string, cfg *Config) error {
|
func SaveConfig(path string, cfg *Config) error {
|
||||||
|
if passphrase := credential.PassphraseProvider(); passphrase != "" {
|
||||||
|
sealed, err := encryptPlaintextAPIKeys(cfg.ModelList, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if sealed != nil {
|
||||||
|
tmp := *cfg
|
||||||
|
tmp.ModelList = sealed
|
||||||
|
cfg = &tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data, err := json.MarshalIndent(cfg, "", " ")
|
data, err := json.MarshalIndent(cfg, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if passphrase := credential.PassphraseProvider(); passphrase != "" {
|
|
||||||
if sealed := encryptPlaintextAPIKeys(cfg.ModelList, passphrase, data); sealed != nil {
|
|
||||||
data = sealed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileutil.WriteFileAtomic(path, data, 0o600)
|
return fileutil.WriteFileAtomic(path, data, 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue