importer

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package importer reads secrets from foreign password managers (Bitwarden, 1Password, LastPass, Chrome, Firefox, Enpass, KeePass, pass, gopass) and converts them into entries suitable for a pass-format store.

The core abstraction is the Importer interface: each format implements Name (for display), Detect (content-based auto-detection), and Import (streaming conversion). A Registry holds all known importers and resolves the right one from an input file.

Entry is the intermediate representation: it carries the richer structure of foreign formats (title, username, URL, group, TOTP, attachments) and is mapped onto a store path and a pass-format secret by the export step.

Usage:

Import a file with auto-detection:

registry := importer.NewRegistry()
imp, _, _ := registry.DetectReader(file)
entries, _ := importer.ImportAll(imp, file)
importer.WriteEntries(store, entries, false)

Plan without writing (dry-run):

plans := importer.Plan(store, entries)
fmt.Println(importer.FormatPlan(plans))

Package importer reads secrets from foreign password managers and converts them into entries suitable for a pass-format store.

The core abstraction is the Importer interface: each supported format implements Name (for display), Detect (content-based auto-detection), and Import (streaming conversion). A Registry holds all known importers and resolves the right one from an input file.

Entry is the intermediate representation: it carries the richer structure of foreign formats (title, username, URL, group, TOTP, attachments) and is later mapped onto a store path and a pass-format secret by the export step.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DeduplicatePaths

func DeduplicatePaths(paths []string) []string

DeduplicatePaths resolves duplicate paths by appending a numeric suffix. Returns a slice of deduplicated paths in the same order as input. The first occurrence keeps its original name; subsequent ones get "-2", "-3", etc.

func FormatPlan

func FormatPlan(plans []PlanEntry) string

FormatPlan formats a dry-run plan for display.

func NormalizePath

func NormalizePath(group, title string) string

NormalizePath converts a title and optional group from a foreign format into a valid store path.

Rules:

  • Group and title are joined with '/'.
  • Whitespace is collapsed and trimmed.
  • Characters invalid in filenames are replaced with '_'.
  • The result is lowercased for consistency (matching pass conventions).
  • Empty segments are dropped.
  • The path is cleaned to prevent directory traversal.

func ValidatePath

func ValidatePath(p string) bool

ValidatePath reports whether p is a safe store path: non-empty, no traversal, no leading slash, and every segment is a valid filename.

func WriteEntries

func WriteEntries(s *store.Store, entries []*Entry, force bool) ([]string, []error)

WriteEntries writes all entries to the store. It skips entries with invalid paths (reporting them via the returned errors) and overwrites existing entries only when force is true.

Types

type Attachment

type Attachment struct {
	// Name is the filename of the attachment.
	Name string
	// Data is the raw binary content.
	Data []byte
}

Attachment is a binary blob from a foreign format, stored as a separate base64-encoded entry in the store.

type BitwardenImporter

type BitwardenImporter struct{}

BitwardenImporter reads CSV exports from the Bitwarden password manager.

Bitwarden CSV columns vary between versions. Known column names are mapped by csvHeaderIndex so that the code works regardless of column order.

func (BitwardenImporter) Detect

func (BitwardenImporter) Detect(r io.Reader) bool

Detect reports whether r looks like a Bitwarden CSV export.

func (BitwardenImporter) Import

Import reads entries from a Bitwarden CSV.

func (BitwardenImporter) Name

func (BitwardenImporter) Name() string

Name returns the format name.

type ChromeImporter

type ChromeImporter struct{}

ChromeImporter reads CSV exports from Google Chrome's password manager.

Chrome exports use: name,url,username,password,note The first line may contain a explanatory prefix that must be skipped.

func (ChromeImporter) Detect

func (ChromeImporter) Detect(r io.Reader) bool

Detect reports whether r looks like a Chrome CSV export.

func (ChromeImporter) Import

func (ChromeImporter) Import(r io.Reader) iter.Seq2[*Entry, error]

Import reads entries from a Chrome CSV.

func (ChromeImporter) Name

func (ChromeImporter) Name() string

Name returns the format name.

type EnpassImporter

type EnpassImporter struct{}

EnpassImporter reads CSV exports from Enpass.

Enpass exports vary by version. Common column names include "Title", "User Name", "Password", "Web Site", "Remarks", "Group", and custom fields as additional columns.

func (EnpassImporter) Detect

func (EnpassImporter) Detect(r io.Reader) bool

Detect reports whether r looks like an Enpass CSV export.

func (EnpassImporter) Import

func (EnpassImporter) Import(r io.Reader) iter.Seq2[*Entry, error]

Import reads entries from an Enpass CSV.

