Summary
sops 3.13.0 cannot decrypt INI files that were encrypted with earlier versions (tested with files written by 3.10.2). Decryption fails with:
failed to create reader for decrypting sops data key with age:
failed to read header: parsing age header: failed to read intro:
invalid armor: invalid first line: "-----BEGIN AGE ENCRYPTED FILE
The same files decrypt successfully with sops 3.12.x. YAML/JSON/.env files written by the same older version still decrypt fine on 3.13.0; the regression is INI-specific.
Root cause
Looks like a side-effect of #2120 ("Use mapstructure to (de-)serialize internal metadata", merged 2026-05-08).
In stores/ini/store.go at v3.10.2, LoadEncryptedFile went through iniSectionToMetadata, which explicitly unescaped \n sequences before handing the armored age/PGP blob to the key service:
// v3.10.2 - stores/ini/store.go
func (store *Store) iniSectionToMetadata(sopsSection *ini.Section) (stores.Metadata, error) {
metadataHash := make(map[string]interface{})
for k, v := range sopsSection.KeysHash() {
metadataHash[k] = v
}
stores.DecodeNewLines(metadataHash) // <-- the relevant step
err := stores.DecodeNonStrings(metadataHash)
...
return stores.UnflattenMetadata(metadataHash)
}
In v3.13.0 this entire path is gone. LoadEncryptedFile now delegates to the generic stores.ExtractMetadata with MetadataFlattenBelowTop, which calls treeBranchToMetadata → mapstructure on the raw values returned by gopkg.in/ini.v1. gopkg.in/ini.v1 doesn't unescape \n by default, and mapstructure doesn't either, so the armored blob arrives at the age library as a single line:
-----BEGIN AGE ENCRYPTED FILE-----\nYWdl...\n-----END AGE ENCRYPTED FILE-----\n
(literal backslash-n characters). Age then fails on the armor header.
Reproduction
- With
sops 3.10.2 (or any pre-3.13.0), age-encrypt an .ini file:
- Upgrade to
sops 3.13.0.
sops --decrypt file.ini → "invalid armor: invalid first line" error above.
- Downgrade to 3.12.x → decrypts fine.
The relevant metadata line in such a file looks like:
age__list_0__map_enc = -----BEGIN AGE ENCRYPTED FILE-----\nYWdl...\n-----END AGE ENCRYPTED FILE-----\n
Questions for maintainers
- Is it expected that v3.13.0 INI emits the same
\n-escaped armor format? If so, restoring an equivalent of DecodeNewLines in the new ExtractMetadata path (probably gated on the INI/.env stores) seems like the minimal fix.
- If the emit side now writes multi-line values differently, then INI files round-trip on 3.13.0 but old files are stranded; in that case a one-shot migration command or a documented "decrypt with 3.12, re-encrypt with 3.13" upgrade path would be helpful.
- Does the same regression affect dotenv files with armored payloads? (I didn't test.)
Environment
sops 3.13.0 (Nix, macOS arm64)
- File originally written by
sops 3.10.2
- Encryption: age (single recipient)
Workaround for now: pin to sops 3.12.x.
Summary
sops 3.13.0cannot decrypt INI files that were encrypted with earlier versions (tested with files written by 3.10.2). Decryption fails with:The same files decrypt successfully with
sops 3.12.x. YAML/JSON/.env files written by the same older version still decrypt fine on 3.13.0; the regression is INI-specific.Root cause
Looks like a side-effect of #2120 ("Use mapstructure to (de-)serialize internal metadata", merged 2026-05-08).
In
stores/ini/store.goat v3.10.2,LoadEncryptedFilewent throughiniSectionToMetadata, which explicitly unescaped\nsequences before handing the armored age/PGP blob to the key service:In v3.13.0 this entire path is gone.
LoadEncryptedFilenow delegates to the genericstores.ExtractMetadatawithMetadataFlattenBelowTop, which callstreeBranchToMetadata→ mapstructure on the raw values returned bygopkg.in/ini.v1.gopkg.in/ini.v1doesn't unescape\nby default, and mapstructure doesn't either, so the armored blob arrives at the age library as a single line:(literal backslash-n characters). Age then fails on the armor header.
Reproduction
sops 3.10.2(or any pre-3.13.0), age-encrypt an.inifile:sops 3.13.0.sops --decrypt file.ini→ "invalid armor: invalid first line" error above.The relevant metadata line in such a file looks like:
Questions for maintainers
\n-escaped armor format? If so, restoring an equivalent ofDecodeNewLinesin the newExtractMetadatapath (probably gated on the INI/.env stores) seems like the minimal fix.Environment
sops3.13.0 (Nix, macOS arm64)sops3.10.2Workaround for now: pin to
sops3.12.x.