FiloSottile/age · error

not a plugin identity: %v

Error message

not a plugin identity: %v

What it means

ParseIdentity decodes a bech32 string and requires the human-readable part to start with 'AGE-PLUGIN-' and end with '-'. If the prefix/suffix check fails, the library rejects the string as not being a plugin identity encoding. This guards against passing native age identities (e.g. AGE-SECRET-KEY-...) or arbitrary strings to the plugin API.

Source

Thrown at plugin/encode.go:35

// EncodeIdentity encodes a plugin identity string for a plugin with the given
// name. If the name is invalid, it returns an empty string.
func EncodeIdentity(name string, data []byte) string {
	if !validPluginName(name) {
		return ""
	}
	s, _ := bech32.Encode("AGE-PLUGIN-"+strings.ToUpper(name)+"-", data)
	return s
}

// ParseIdentity decodes a plugin identity string. It returns the plugin name
// in lowercase and the encoded data.
func ParseIdentity(s string) (name string, data []byte, err error) {
	hrp, data, err := bech32.Decode(s)
	if err != nil {
		return "", nil, fmt.Errorf("invalid identity encoding: %v", err)
	}
	if !strings.HasPrefix(hrp, "AGE-PLUGIN-") || !strings.HasSuffix(hrp, "-") {
		return "", nil, fmt.Errorf("not a plugin identity: %v", err)
	}
	name = strings.TrimSuffix(strings.TrimPrefix(hrp, "AGE-PLUGIN-"), "-")
	name = strings.ToLower(name)
	if !validPluginName(name) {
		return "", nil, fmt.Errorf("invalid plugin name: %q", name)
	}
	return name, data, nil
}

// EncodeRecipient encodes a plugin recipient string for a plugin with the given
// name. If the name is invalid, it returns an empty string.
func EncodeRecipient(name string, data []byte) string {
	if !validPluginName(name) {
		return ""
	}
	s, _ := bech32.Encode("age1"+strings.ToLower(name), data)
	return s
}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Verify the string is a plugin identity: it must be bech32 and its hrp must match AGE-PLUGIN-<name>- exactly.
  2. If it is a native age identity (AGE-SECRET-KEY-...), parse it with the age/crypto package instead of the plugin package.
  3. Restore the trailing '-' if the string was truncated.
  4. Call ParseIdentity only on identity strings produced by EncodeIdentity or the plugin itself.

Example fix

// before
name, data, err := plugin.ParseIdentity("AGE-SECRET-KEY-1QQQQ...")
// after
if strings.HasPrefix(idStr, "AGE-SECRET-KEY-") { /* use native age identity parser */ }
name, data, err := plugin.ParseIdentity("AGE-PLUGIN-FROOD-1QQQQ...")
Defensive patterns

Strategy: validation

Validate before calling

func looksLikePluginIdentity(s string) bool {
	hrp, _, err := bech32.Decode(s)
	return err == nil && strings.HasPrefix(hrp, "AGE-PLUGIN-") && strings.HasSuffix(hrp, "-")
}

Type guard

func isPluginIdentity(s string) bool { return looksLikePluginIdentity(s) }

Try / catch

name, data, err := plugin.ParseIdentity(s)
if err != nil {
	if strings.Contains(err.Error(), "not a plugin identity") { return fmt.Errorf("%q is not a plugin identity (expected AGE-PLUGIN-...)", s) }
	return err
}

Prevention

When it happens

Trigger: Calling plugin.ParseIdentity (directly or via plugin.NewIdentity) with a bech32 string whose hrp is not AGE-PLUGIN-<name>- — e.g. a native 'AGE-SECRET-KEY-1...' identity, an 'age1...' recipient, or a plugin string missing the trailing dash.

Common situations: Developers confuse native age identity files with plugin identity strings; pass a recipient string where an identity is expected; hand-truncate or reformat a plugin identity losing the trailing '-'; paste strings from docs with wrong case for the hrp (bech32 hrp is lowercase, but the plugin convention uses AGE-PLUGIN-).

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/48396603d47195d8. Report an issue: GitHub.