Skip to content

Commit 886e7e8

Browse files
author
teycir
committed
feat(security): add timing attack prevention and content validation
Implement random jitter in seal retrieval API responses to mitigate timing attacks. Update documentation and security page to reflect timing attack protection and explain Key A security in URLs. Add UTF-8 content validation during decryption to handle potential content corruption.
1 parent b8986c0 commit 886e7e8

4 files changed

Lines changed: 21 additions & 3 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ sequenceDiagram
178178
### "Can I bypass rate limits by rotating IPs or using VPNs?"
179179
**⚠️ HARDER.** Rate limiting uses browser fingerprinting (IP + User-Agent + Language), making simple IP rotation ineffective. You'd need to change your entire browser signature.
180180

181+
### "Can I use timing attacks to detect the exact unlock time?"
182+
**❌ NO.** Server responses include random jitter (0-100ms delay) to prevent timing-based information leakage.
183+
181184
### "Why is there no user authentication?"
182185
**✅ BY DESIGN.** Authentication adds attack vectors (credential theft, phishing, password breaches, session hijacking). TimeSeal uses cryptography-only security: possession of the vault link (Key A) is the authentication. No passwords to steal, no accounts to hack.
183186

@@ -193,6 +196,9 @@ sequenceDiagram
193196
### "What if I lose the vault link?"
194197
**💀 LOST FOREVER.** Key A is in the URL hash. No Key A = No decryption. **Save your links securely.**
195198

199+
### "Is Key A in the URL hash secure?"
200+
**✅ YES, BY DESIGN.** The URL hash is never sent to the server (unlike query parameters). HTTPS protects it in transit. Browser history/bookmarks are your responsibility—treat vault links like passwords. This is the tradeoff for zero-trust, no-authentication security. Alternative approaches (server-side key storage, password protection) would defeat the entire architecture.
201+
196202
### "Can I delete or cancel a seal after creating it?"
197203
- **Timed Release:** ❌ NO. WORM storage prevents deletion.
198204
- **Dead Man's Switch:** ✅ YES. Use the pulse token to burn the seal permanently.

app/api/seal/[id]/route.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export async function GET(
4949
const metadata = await sealService.getSeal(sealId, ip);
5050

5151
if (metadata.status === 'locked') {
52+
// Add jitter to prevent timing attacks
53+
const jitter = Math.floor(Math.random() * 100);
54+
await new Promise(resolve => setTimeout(resolve, jitter));
55+
5256
return jsonResponse({
5357
id: sealId,
5458
isLocked: true,

app/security/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,16 @@ export default function SecurityPage() {
116116
<li>Data tampering (WORM storage + AEAD)</li>
117117
<li>Brute force attacks (256-bit keys + fingerprinted rate limiting)</li>
118118
<li>IP rotation bypass (browser fingerprinting)</li>
119+
<li>Timing attacks (response jitter)</li>
119120
<li>Automated abuse (Turnstile CAPTCHA)</li>
120121
<li>Replay attacks (nonce validation on pulse tokens)</li>
121122
</ul>
122123
</div>
123124
<div>
124125
<p className="text-neon-green font-bold mb-2">Not Protected Against:</p>
125126
<ul className="list-disc list-inside ml-4 space-y-1">
126-
<li>Loss of vault link (Key A is in URL hash)</li>
127+
<li>Loss of vault link (Key A is in URL hash - treat like a password)</li>
128+
<li>Browser history/bookmark exposure (inherent to client-side crypto)</li>
127129
<li>Compromised recipient device after unlock</li>
128130
<li>Cloudflare infrastructure failure</li>
129131
<li>Quantum computing attacks (future threat)</li>

app/v/[id]/page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,14 @@ function VaultPageClient({ id }: { id: string }) {
5555
const encryptedBuffer = bytes.buffer;
5656

5757
const decrypted = await decryptData(encryptedBuffer, { keyA, keyB, iv });
58-
const content = new TextDecoder().decode(decrypted);
59-
setDecryptedContent(content);
58+
59+
// Validate decrypted content is valid UTF-8
60+
try {
61+
const content = new TextDecoder('utf-8', { fatal: true }).decode(decrypted);
62+
setDecryptedContent(content);
63+
} catch {
64+
setError('Decryption succeeded but content is corrupted');
65+
}
6066
} catch (err) {
6167
console.error('Decryption failed:', err);
6268
setError('Failed to decrypt message');

0 commit comments

Comments
 (0)