package discover import ( "bytes" "context" "os" "path/filepath" "strconv" "strings" "incredigo/internal/vault" ) func init() { Register(&sshScanner{}) } // sshScanner harvests private keys from ~/.ssh (files named id_* that aren't // .pub). The whole key file is the secret. Read-only. type sshScanner struct{} func (s *sshScanner) Name() string { return "ssh" } func (s *sshScanner) dir() string { home, _ := os.UserHomeDir() return filepath.Join(home, ".ssh") } func (s *sshScanner) Available() bool { fi, err := os.Stat(s.dir()) return err == nil && fi.IsDir() } func (s *sshScanner) Scan(ctx context.Context, v *vault.Vault) ([]Credential, error) { entries, err := os.ReadDir(s.dir()) if err != nil { return nil, err } var creds []Credential for _, e := range entries { name := e.Name() if !e.Type().IsRegular() || !strings.HasPrefix(name, "id_") || strings.HasSuffix(name, ".pub") { continue } p := filepath.Join(s.dir(), name) b, err := os.ReadFile(p) // read-only if err != nil { continue } if !bytes.Contains(b, []byte("PRIVATE KEY")) { continue // not a private key file } fi, _ := os.Stat(p) // Best-effort: PEM-encrypted keys carry an explicit marker. (OpenSSH-format // encryption isn't detectable without parsing the body, so this can // under-report; it never claims a plaintext key is encrypted.) encrypted := bytes.Contains(b, []byte("ENCRYPTED")) creds = append(creds, Credential{ Source: s.Name(), Kind: KindPrivateKey, Identity: name, Location: p, Modified: fi.ModTime(), Secret: v.Store(b), Meta: map[string]string{"encrypted": strconv.FormatBool(encrypted)}, }) } return creds, nil }