LinkDen
Using LinkDen

Security

How LinkDen protects your data with input sanitization, secure setup, and authentication.

Security

LinkDen includes built-in protections against common web attacks. Most security measures work automatically -- this page explains what's covered and what you can configure.

First-User-Is-Admin

LinkDen uses a simple, secure setup model:

  1. Navigate to /admin/setup after deployment to create your admin account.
  2. The first user to register becomes the admin.
  3. After the first user is created, registration is permanently locked -- the API returns 403 "Registration is closed" for any subsequent signup attempts.
  4. If you visit /admin/login before an account exists, you'll be redirected to the setup page.

This eliminates the need for setup tokens or invitation codes while ensuring only you can create the admin account.

File Upload Restrictions

Image uploads are validated server-side:

  • Max file size: 5 MB — enforced from the Content-Length header before the body is buffered, so oversized uploads are rejected without loading them into memory.
  • Allowed extensions: jpg, jpeg, png, gif, webp, ico
  • Allowed MIME types: image/jpeg, image/png, image/gif, image/webp, image/x-icon, image/vnd.microsoft.icon
  • Magic-byte check: the file's actual signature must match the claimed extension — an HTML or script file renamed to .png is rejected even if its client MIME says image/png.

Uploads that fail validation are rejected with descriptive error messages (400 for invalid type or content mismatch, 413 for oversized files). When an image is replaced, the previous R2 object is deleted; a daily retention sweep removes any images no longer referenced by a setting or block.

Settings Key Allowlist

The settings API only accepts known setting keys. Attempts to read or write arbitrary keys are rejected by Zod validation. This prevents attackers from injecting unexpected configuration values even if they obtain a valid session.

Input Sanitization

LinkDen sanitizes all user-provided content on the server side before saving it. This protects against stored XSS attacks even if someone bypasses frontend validation.

What gets sanitized:

  • Text fields (profile name, bio, titles, etc.) -- HTML tags are stripped and length limits are enforced.
  • URLs -- Validated to ensure they use http:// or https:// protocols. Dangerous protocols like javascript: are rejected.
  • Colors -- Must match #RRGGBB hex format.
  • Custom CSS -- Script tags, expression() calls, javascript: URLs, @import directives, data: URLs, and backslash escape sequences are blocked.
  • Contact form submissions -- All text fields are stripped of HTML before storage.
  • Embed URLs -- Validated against platform-specific patterns (e.g., YouTube, Spotify, SoundCloud).

Authentication

LinkDen uses Better Auth for authentication with email and password.

  • Sessions are signed with the BETTER_AUTH_SECRET environment variable.
  • All admin routes require a valid session.
  • Public routes are limited to page data, contact submissions, analytics tracking, vCard downloads, and setup.

CORS

The API server checks the request origin against the CORS_ORIGIN environment variable. Only requests from your configured frontend URL are accepted.

Make sure CORS_ORIGIN matches your frontend URL exactly (including protocol, no trailing slash).

CAPTCHA

The contact form supports optional CAPTCHA verification via Cloudflare Turnstile or Google reCAPTCHA. When configured, tokens are validated server-side before accepting submissions. See Contact Form for setup details.

Rate Limiting

When deployed to Cloudflare Workers, LinkDen uses Cloudflare Rate Limiting (via Alchemy bindings) to protect all API endpoints. Four independent rate limit buckets are applied:

BindingProtectsPurpose
RL_AUTH/auth/* routesPrevents brute-force login and password-reset attacks
RL_STRICTSensitive mutationsExtra protection for admin write operations
RL_UPLOAD/api/uploadLimits file upload frequency
RL_PUBLICPublic page + analyticsPrevents scraping and analytics flooding

Rate limit thresholds are configured in packages/infra/src/alchemy.run.ts and apply automatically on deployment. No additional configuration is needed. Limits are not active in local development.

On this page