func (EnpassImporter) Name

func (EnpassImporter) Name() string

Name returns the format name.

type Entry

type Entry struct {
	// Title is the entry name as reported by the source. It is used as the
	// basis for the store path after normalisation.
	Title string

	// Path overrides Title as the store path. When set, the export step uses
	// it directly instead of deriving a path from Title + Group.
	Path string

	// Group is the folder or category the entry belongs to in the source
	// manager (e.g. "Finance/Banks"). It is joined with Title to form the
	// store path when Path is empty.
	Group string

	// Password is the primary secret.
	Password string

	// Username is the login name, written as a "username:" field.
	Username string

	// URL is the associated web address, written as a "url:" field.
	URL string

	// Notes is free-form text appended after structured fields.
	Notes string

	// TOTPURI is an otpauth:// URI, if the source carries a TOTP secret.
	// Importers that find TOTP secrets in other representations (e.g. a
	// separate secret field) must convert them to an otpauth:// URI.
	TOTPURI string

	// Fields are additional "key: value" pairs not covered by the structured
	// fields above.
	Fields []Field

	// Attachments are binary blobs from the source (e.g. KDBX attachments).
	// They are stored as "name.b64" entries with base64-encoded content,
	// following gopass convention (§1 of ARCHITECTURE.md).
	Attachments []Attachment
}

Entry is a single secret extracted from a foreign format, before it has been mapped to a store path and a pass-format secret.

An importer populates whichever fields its source provides; the export step ignores empty fields. Password is the only truly required field — even an empty password is valid, because the user may intend to edit the entry later.

func ImportAll

func ImportAll(imp Importer, r io.Reader) ([]*Entry, error)

ImportAll reads every entry from an importer, stopping at the first error that is not attached to an entry.

func (*Entry) AttachmentStorePath

func (e *Entry) AttachmentStorePath(i int) (string, error)

AttachmentStorePath returns the store path for attachment i, using the gopass convention of "name.b64" (§1 of ARCHITECTURE.md).

func (*Entry) StorePath

func (e *Entry) StorePath() (string, error)

StorePath returns the path under which this entry should be stored, after normalisation and validation. If the entry already has an explicit Path, it is used directly; otherwise the path is derived from Group and Title.

func (*Entry) ToSecret

func (e *Entry) ToSecret() *secret.Secret

ToSecret converts an Entry into a pass-format secret. The first line is the password; subsequent lines carry structured fields, the OTP URI, and free-form notes, in that order.

type Field

type Field struct {
	// Name is the field label.
	Name string
	// Value is the field content.
	Value string
}

Field is a key-value pair that becomes a "key: value" line in the secret.

type FirefoxImporter

type FirefoxImporter struct{}

FirefoxImporter reads CSV exports from Firefox Lockwise / Firefox Password Manager.

Firefox exports use: url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged Only url, username, and password carry secret data; the rest are metadata.

func (FirefoxImporter) Detect

func (FirefoxImporter) Detect(r io.Reader) bool

Detect reports whether r looks like a Firefox CSV export.

func (FirefoxImporter) Import

func (FirefoxImporter) Import(r io.Reader) iter.Seq2[*Entry, error]

Import reads entries from a Firefox CSV.

func (FirefoxImporter) Name

func (FirefoxImporter) Name() string

Name returns the format name.

type Importer

type Importer interface {
	// Name returns the human-readable name of the source format (e.g.
	// "Bitwarden", "KeePass").
	Name() string

	// Detect reports whether r contains this format. The caller must ensure
	// that r is positioned at the start; the implementation must not consume
	// more bytes than necessary for detection. For binary formats (KDBX), the
	// caller should re-open the file for Import.
	Detect(r io.Reader) bool

	// Import reads r and yields entries. The iterator stops when the reader is
	// exhausted or an error makes further progress impossible. A non-nil error
	// on the value channel means the entry may be partially usable; the caller
	// decides whether to keep it. A non-nil error as the final yield means the
	// import aborted.
	Import(r io.Reader) iter.Seq2[*Entry, error]
}

Importer reads a foreign format and yields entries as a stream.

type KeePassImporter

type KeePassImporter struct {
	// Password is the database master password. It must be set before
	// calling Import. The caller is responsible for reading it from the
	// terminal.
	Password string
}

KeePassImporter reads KDBX (KeePass 2.x) databases.

KDBX is a binary format, so Detect checks for the KDBX magic bytes and Import re-reads the file. The caller should re-open the file for Import.

The database password is read from TTY/stdin, never from argv (§3 of the project rules). Pass the password via the Password field before calling Import.

func (KeePassImporter) Detect

func (KeePassImporter) Detect(r io.Reader) bool

