ONYX v1.5-beta: Emergency PIN with a full decoy environment
Released v1.5-beta of ONYX, and one of the things we added is Emergency PIN support.
A secondary PIN opens a completely separate decoy environment instead of your real account. You configure what’s in it — chats, avatars, display names — so it looks like a normal, lived-in account. There’s no visual indicator that it’s a decoy.
The use case is straightforward: situations where you’re pressured into unlocking your messenger. The Emergency PIN is kept entirely separate from your main PIN and can be changed at any time.
- GitHub: https://github.com/wardcore-dev/onyx/releases/tag/v1.5-beta
- Website: https://onyx.wardcore.com/
Happy to answer questions.
Decoy environments only work if a) there’s plausible deniability for having the feature enabled, b) the contents of the decoy are realistic and c) the decoy environment has a consistent timeline of activity up to the present.
If any of those isn’t true, it just provides probable cause to dig deeper.
Definitely a cool feature but I’m happy that my threat model doesn’t require having an answer to someone beating the shit out of me if I don’t give them a pin 😅.
Never date someone prone to Jealousy.
What does the decoy env look like on disk? Is it two separate file systems/DBs that are decrypted by different pins?
If so, a smart attacker is just going to insist you give them both pins, and keep coercing you to give up both.
I had a decoy env for a period app I wrote, and to solve that problem I just randomly generated a new in memory DB for any failed pins. On disk, it looked like just one encrypted DB. But generating random period data is easier than chat messages. And it has its own issues, where no matter what pin you put in, you get a result, so the attacker can just keep coercing you until they get what they think is a valid result.
Do you have any doco for how you have designed the at rest and e2e encryption?
Good points, but the architecture is a bit different. It’s not two separate encrypted files — from the outside there’s no indication there are two PINs at all. The decoy environment looks like a fully functional account: its own chats, groups, contacts, message history. The attacker doesn’t know there’s a second layer because nothing tells them the first layer is a decoy.
Your approach of generating a random DB for any wrong PIN is interesting too, but it has a different problem: there’s no “correct” result, any PIN “works”, and a paranoid attacker can figure that out. Here the decoy environment is convincing precisely because it’s a real-looking account, not a placeholder.
On encryption: AES-256-GCM, keys derived via HKDF-SHA256 with salt. The code is fully open source on GitHub — that is the documentation. Separate security docs don’t make sense for me to write right now, but if the project grows — definitely yes.
So, 1 encrypted file, that can be decrypted with two different keys? How does that work?
The lack of response is clear. You don’t know how it actually works, because you’ve just been asking the robot to do it. That is unacceptable for something you are pitching as private and secure.
No, it’s not one file with two decryption keys — you’ve got the architecture slightly wrong. There’s one storage, encrypted with AES-256-GCM. The PIN doesn’t decrypt the file — it’s verified at the application level. If the real PIN matches, the real environment loads. If the decoy PIN matches, the decoy environment loads with its own data, stored in the same storage under separate keys. Two layers of data in one container, not two containers with different keys.
Where does the first decryption key come from? Does the user supply a decryption key first, and then supplies a pin? Verifying the pin at the application level means that once its decrypted, the attacker doesn’t need the pin at all, they can just read the decrypted data directly.
I’m fairly sure you are just sending these comments directly to your LLM.
On mobile the key comes from the platform keychain — tied to device unlock, not the app. On desktop it’s machine-derived. The decoy PIN threat model is coercion — someone watching you unlock. If an attacker already has filesystem access, you’ve got bigger problems than the PIN.
Machine derived decryption key is basically the same as unencrypted.
Android story is better, but you effectively hold a backdoor. You can push an update that defeats the decryption without any user interaction.
Ideally, decryption should involve the user inputting a pin or password.
Yeah, you’re right. I’ll fix this in the next update.


