Summary
proton-vpn-gtk-app (via python-proton-keyring-linux) writes VPN session credentials into the GNOME keyring as a single JSON secret value whose ClientKey, Certificate, and ServerPublicKey fields contain PEM blocks with literal, unescaped newlines. When the target keyring is stored in GNOME's unencrypted keyfile format (no keyring password set — a common and supported configuration, e.g. any system relying on the login keyring auto-unlocking, or one deliberately left passwordless), those raw newlines break GLib.KeyFile's parser for the entire keyring file, not just the one entry. Every other secret stored in that keyring (browser saved passwords, other apps' Safe Storage keys, etc.) becomes unreadable until the file is manually repaired, and GNOME Keyring responds by offering to silently create a brand-new empty keyring — which, if the user accepts and then relaunches apps like a browser, causes them to mint new encryption keys and permanently orphan previously-saved secrets.
This has recurred 5 times over ~3 weeks on the same machine, each time on a ProtonVPN session/cert renewal.
Environment
proton-vpn-gtk-app: 4.18.1-1 (Arch/AUR native package; confirmed identical version 4.18.1 on the Flathub flatpak build, so this is not packaging-specific)
python-proton-vpn-api-core: 5.6.10-1
python-proton-keyring-linux: 0.2.3-1
- Distro: Arch Linux (Omarchy)
- Keyring backend: gnome-keyring-daemon,
Default.keyring with no keyring password set (unencrypted keyfile-format storage — this is the config that exposes the bug)
Exact error
g-key-file-error-quark: Key file contains line "-----END PUBLIC KEY-----", "Certificate": "-----BEGIN CERTIFICATE-----" which is not a key-value pair, group, or comment (1)
Raised by GLib.KeyFile.load_from_file() when trying to parse ~/.local/share/keyrings/Default.keyring.
Root cause
Inspecting the corrupted file directly, the offending entry looks like this (secrets redacted, structure preserved):
secret={"UID": "...", "AccessToken": "...", ..., "vpn": {"vpninfo": {...}, "certificate": {"SerialNumber": "...", "ClientKeyFingerprint": "...", "ClientKey": "-----BEGIN PUBLIC KEY-----
<base64 lines with REAL newlines>
-----END PUBLIC KEY-----", "Certificate": "-----BEGIN CERTIFICATE-----
<base64 lines with REAL newlines>
-----END CERTIFICATE-----", ...}, ..., "ServerPublicKey": "-----BEGIN PUBLIC KEY-----
<base64 lines with REAL newlines>
-----END PUBLIC KEY-----"}}}
A GLib.KeyFile-format file requires every value to be a single line; multi-line string values must have their newlines escaped as \n before being written. This JSON blob was written with the PEM certificates' actual newline characters intact, so the single secret= line becomes dozens of malformed lines, and the parser gives up on the whole file.
This has occurred identically 5 times (2026-08-26, 2026-09-02 ×2, 2026-09-13, 2026-09-15), always immediately following a ProtonVPN session/cert renewal, always with the same three fields affected (ClientKey, Certificate, ServerPublicKey).
Steps to reproduce
- Use
proton-vpn-gtk-app on a system where the GNOME login/default keyring has no password set (unencrypted keyfile format).
- Sign in / let a VPN session cert renew.
- Inspect
~/.local/share/keyrings/Default.keyring — the secret= line for the Proton SSO account entry spans multiple raw lines instead of one \n-escaped line.
- Attempt to load it:
GLib.KeyFile.new().load_from_file(path, GLib.KeyFileFlags.NONE) → raises the error above.
- In practice this surfaces to the user as a "keyring file corrupted" or "an application wants to create a new keyring called Default" popup.
Suggested fix
Whatever serializes the VPN session JSON before handing it to the Secret Service API (python-proton-keyring-linux) should either:
- Ensure the underlying secret-storage call properly escapes the value for the target backend (this looks like it may be bypassing normal
libsecret/Secret Service escaping and writing closer to the raw file), or
- At minimum, avoid embedding raw multi-line PEM text in a value that's going to be stored in a line-oriented format — e.g. base64-encode or otherwise flatten
ClientKey/Certificate/ServerPublicKey before nesting them in the JSON secret.
Related existing reports
This looks like the same underlying issue behind:
- #16 "Finally found the keyring problem" (still open)
- #100 "Keyring bug still still still alive..." (still open, reporter using flatpak — confirms this isn't packaging-specific)
Neither of those pins down the actual GLib KeyFile parse failure or the raw-PEM-newline mechanism, so filing this separately with the concrete root cause in case it helps prioritize a real fix rather than another manual keyring wipe.
Workaround (for anyone hitting this)
The corruption is confined to one malformed multi-line secret= value; re-collapsing it back into a single \n-escaped line (matching every other line in the file) restores the file immediately with zero data loss to other keyring entries. No need to delete/recreate the whole keyring.
Summary
proton-vpn-gtk-app(viapython-proton-keyring-linux) writes VPN session credentials into the GNOME keyring as a single JSON secret value whoseClientKey,Certificate, andServerPublicKeyfields contain PEM blocks with literal, unescaped newlines. When the target keyring is stored in GNOME's unencrypted keyfile format (no keyring password set — a common and supported configuration, e.g. any system relying on the login keyring auto-unlocking, or one deliberately left passwordless), those raw newlines breakGLib.KeyFile's parser for the entire keyring file, not just the one entry. Every other secret stored in that keyring (browser saved passwords, other apps' Safe Storage keys, etc.) becomes unreadable until the file is manually repaired, and GNOME Keyring responds by offering to silently create a brand-new empty keyring — which, if the user accepts and then relaunches apps like a browser, causes them to mint new encryption keys and permanently orphan previously-saved secrets.This has recurred 5 times over ~3 weeks on the same machine, each time on a ProtonVPN session/cert renewal.
Environment
proton-vpn-gtk-app: 4.18.1-1 (Arch/AUR native package; confirmed identical version 4.18.1 on the Flathub flatpak build, so this is not packaging-specific)python-proton-vpn-api-core: 5.6.10-1python-proton-keyring-linux: 0.2.3-1Default.keyringwith no keyring password set (unencrypted keyfile-format storage — this is the config that exposes the bug)Exact error
Raised by
GLib.KeyFile.load_from_file()when trying to parse~/.local/share/keyrings/Default.keyring.Root cause
Inspecting the corrupted file directly, the offending entry looks like this (secrets redacted, structure preserved):
A
GLib.KeyFile-format file requires every value to be a single line; multi-line string values must have their newlines escaped as\nbefore being written. This JSON blob was written with the PEM certificates' actual newline characters intact, so the singlesecret=line becomes dozens of malformed lines, and the parser gives up on the whole file.This has occurred identically 5 times (2026-08-26, 2026-09-02 ×2, 2026-09-13, 2026-09-15), always immediately following a ProtonVPN session/cert renewal, always with the same three fields affected (
ClientKey,Certificate,ServerPublicKey).Steps to reproduce
proton-vpn-gtk-appon a system where the GNOME login/default keyring has no password set (unencrypted keyfile format).~/.local/share/keyrings/Default.keyring— thesecret=line for the Proton SSO account entry spans multiple raw lines instead of one\n-escaped line.GLib.KeyFile.new().load_from_file(path, GLib.KeyFileFlags.NONE)→ raises the error above.Suggested fix
Whatever serializes the VPN session JSON before handing it to the Secret Service API (
python-proton-keyring-linux) should either:libsecret/Secret Service escaping and writing closer to the raw file), orClientKey/Certificate/ServerPublicKeybefore nesting them in the JSON secret.Related existing reports
This looks like the same underlying issue behind:
Neither of those pins down the actual GLib KeyFile parse failure or the raw-PEM-newline mechanism, so filing this separately with the concrete root cause in case it helps prioritize a real fix rather than another manual keyring wipe.
Workaround (for anyone hitting this)
The corruption is confined to one malformed multi-line
secret=value; re-collapsing it back into a single\n-escaped line (matching every other line in the file) restores the file immediately with zero data loss to other keyring entries. No need to delete/recreate the whole keyring.