Detect reports whether r looks like a KDBX file by checking the magic signature bytes. The KDBX 3.1 and 4.0 signatures both start with 0x03 0xd9 0xa2 0x96.

func (*KeePassImporter) Import

func (k *KeePassImporter) Import(r io.Reader) iter.Seq2[*Entry, error]

Import decrypts the KDBX database and yields entries.

func (KeePassImporter) Name

func (KeePassImporter) Name() string

Name returns the format name.

type LastPassImporter

type LastPassImporter struct{}

LastPassImporter reads CSV exports from LastPass.

LastPass exports use columns: url,username,password,extra,name,grouping,fav The "extra" field contains notes. "grouping" maps to the store group. LastPass may encode the CSV in the system's default encoding rather than UTF-8, which is why csvReadAll handles encoding detection.

func (LastPassImporter) Detect

func (LastPassImporter) Detect(r io.Reader) bool

Detect reports whether r looks like a LastPass CSV export.

func (LastPassImporter) Import

func (LastPassImporter) Import(r io.Reader) iter.Seq2[*Entry, error]

Import reads entries from a LastPass CSV.

func (LastPassImporter) Name

func (LastPassImporter) Name() string

Name returns the format name.

type OnePasswordImporter

type OnePasswordImporter struct{}

OnePasswordImporter reads CSV exports from 1Password.

1Password exports vary by vault type. The "Login" export has columns like Title, Username, Password, URL, OTP, Notes. The "1Password 8" format uses slightly different names.

func (OnePasswordImporter) Detect

func (OnePasswordImporter) Detect(r io.Reader) bool

Detect reports whether r looks like a 1Password CSV export.

func (OnePasswordImporter) Import

Import reads entries from a 1Password CSV.

func (OnePasswordImporter) Name

func (OnePasswordImporter) Name() string

Name returns the format name.

type PassImporter

type PassImporter struct {
	// Dir is the path to the source pass store. If empty, the importer
	// reports an error at import time.
	Dir string
}

PassImporter reads entries from an existing pass or gopass store on disk. It does not decrypt; it copies the ciphertext, which the export step can then re-encrypt for the target store.

Because pass stores use the same format as binpass, this importer is also the basis for "binpass import pass /path/to/other/store".

func (PassImporter) Detect

func (PassImporter) Detect(_ io.Reader) bool

Detect reports whether the reader looks like a pass store. A pass store is identified by its directory structure (a tree of .gpg or .age files with a .gpg-id or .age-recipients file), not by a single file's content, so Detect always returns false when called on a stream. Use PassImporter with Dir set instead.

func (*PassImporter) Import

func (p *PassImporter) Import(_ io.Reader) iter.Seq2[*Entry, error]

Import walks the source store directory and yields entries. It reads the ciphertext from each entry but does not decrypt it; the Password field is left empty and the raw ciphertext is carried in a special field so that the export step can re-encrypt without needing the source store's keys.

func (PassImporter) Name

func (PassImporter) Name() string

Name returns the format name.

type PlanEntry

type PlanEntry struct {
	// StorePath is the path where the entry will be stored.
	StorePath string

	// Title is the original entry title from the source.
	Title string

	// HasPassword reports whether the entry has a non-empty password.
	HasPassword bool

	// HasTOTP reports whether the entry carries a TOTP secret.
	HasTOTP bool

	// AttachmentCount is the number of attachments.
	AttachmentCount int

	// Conflict reports that an entry with the same path already exists in the
	// store.
	Conflict bool
}

PlanEntry is the result of planning an import: it shows what will be written without actually writing anything. Used by --dry-run.

func Plan

func Plan(s *store.Store, entries []*Entry) []PlanEntry

Plan computes what an import would write without writing anything.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds known importers and resolves the right one for an input.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns a registry with the standard set of importers.

func (*Registry) ByName

func (r *Registry) ByName(name string) Importer

ByName returns the importer with the given name, or nil.

func (*Registry) Detect

func (r *Registry) Detect(peek []byte) Importer

Detect reads the beginning of r and returns the first importer that recognises the content. The caller should re-open the file for Import, because Detect consumes an unspecified number of bytes.

If no importer matches, it returns nil.

func (*Registry) DetectReader

func (r *Registry) DetectReader(rd io.Reader) (Importer, []byte, error)

DetectReader is like Detect but reads from an io.Reader. It buffers up to peekSize bytes for detection.

func (*Registry) Importers

func (r *Registry) Importers() []Importer

Importers returns all registered importers sorted by name.

func (*Registry) Register

func (r *Registry) Register(imps ...Importer)

Register adds one or more importers to the registry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL