agalwood/Motrix · error · SecretStoreError
plugin.lifecycle.secrets_invalid_token
plugin.lifecycle.secrets_invalid_token
Error message
secret token has invalid format: expected prefix "${PREFIX}" What it means
Thrown by LibsodiumSecretStore.decrypt when the supplied token does not start with the expected PREFIX (`"box:"`). Every token this store produces is shaped `box:<base64(nonce)>:<base64(ciphertext)>`, so a missing prefix means the value was not produced by this store — it is plaintext, a legacy `safe:` token from the deprecated Electron SafeStorage backend, or corrupted.
Source
Thrown at src/core/plugin/secret-store-libsodium.ts:110
}
}
available(): boolean {
return true
}
async encrypt(plaintext: string): Promise<string> {
await sodium.ready
const nonce = sodium.randombytes_buf(NONCE_BYTES)
const ct = sodium.crypto_secretbox_easy(plaintext, nonce, this.key)
const nonceb64 = Buffer.from(nonce).toString('base64')
const ctb64 = Buffer.from(ct).toString('base64')
return `${PREFIX}${nonceb64}:${ctb64}`
}
async decrypt(token: string): Promise<string> {
if (!token.startsWith(PREFIX)) {
throw new SecretStoreError(
'plugin.lifecycle.secrets_invalid_token',
`secret token has invalid format: expected prefix "${PREFIX}"`
)
}
const rest = token.slice(PREFIX.length)
const colonIdx = rest.indexOf(':')
if (colonIdx === -1) {
throw new SecretStoreError(
'plugin.lifecycle.secrets_invalid_token',
'secret token has invalid format: missing nonce/ciphertext separator'
)
}
const nonceb64 = rest.slice(0, colonIdx)
const ctb64 = rest.slice(colonIdx + 1)
if (!nonceb64 || !ctb64) {View on GitHub (pinned to 1a708ee577)
Solutions
- If migrating from a legacy store, re-encrypt the plaintext through LibsodiumSecretStore.encrypt once and persist the new `box:` token.
- If the field should be plaintext, do not route it through decrypt().
- Confirm only LibsodiumSecretStore (or FailingSecretStore) is used to write/read the field across all runtimes.
Example fix
// before — legacy/plaintext value fed to decrypt
await store.decrypt('my-api-key')
// after — re-encrypt once, then store the resulting token
const token = await store.encrypt('my-api-key')
await saveSettings({ apiKey: token })
// later: await store.decrypt(settings.apiKey) Defensive patterns
Strategy: validation
Validate before calling
const BOX_PREFIX = 'box:'
function isLikelyBoxToken(s: unknown): boolean {
return typeof s === 'string' && s.startsWith(BOX_PREFIX)
}
if (!isLikelyBoxToken(settings.apiKey)) {
// value is plaintext or legacy; re-encrypt before calling decrypt
settings.apiKey = await store.encrypt(settings.apiKey)
} Type guard
function isBoxToken(s: unknown): s is string {
return typeof s === 'string' && s.startsWith('box:')
} Try / catch
try {
await store.decrypt(token)
} catch (e) {
if (e instanceof SecretStoreError && e.code === 'plugin.lifecycle.secrets_invalid_token') {
// token was not produced by this store — migrate/re-encrypt, then retry
} else throw e
} Prevention
- Always persist only tokens returned by LibsodiumSecretStore.encrypt (prefix 'box:').
- On store migrations, re-encrypt legacy/plaintext values once and persist the new token.
- Use the same store implementation across all runtimes that read a given secret field.
When it happens
Trigger: decrypt(token) where `!token.startsWith('box:')`. Fires when reading an old config secret that predates the libsodium store, when the secret field holds raw plaintext, or when a different SecretStore implementation wrote the value.
Common situations: Upgrading from the deprecated Electron keychain-backed SafeStorageSecretStore whose tokens used a `safe:` prefix. Manual edit of appSettings JSON replaced an encrypted value with plaintext. Two stores (env-seed vs lockbox) — value encrypted under one key source, decrypted under another runtime with no matching key.
Related errors
- plugin.lifecycle.secrets_decrypt_failed
- plugin.lifecycle.secrets_seed_missing
- PluginRuntimeFault
- PluginManifestInvalid
- plugin.capability.bad_args
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/19ebdf1fb68178ff.
Report an issue: GitHub.