Skip to content

Commit 5158795

Browse files
author
teycir
committed
fix(pulse, security): resolve critical DMS pulse and harden system
This release addresses critical Dead Man's Switch (DMS) pulse bugs, implements significant security enhancements, and refines the warrant canary. Fixed: - Critical DMS pulse bug causing 500 errors on subsequent pulses - Pulse token URL encoding issues leading to 404 errors - Public pulse URL security leak from vault page - Nonce validation reordered after token validation to prevent DoS - Incorrect pulse interval calculation - Transaction safety for seal creation and burn operations - MockDatabase consistency with production behavior Added: - Comprehensive error logging with ErrorLogger utility - New pulse token generation after each successful pulse - URL encoding for pulse tokens in display contexts - Transaction rollback for database operations - Enhanced warrant canary page with detailed explanations - Input validation for pulse interval (1-30 days) Removed: - Admin canary update endpoint and management page - Public pulse URL display from vault page
1 parent d761856 commit 5158795

15 files changed

Lines changed: 279 additions & 227 deletions

File tree

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,26 @@ sequenceDiagram
194194

195195
See [HARDENING.md](docs/HARDENING.md) for full details.
196196

197+
### 🕊️ Warrant Canary - Transparency by Design
198+
199+
**What is it?** A warrant canary is a method to inform users that a service has NOT received secret government requests. If the canary disappears or stops updating, it signals potential compromise.
200+
201+
**How it works:**
202+
- Visit [/canary](https://timeseal.dev/canary) to see live transparency status
203+
- Page auto-generates with current date on every visit
204+
- Lists all security checkpoints (no warrants, no gag orders, no data requests, etc.)
205+
- If page shows outdated date or returns error, assume compromise
206+
207+
**Why it matters:** Some government requests come with gag orders preventing disclosure. By regularly stating we have NOT received such requests, we can signal compromise by simply stopping updates (which is legal even under gag orders).
208+
209+
**Technical implementation:**
210+
- Server-side rendered on every request (no stored file to tamper with)
211+
- No database or manual updates required
212+
- Open source code publicly auditable
213+
- Distributed on Cloudflare Workers edge network
214+
215+
**Verification:** Bookmark the canary page and check it monthly. The date should always be current.
216+
197217
---
198218

199219
### "Can I just change my computer's clock to unlock it early?"
@@ -332,7 +352,15 @@ See [LICENSE](LICENSE) for full terms.
332352

333353
## 🔮 Roadmap
334354

335-
**Recently Implemented (v0.6.0):**
355+
**Recently Implemented (v0.6.1):**
356+
- ✅ Dead Man's Switch Pulse Fix - Resolved 500 error on repeated pulses
357+
- ✅ Pulse Token URL Encoding - Fixed 404 errors with special characters
358+
- ✅ Security Hardening - Removed public pulse URL exposure
359+
- ✅ Error Logging System - Comprehensive debugging with ErrorLogger
360+
- ✅ Transaction Safety - Rollback mechanisms for database operations
361+
- ✅ Warrant Canary Cleanup - Removed broken admin endpoints
362+
363+
**Recently Implemented (v0.6.0):****
336364
- ✅ Security Hardening - Memory protection, extension detection, warrant canary
337365
- ✅ Built-in Warrant Canary - Auto-updating transparency at /canary
338366
- ✅ Security Dashboard - Real-time browser extension warnings

app/admin/canary/page.tsx

Lines changed: 0 additions & 94 deletions
This file was deleted.

app/api/admin/update-canary/route.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

app/api/cron/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { jsonResponse } from "@/lib/apiHandler";
44
export async function GET(request: NextRequest) {
55
// Verify cron secret
66
const authHeader = request.headers.get("authorization");
7-
const cronSecret = process.env.CRON_SECRET || "change-me";
7+
const cronSecret = process.env.CRON_SECRET;
8+
9+
if (!cronSecret || cronSecret === '' || cronSecret === 'change-me') {
10+
return jsonResponse({ error: "CRON_SECRET not configured" }, 500);
11+
}
812

913
if (authHeader !== `Bearer ${cronSecret}`) {
1014
return jsonResponse({ error: "Unauthorized" }, 401);

app/api/metrics/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { handleMetricsRequest } from '@/lib/metrics';
44
const METRICS_SECRET = process.env.METRICS_SECRET;
55

66
export async function GET(request: NextRequest) {
7-
if (!METRICS_SECRET || METRICS_SECRET === 'dev-secret') {
7+
if (!METRICS_SECRET || METRICS_SECRET === '' || METRICS_SECRET === 'dev-secret') {
88
return new Response('Metrics disabled', { status: 404 });
99
}
1010

app/api/pulse/route.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ export async function POST(request: NextRequest) {
1212
return createErrorResponse(ErrorCode.INVALID_INPUT, 'Pulse token required');
1313
}
1414

15+
// Validate newInterval if provided
16+
if (newInterval !== undefined) {
17+
if (typeof newInterval !== 'number' || newInterval <= 0 || newInterval > 30) {
18+
return createErrorResponse(ErrorCode.INVALID_INPUT, 'Pulse interval must be between 1 and 30 days');
19+
}
20+
}
21+
1522
const sealService = container.sealService;
1623
const result = await sealService.pulseSeal(pulseToken, ip, newInterval);
1724

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,12 @@ export async function GET(
5555
const jitter = Math.floor(Math.random() * 100);
5656
await new Promise(resolve => setTimeout(resolve, jitter));
5757

58-
const pulseToken = metadata.isDMS ? (await container.database.getSeal(sealId))?.pulseToken : undefined;
59-
const pulseUrl = pulseToken ? `${new URL(request.url).origin}/pulse/${pulseToken}` : undefined;
60-
6158
return jsonResponse({
6259
id: sealId,
6360
isLocked: true,
6461
unlockTime: metadata.unlockTime,
6562
timeRemaining: metadata.unlockTime - Date.now(),
6663
isDMS: metadata.isDMS,
67-
pulseUrl,
6864
});
6965
}
7066

app/canary/page.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,31 @@ export default function CanaryPage() {
6161
</div>
6262

6363
{/* Info */}
64-
<div className="cyber-card p-6 bg-dark-bg/50">
64+
<div className="cyber-card p-6 bg-dark-bg/50 mb-6">
6565
<div className="flex items-start gap-3">
6666
<AlertTriangle className="w-5 h-5 text-neon-green/60 flex-shrink-0 mt-0.5" />
6767
<div className="text-sm text-neon-green/60 space-y-2">
68-
<p><strong className="text-neon-green">How this works:</strong> This page is automatically generated. If you can see this page, TimeSeal infrastructure is operational and uncompromised.</p>
69-
<p><strong className="text-neon-green">What to watch for:</strong> If this page returns an error, shows outdated information, or any checkmark is missing, assume compromise.</p>
70-
<p><strong className="text-neon-green">Verification:</strong> This canary updates automatically with each page load. The date above should always be current.</p>
68+
<p><strong className="text-neon-green">What is a Warrant Canary?</strong> A warrant canary is a method to inform users that a service has NOT received secret government requests (warrants, subpoenas, gag orders). If the canary disappears or stops updating, it signals potential compromise.</p>
69+
<p><strong className="text-neon-green">How this works:</strong> This page is automatically generated on every visit with the current date. No manual updates needed. If you can see this page with today&apos;s date, TimeSeal infrastructure is operational and uncompromised.</p>
70+
<p><strong className="text-neon-green">What to watch for:</strong> If this page returns an error, shows outdated information, any checkmark is missing, or the date is not current, assume compromise.</p>
71+
<p><strong className="text-neon-green">Why it matters:</strong> Some government requests come with gag orders preventing disclosure. By regularly stating we have NOT received such requests, we can signal compromise by simply stopping updates (which is legal even under gag orders).</p>
72+
<p><strong className="text-neon-green">Verification:</strong> Bookmark this page and check it monthly. The date should always be current when you visit.</p>
7173
</div>
7274
</div>
7375
</div>
7476

77+
{/* Technical Details */}
78+
<div className="cyber-card p-6 bg-dark-bg/50">
79+
<h3 className="text-lg font-bold text-neon-green mb-4">Technical Implementation</h3>
80+
<div className="text-sm text-neon-green/60 space-y-2">
81+
<p><strong className="text-neon-green">Auto-Generated:</strong> This page is rendered server-side on every request with the current timestamp</p>
82+
<p><strong className="text-neon-green">No Database:</strong> No stored canary file that could be seized or tampered with</p>
83+
<p><strong className="text-neon-green">Open Source:</strong> Code is publicly auditable on GitHub</p>
84+
<p><strong className="text-neon-green">Edge Deployed:</strong> Runs on Cloudflare Workers distributed infrastructure</p>
85+
<p><strong className="text-neon-green">Legal Compliance:</strong> Stopping updates is legal even under gag orders (we simply don&apos;t make false statements)</p>
86+
</div>
87+
</div>
88+
7589
{/* Footer */}
7690
<div className="mt-8 text-center">
7791
<a href="/" className="text-neon-green/60 hover:text-neon-green text-sm transition-colors">

0 commit comments

Comments
 (0)