Skip to content

Commit ab81313

Browse files
author
teycir
committed
feat(storage): migrate to D1 for encrypted blob storage
This commit migrates the encrypted blob storage mechanism from Cloudflare R2 to Cloudflare D1. This change streamlines the infrastructure, reduces operational complexity, and consolidates all data (metadata and blobs) within the D1 database for improved consistency and edge performance. BREAKING CHANGE: The maximum file size for seals has been reduced from 25MB to 750KB. This is due to Cloudflare D1's column size limitations when storing base64 encoded blobs.
1 parent 5905521 commit ab81313

13 files changed

Lines changed: 38 additions & 83 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ sequenceDiagram
203203
**✅ 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.
204204

205205
### "Can I delete or cancel a seal after creating it?"
206-
- **Timed Release:** ❌ NO. WORM storage prevents deletion.
206+
- **Timed Release:** ❌ NO. Cannot be deleted once created.
207207
- **Dead Man's Switch:** ✅ YES. Use the pulse token to burn the seal permanently.
208208

209209
### "Can I spam seal creation with bots?"

app/api/health/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getCloudflareContext } from '@opennextjs/cloudflare';
22
import { jsonResponse } from '@/lib/apiHandler';
3-
import { r2CircuitBreaker } from '@/lib/circuitBreaker';
3+
import { storageCircuitBreaker } from '@/lib/circuitBreaker';
44

