After shipping v0.1.0 I did what most developers do after a release — I opened my own app and started poking around.
The values were ciphertext. Good. But the keys were sitting right there in plain English. auth_state. cart_items. pending_payment. Anyone who opened DevTools knew exactly what I was keeping track of, even if they couldn't read the contents. That shouldn't have bothered me as much as it did. But I couldn't let it go.
So I kept going.
Your keys now mean nothing to anyone but you
tessera now runs every key name through HMAC-SHA-256 before it touches storage. What you call cart_items, tessera stores as t_3a9f7c2e. Close DevTools, reopen it, and all you see is:
t_3a9f7c2e → <ciphertext>
t_b2d4f110 → <ciphertext>
t_03e8a5cc → <ciphertext>
The mapping only exists in memory, derived from your passcode. Lock the vault — it's gone.
Some of those entries are fake
Here's the thing I'm most pleased with: not all of those entries are real. tessera automatically plants honey keys — decoys that look exactly like real values. Same key format, same ciphertext format. Completely indistinguishable.
Real code never touches them. Only something enumerating your storage and guessing would. That's the tripwire.
vault.on('honey-hit', (event) => {
// something is probing your storage
});
Values that delete themselves
Some data shouldn't outlive its purpose. A one-time code. A recovery token. A payment session. v0.1.1 lets values carry their own expiry:
vault.local.setItem('one_time_code', value, {
ttl: 30_000, // gone after 30 seconds
maxReads: 1, // gone after first read
});
There's no background timer running. The check happens at read time — the moment something requests the value, tessera looks at the write timestamp and acts. If it's expired, it wipes before returning anything. A timer can be cleared by an attacker. A check on read cannot.
Don't want to configure every key manually? Sensitivity presets have you covered:
vault.local.setItem('recovery_code', value, { sensitivity: 'critical' });
// 5 minute TTL, 3 max reads, wiped at first sign of trouble
It notices things that shouldn't be happening
The last thing I added was a suspicion engine. If reads start coming in faster than any human could trigger them, tessera notices. If an HMAC check fails on a read — meaning the value was touched outside the API — tessera notices that too.
You decide what happens:
Tessera.unlock('abc123', {
suspicion: {
rateLimit: { callsPerSecond: 10 },
onSuspicion: 'lock',
},
});
lock, wipe, or throw. tessera just makes sure something happens.
The encryption in v0.1.0 was the obvious part. v0.1.1 is all the stuff I couldn't stop thinking about after — the layers that make it hard to learn anything useful from your storage even when someone already has full read access.
npm install @mrtinkz/tessera
GitHub — feedback always welcome.
Top comments (0)