fix: harden SSH trust and dependency security

This commit is contained in:
Andy Dixon
2026-06-02 12:25:20 +01:00
parent fb230ee35d
commit ea6a9743d1
17 changed files with 1365 additions and 186 deletions
+27
View File
@@ -3,6 +3,7 @@ package storage
import (
"encoding/json"
"path/filepath"
"strings"
"testing"
"time"
@@ -455,6 +456,32 @@ func TestExportProfiles(t *testing.T) {
}
}
func TestExportProfilesRedactsInlineSSHSecrets(t *testing.T) {
store := setupTestStore(t)
profile := makeSSHProfile("Export Secret", "secret.example.com")
profile.SSH.ProxyPassword = "proxy-secret"
profile.SSH.TOTPSecret = "totp-secret"
if err := store.CreateProfile(profile); err != nil {
t.Fatalf("CreateProfile() error = %v", err)
}
data, err := store.ExportProfiles(false)
if err != nil {
t.Fatalf("ExportProfiles(false) error = %v", err)
}
if strings.Contains(string(data), "proxy-secret") || strings.Contains(string(data), "totp-secret") {
t.Fatalf("ExportProfiles(false) leaked inline SSH secrets: %s", string(data))
}
data, err = store.ExportProfiles(true)
if err != nil {
t.Fatalf("ExportProfiles(true) error = %v", err)
}
if !strings.Contains(string(data), "proxy-secret") || !strings.Contains(string(data), "totp-secret") {
t.Fatalf("ExportProfiles(true) did not include inline SSH secrets: %s", string(data))
}
}
func TestImportProfiles(t *testing.T) {
store := setupTestStore(t)
p := makeSSHProfile("Import Me", "import.local")
+27 -10
View File
@@ -346,13 +346,15 @@ func (s *Store) UpdateLastConnected(id string) error {
}
// ExportProfiles serialises all profiles as JSON. If includeSecrets is false
// the export omits credential-related fields (currently a placeholder — all
// secrets live in the SecretStore, not in profiles).
// the export omits inline credential-related fields.
func (s *Store) ExportProfiles(includeSecrets bool) ([]byte, error) {
profiles, err := s.ListProfiles()
if err != nil {
return nil, fmt.Errorf("storage: export failed: %w", err)
}
if !includeSecrets {
profiles = redactProfileSecrets(profiles)
}
return json.MarshalIndent(profiles, "", " ")
}
@@ -567,11 +569,11 @@ func (s *Store) SavePreferences(prefs *models.AppPreferences) error {
func scanProfile(row *sql.Row) (*models.Profile, error) {
p := &models.Profile{}
var (
protocol, tagsJSON string
protocol, tagsJSON string
sshJSON, telnetJSON, rawJSON, githubJSON, vncJSON sql.NullString
lastConn sql.NullString
favourite int
createdAt, updatedAt string
lastConn sql.NullString
favourite int
createdAt, updatedAt string
)
err := row.Scan(
@@ -634,11 +636,11 @@ func scanProfiles(rows *sql.Rows) ([]*models.Profile, error) {
for rows.Next() {
p := &models.Profile{}
var (
protocol, tagsJSON string
protocol, tagsJSON string
sshJSON, telnetJSON, rawJSON, githubJSON, vncJSON sql.NullString
lastConn sql.NullString
favourite int
createdAt, updatedAt string
lastConn sql.NullString
favourite int
createdAt, updatedAt string
)
err := rows.Scan(
@@ -720,6 +722,21 @@ func nullableJSON(ptr interface{}, data []byte) interface{} {
return string(data)
}
func redactProfileSecrets(profiles []*models.Profile) []*models.Profile {
redacted := make([]*models.Profile, len(profiles))
for i, profile := range profiles {
cp := *profile
if cp.SSH != nil {
sshCfg := *cp.SSH
sshCfg.ProxyPassword = ""
sshCfg.TOTPSecret = ""
cp.SSH = &sshCfg
}
redacted[i] = &cp
}
return redacted
}
// ---------------------------------------------------------------------------
// Snippet CRUD
// ---------------------------------------------------------------------------