lint: fix credential keygen lint, fix test keygen

This commit is contained in:
sky5454 2026-03-16 11:28:34 +08:00
parent 82c17b4134
commit 71fbf6db9d
6 changed files with 43 additions and 17 deletions

View file

@ -6,10 +6,11 @@ import (
"os"
"path/filepath"
"golang.org/x/term"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/credential"
"golang.org/x/term"
)
func onboard() {
@ -49,7 +50,7 @@ func onboard() {
// the current process and disappears when it exits.
os.Setenv(credential.PassphraseEnvVar, passphrase)
if err := setupSSHKey(); err != nil {
if err = setupSSHKey(); err != nil {
fmt.Printf("Error generating SSH key: %v\n", err)
os.Exit(1)
}

View file

@ -834,7 +834,9 @@ func LoadConfig(path string) (*Config, error) {
if passphrase := credential.PassphraseProvider(); passphrase != "" {
for _, m := range cfg.ModelList {
if m.APIKey != "" && !strings.HasPrefix(m.APIKey, "enc://") && !strings.HasPrefix(m.APIKey, "file://") {
fmt.Fprintf(os.Stderr, "picoclaw: warning: model %q has a plaintext api_key; call SaveConfig to encrypt it\n", m.ModelName)
fmt.Fprintf(os.Stderr,
"picoclaw: warning: model %q has a plaintext api_key; call SaveConfig to encrypt it\n",
m.ModelName)
}
}
}

View file

@ -11,6 +11,18 @@ import (
"github.com/sipeed/picoclaw/pkg/credential"
)
// mustSetupSSHKey generates a temporary Ed25519 SSH key in t.TempDir() and sets
// PICOCLAW_SSH_KEY_PATH to its path for the duration of the test. This is required
// whenever a test exercises encryption/decryption via credential.Encrypt or SaveConfig.
func mustSetupSSHKey(t *testing.T) {
t.Helper()
keyPath := filepath.Join(t.TempDir(), "picoclaw_ed25519.key")
if err := credential.GenerateSSHKey(keyPath); err != nil {
t.Fatalf("mustSetupSSHKey: %v", err)
}
t.Setenv("PICOCLAW_SSH_KEY_PATH", keyPath)
}
func TestAgentModelConfig_UnmarshalString(t *testing.T) {
var m AgentModelConfig
if err := json.Unmarshal([]byte(`"gpt-4"`), &m); err != nil {
@ -666,7 +678,7 @@ func TestSaveConfig_EncryptsPlaintextAPIKey(t *testing.T) {
cfgPath := filepath.Join(dir, "config.json")
t.Setenv("PICOCLAW_KEY_PASSPHRASE", "test-passphrase")
t.Setenv("PICOCLAW_SSH_KEY_PATH", "")
mustSetupSSHKey(t)
cfg := DefaultConfig()
cfg.ModelList = []ModelConfig{
@ -755,7 +767,7 @@ func TestSaveConfig_MixedKeys(t *testing.T) {
cfgPath := filepath.Join(dir, "config.json")
t.Setenv("PICOCLAW_KEY_PASSPHRASE", "test-passphrase")
t.Setenv("PICOCLAW_SSH_KEY_PATH", "")
mustSetupSSHKey(t)
// Pre-encrypt one key so we have a genuine enc:// value to put in the config.
if err := SaveConfig(cfgPath, &Config{
@ -844,7 +856,7 @@ func TestLoadConfig_MixedKeys_NoPassphrase(t *testing.T) {
// First encrypt a key so we have a real enc:// value.
t.Setenv("PICOCLAW_KEY_PASSPHRASE", "test-passphrase")
t.Setenv("PICOCLAW_SSH_KEY_PATH", "")
mustSetupSSHKey(t)
if err := SaveConfig(cfgPath, &Config{
ModelList: []ModelConfig{
{ModelName: "m", Model: "openai/gpt-4", APIKey: "sk-secret"},
@ -901,7 +913,7 @@ func TestSaveConfig_UsesPassphraseProvider(t *testing.T) {
// Ensure the env var is empty — passphrase must come from PassphraseProvider only.
t.Setenv("PICOCLAW_KEY_PASSPHRASE", "")
t.Setenv("PICOCLAW_SSH_KEY_PATH", "")
mustSetupSSHKey(t)
// Replace PassphraseProvider with an in-memory function (simulating SecureStore).
const testPassphrase = "provider-passphrase"
@ -931,7 +943,7 @@ func TestLoadConfig_UsesPassphraseProvider(t *testing.T) {
// Ensure the env var is empty throughout.
t.Setenv("PICOCLAW_KEY_PASSPHRASE", "")
t.Setenv("PICOCLAW_SSH_KEY_PATH", "")
mustSetupSSHKey(t)
const testPassphrase = "provider-passphrase"
const plainKey = "sk-secret"
@ -947,7 +959,7 @@ func TestLoadConfig_UsesPassphraseProvider(t *testing.T) {
{"model_name": "test", "model": "openai/gpt-4", "api_key": encrypted},
},
})
if err := os.WriteFile(cfgPath, raw, 0o600); err != nil {
if err = os.WriteFile(cfgPath, raw, 0o600); err != nil {
t.Fatalf("setup: %v", err)
}

View file

@ -1,6 +1,6 @@
// Package credential resolves API credential values for model_list entries.
//
// An API key is a form of authorization credential. This package centralises
// An API key is a form of authorization credential. This package centralizes
// how raw credential strings—plaintext or file references—are resolved into
// their actual values, keeping that logic out of the config loader.
//
@ -88,8 +88,8 @@ type Resolver struct {
func NewResolver(configDir string) *Resolver {
resolved := configDir
if configDir != "" {
if real, err := filepath.EvalSymlinks(configDir); err == nil {
resolved = real
if linkedPath, err := filepath.EvalSymlinks(configDir); err == nil {
resolved = linkedPath
}
}
return &Resolver{configDir: configDir, resolvedConfigDir: resolved}
@ -278,10 +278,15 @@ func allowedSSHKeyPath(path string) bool {
// sshKeyPath must be non-empty; returns an error otherwise.
func deriveKey(passphrase, sshKeyPath string, salt []byte) ([]byte, error) {
if sshKeyPath == "" {
return nil, fmt.Errorf("credential: SSH private key is required but not found (set PICOCLAW_SSH_KEY_PATH or place key at ~/.ssh/picoclaw_ed25519.key)")
return nil, fmt.Errorf(
"credential: SSH private key is required but not found" +
" (set PICOCLAW_SSH_KEY_PATH or place key at ~/.ssh/picoclaw_ed25519.key)")
}
if !allowedSSHKeyPath(sshKeyPath) {
return nil, fmt.Errorf("credential: SSH key path %q is not in an allowed location (PICOCLAW_SSH_KEY_PATH, PICOCLAW_HOME, or ~/.ssh/)", sshKeyPath)
return nil, fmt.Errorf(
"credential: SSH key path %q is not in an allowed location (PICOCLAW_SSH_KEY_PATH, PICOCLAW_HOME, or ~/.ssh/)",
sshKeyPath,
)
}
sshBytes, err := os.ReadFile(sshKeyPath)
if err != nil {

View file

@ -42,7 +42,7 @@ func GenerateSSHKey(path string) error {
}
privPEM := pem.EncodeToMemory(block)
if err := os.WriteFile(path, privPEM, 0o600); err != nil {
if err = os.WriteFile(path, privPEM, 0o600); err != nil {
return fmt.Errorf("credential: keygen: write private key %q: %w", path, err)
}

View file

@ -61,10 +61,16 @@ func TestGenerateSSHKey_CreatesFiles(t *testing.T) {
if err != nil {
t.Fatalf("read public key: %v", err)
}
_, _, _, _, err = ssh.ParseAuthorizedKey(pubBytes)
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(pubBytes)
if err != nil {
t.Fatalf("parse public key: %v", err)
}
if pubKey == nil {
t.Fatal("expected non-nil public key")
}
if len(rest) > 0 {
t.Errorf("unexpected trailing bytes after public key: %d bytes", len(rest))
}
}
func TestGenerateSSHKey_OverwritesExisting(t *testing.T) {
@ -80,7 +86,7 @@ func TestGenerateSSHKey_OverwritesExisting(t *testing.T) {
t.Fatalf("read first key: %v", err)
}
if err := GenerateSSHKey(keyPath); err != nil {
if err = GenerateSSHKey(keyPath); err != nil {
t.Fatalf("second GenerateSSHKey() error = %v", err)
}
second, err := os.ReadFile(keyPath)