55
export async function GET() {
66
try {
@@ -11,7 +11,7 @@ export async function GET() {
1111
timestamp: Date.now(),
1212
version: '0.1.0',
1313
services: {
14-
storage: r2CircuitBreaker.getState(),
14+
storage: storageCircuitBreaker.getState(),
1515
database: env?.DB ? 'operational' : 'not configured',
1616
encryption: env?.MASTER_ENCRYPTION_KEY ? 'configured' : 'missing',
1717
},

app/components/StructuredData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function StructuredData() {
2020
'Zero-trust architecture',
2121
'Split-key cryptography',
2222
'Cloudflare edge deployment',
23-
'R2 object lock storage',
23+
'D1 database storage',
2424
],
2525
screenshot: 'https://timeseal.dev/explainerimage.png',
2626
aggregateRating: {

app/faq/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function FAQPage() {
2424
<Card className="p-4 sm:p-6 md:p-8 space-y-6">
2525
<div>
2626
<h3 className="text-base sm:text-lg font-bold text-neon-green mb-2">What is the maximum file size?</h3>
27-
<p className="text-neon-green/60 text-sm">25 MB per seal (Cloudflare Pages limit).</p>
27+
<p className="text-neon-green/60 text-sm">750 KB per seal (D1 database limit with base64 encoding).</p>
2828
</div>
2929

3030
<div>
@@ -44,17 +44,17 @@ export default function FAQPage() {
4444

4545
<div>
4646
<h3 className="text-base sm:text-lg font-bold text-neon-green mb-2">Can I cancel or delete a seal?</h3>
47-
<p className="text-neon-green/60 text-sm">Dead Man&apos;s Switch seals can be burned (permanently destroyed) using the pulse token. Timed seals cannot be deleted due to WORM storage.</p>
47+
<p className="text-neon-green/60 text-sm">Dead Man&apos;s Switch seals can be burned (permanently destroyed) using the pulse token. Timed seals cannot be deleted.</p>
4848
</div>
4949

5050
<div>
5151
<h3 className="text-base sm:text-lg font-bold text-neon-green mb-2">Where is my data stored?</h3>
52-
<p className="text-neon-green/60 text-sm">Encrypted blobs are stored in Cloudflare R2 (global edge storage). Metadata is in Cloudflare D1 database.</p>
52+
<p className="text-neon-green/60 text-sm">Encrypted blobs are stored in Cloudflare D1 database. Metadata and keys are also in D1.</p>
5353
</div>
5454

5555
<div>
5656
<h3 className="text-base sm:text-lg font-bold text-neon-green mb-2">Is this really secure?</h3>
57-
<p className="text-neon-green/60 text-sm">Yes. We use AES-GCM 256-bit encryption, split-key architecture, and WORM storage. The code is open source for audit.</p>
57+
<p className="text-neon-green/60 text-sm">Yes. We use AES-GCM 256-bit encryption, split-key architecture, and database-backed storage. The code is open source for audit.</p>
5858
</div>
5959

6060
<div>

app/how-it-works/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ export default function HowItWorksPage() {
2525
<Card className="p-4 sm:p-6 md:p-8 space-y-6">
2626
<div>
2727
<h2 className="text-xl sm:text-2xl font-bold text-neon-green mb-4 flex items-center gap-2">
28-
<Lock className="w-6 h-6" /> Layer 1: The Vault (R2 Object Lock)
28+
<Lock className="w-6 h-6" /> Layer 1: The Vault (D1 Database Storage)
2929
</h2>
30-
<p className="text-neon-green/80 mb-2 text-sm sm:text-base">Immutable Storage</p>
30+
<p className="text-neon-green/80 mb-2 text-sm sm:text-base">Encrypted Storage</p>
3131
<p className="text-neon-green/60 text-sm leading-relaxed">
32-
Files are stored in Cloudflare R2 with WORM Compliance (Write Once, Read Many).
33-
This prevents deletion—even by the admin—until the unlock time expires.
32+
Files are stored encrypted in Cloudflare D1 database. The encrypted blobs are stored alongside metadata,
33+
with cryptographic enforcement preventing early access.
3434
</p>
3535
</div>
3636

@@ -68,7 +68,7 @@ export default function HowItWorksPage() {
6868
<ul className="text-neon-green/60 text-sm space-y-2">
6969
<li><strong className="text-neon-green">Algorithm:</strong> AES-GCM (256-bit)</li>
7070
<li><strong className="text-neon-green">Key Generation:</strong> Web Crypto API (CSPRNG)</li>
71-
<li><strong className="text-neon-green">Storage:</strong> Cloudflare R2 with Object Lock</li>
71+
<li><strong className="text-neon-green">Storage:</strong> Cloudflare D1 (Encrypted Blobs)</li>
7272
<li><strong className="text-neon-green">Database:</strong> Cloudflare D1 (SQLite)</li>
7373
<li><strong className="text-neon-green">Audit Trail:</strong> Immutable access logs</li>
7474
</ul>

app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export const metadata: Metadata = {
3434
'Cloudflare Workers',
3535
'zero-trust encryption',
3636
'split-key cryptography',
37-
'R2 object lock',
38-
'WORM storage',
37+
'D1 database',
38+
'encrypted blob storage',
3939
'time capsule encryption'
4040
],
4141
authors: [{ name: 'TimeSeal' }],

app/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,15 @@ export default function HomePage() {
189189
return;
190190
}
191191

192-
// Validate message length
193-
if (message.trim() && message.length > 1000000) {
194-
toast.error('Message too large (max 1MB)');
192+
// Validate message length (D1 limit: 750KB)
193+
if (message.trim() && message.length > 750000) {
194+
toast.error('Message too large (max 750KB)');
195195
return;
196196
}
197197

198-
// Validate file size
199-
if (file && file.size > 25 * 1024 * 1024) {
200-
toast.error('File too large (max 25MB)');
198+
// Validate file size (D1 limit: 750KB)
199+
if (file && file.size > 750 * 1024) {
200+
toast.error('File too large (max 750KB)');
201201
return;
202202
}
203203

app/security/page.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,8 @@ export default function SecurityPage() {
4848
</h2>
4949
<div className="space-y-4 text-neon-green/60 text-sm">
5050
<div>
51-
<p className="text-neon-green font-bold mb-2">Cloudflare R2 Object Lock (WORM)</p>
52-
<p>Encrypted blobs are stored with Write Once, Read Many compliance. Files cannot be deleted or modified until the retention period expires—even by administrators.</p>
53-
</div>
54-
<div>
55-
<p className="text-neon-green font-bold mb-2">Cloudflare D1 Database</p>
56-
<p>Metadata and Key B are stored in Cloudflare&apos;s edge database with automatic replication and encryption at rest.</p>
51+
<p className="text-neon-green font-bold mb-2">Cloudflare D1 Database Storage</p>
52+
<p>Encrypted blobs and metadata are stored in Cloudflare&apos;s edge database with automatic replication and encryption at rest.</p>
5753
</div>
5854
<div>
5955
<p className="text-neon-green font-bold mb-2">Edge Runtime</p>
@@ -88,8 +84,8 @@ export default function SecurityPage() {
8884
<p>Split-key architecture means neither the server alone nor the client alone can decrypt content.</p>
8985
</div>
9086
<div>
91-
<p className="text-neon-green font-bold mb-2 flex items-center gap-2"><CheckCircle2 className="w-4 h-4" /> Immutable Storage</p>
92-
<p>R2 Object Lock prevents premature deletion or modification of encrypted content.</p>
87+
<p className="text-neon-green font-bold mb-2 flex items-center gap-2"><CheckCircle2 className="w-4 h-4" /> Encrypted Storage</p>
88+
<p>All data stored in D1 database with encryption at rest and cryptographic access controls.</p>
9389
</div>
9490
<div>
9591
<p className="text-neon-green font-bold mb-2 flex items-center gap-2"><CheckCircle2 className="w-4 h-4" /> Client-Side Decryption</p>
@@ -113,7 +109,7 @@ export default function SecurityPage() {
113109
<li>Unauthorized early access (time-lock enforced server-side)</li>
114110
<li>Client-side time manipulation (server validates with Date.now())</li>
115111
<li>Server compromise (split-key architecture)</li>
116-
<li>Data tampering (WORM storage + AEAD)</li>
112+
<li>Data tampering (AEAD encryption + database integrity)</li>
117113
<li>Brute force attacks (256-bit keys + fingerprinted rate limiting)</li>
118114
<li>IP rotation bypass (browser fingerprinting)</li>
119115
<li>Timing attacks (response jitter)</li>

env.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
interface CloudflareEnv {
22
DB: D1Database;
33
MASTER_ENCRYPTION_KEY: string;
4-
R2_BUCKET?: R2Bucket;
54
}
65

76
declare module '@cloudflare/next-on-pages' {

lib/circuitBreaker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Circuit Breaker for R2 Operations
1+
// Circuit Breaker for Storage Operations
22
export enum CircuitState {
33
CLOSED = 'closed',
44
OPEN = 'open',
@@ -89,4 +89,4 @@ export async function withRetry<T>(
8989
throw lastError!;
9090
}
9191

92-
export const r2CircuitBreaker = new CircuitBreaker();
92+
export const storageCircuitBreaker = new CircuitBreaker();

0 commit comments

Comments
 (0)