How to Handle User Input Validation in Karakeep: A Three-Layer Security Approach

Karakeep validates user input through a three-layer pipeline combining Zod schema validation, domain-specific helper functions, and fail-fast error propagation to ensure type safety and security before any data reaches the database.

Karakeep, the open-source bookmarking application, implements a robust validation strategy to protect against malformed data, injection attacks, and open-redirect vulnerabilities. Understanding how to handle user input validation in Karakeep requires examining its layered architecture that processes every API request through declarative schemas, contextual business logic, and unified error handling. This approach leverages TypeScript and tRPC to maintain type safety from the API boundary down to the database layer.

Layer 1: Schema Validation with Zod

All public API procedures in Karakeep define their inputs using Zod schemas within tRPC routers. This declarative validation enforces type safety, required fields, string length limits, enum values, and custom refinements before any business logic executes.

In packages/trpc/routers/users.ts, the sign-up procedure extends a base schema to include optional fields while maintaining strict validation rules:

// packages/trpc/routers/users.ts
.input(zSignUpSchema.safeExtend({ redirectUrl: z.string().optional() }))

The zSignUpSchema lives in shared utilities and contains canonical rules for usernames, passwords, and emails. When a request arrives, Zod automatically validates the payload against this schema, rejecting malformed data with a clear ZodError before any database interaction occurs. This schema-first approach ensures that TypeScript types remain synchronized with runtime validation.

Layer 2: Domain-Specific Validation Helpers

When generic schema rules are insufficient, Karakeep implements domain-specific validation helpers as pure functions called after the Zod layer passes. These validators handle context-aware security checks that require business logic.

Redirect URL Validation

To prevent open-redirect attacks, the validateRedirectUrl function in packages/shared/utils/redirectUrl.ts restricts URLs to safe patterns:

// packages/shared/utils/redirectUrl.ts
export function validateRedirectUrl(url: string | null | undefined): string | undefined {
  if (!url) return undefined;
  // Allow only relative paths (e.g., "/dashboard") and the karakeep:// scheme.
  if (url.startsWith("/") && !url.startsWith("//")) return url;
  if (url.startsWith("karakeep://")) return url;
  // Everything else is rejected.
  return undefined;
}

Routers invoke this helper immediately after Zod parsing to sanitize redirect parameters before they reach authentication flows or email dispatch logic.

Password Verification

The validatePassword function in packages/trpc/auth.ts performs secure credential verification:

// packages/trpc/auth.ts usage context
const user = await validatePassword(input.email, input.password, ctx.db);

This helper checks passwords against stored hashes using timing-safe comparison, enforces strength requirements, and prevents timing attacks. It is reused across both sign-up and login flows to ensure consistent security policy enforcement.

Layer 3: Fail-Fast Error Propagation

Karakeep employs a fail-fast strategy where validation errors immediately terminate request processing. When Zod or a custom validator rejects data, the system returns a standardized error response—either a ZodError or a custom InvalidInputError—before any database writes, email dispatches, or external API calls occur.

All error handling paths funnel through the unified formatting utility in packages/trpc/lib/eventLog.ts, which guarantees a consistent API contract for clients. This architecture prevents partial state changes and ensures that unsafe input never reaches core business logic.

Complete Implementation Examples

The following patterns demonstrate how to combine these three layers when building new features in the Karakeep codebase.

Validating a New Bookmark Field

When adding a bookmark with an optional redirect parameter, combine Zod schema validation with the redirect helper:

// 1️⃣ Extend the Zod schema in the router.
import { z } from "zod";
import { validateRedirectUrl } from "@karakeep/shared/utils/redirectUrl";

export const createBookmark = publicProcedure
  .input(
    z.object({
      url: z.string().url(),
      title: z.string().max(200).optional(),
      // New optional field that must be a safe relative path or karakeep:// link.
      redirectUrl: z.string().optional(),
    })
  )
  .mutation(async ({ ctx, input }) => {
    // 2️⃣ Run the domain-specific validator.
    const safeRedirect = validateRedirectUrl(input.redirectUrl);
    if (input.redirectUrl && !safeRedirect) {
      throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid redirect URL" });
    }

    // 3️⃣ Proceed with business logic using the sanitized value.
    await ctx.db.bookmarks.create({
      url: input.url,
      title: input.title,
      redirectUrl: safeRedirect,
    });
  });

Reusing Password Validation

Leverage the existing authentication helpers for credential verification:

import { validatePassword } from "@karakeep/trpc/auth";

export const login = publicProcedure
  .input(z.object({ email: z.string().email(), password: z.string() }))
  .mutation(async ({ ctx, input }) => {
    // Throws if the password does not match the stored hash.
    const user = await validatePassword(input.email, input.password, ctx.db);
    // Continue with session creation…
    return { userId: user.id };
  });

Summary

  • Schema Validation: All tRPC procedures use Zod schemas in packages/trpc/routers/ to enforce type safety and structural validation at the API boundary.
  • Domain Helpers: Specialized functions like validateRedirectUrl and validatePassword provide context-aware security checks for business-critical fields.
  • Fail-Fast Architecture: Validation errors immediately return standardized responses via packages/trpc/lib/eventLog.ts, preventing unsafe data from reaching database operations.
  • Type Safety: The combination of Zod and TypeScript ensures that validated data maintains correct types throughout the application stack.

Frequently Asked Questions

What validation library does Karakeep use?

Karakeep uses Zod for all schema validation within its tRPC API layer. Zod schemas define input requirements for every public procedure, providing both TypeScript type inference and runtime validation. The schemas are typically defined directly in router files like packages/trpc/routers/users.ts or imported from shared utility packages.

How does Karakeep prevent open redirect attacks?

The application prevents open redirects through the validateRedirectUrl function in packages/shared/utils/redirectUrl.ts. This helper strictly allows only relative paths starting with a single forward slash (e.g., /dashboard) or URLs using the custom karakeep:// scheme. Any absolute URLs pointing to external domains are rejected and returned as undefined, effectively neutralizing malicious redirect attempts.

Where are validation schemas defined in the Karakeep codebase?

Validation schemas are primarily located in packages/trpc/routers/, with central definitions for user authentication residing in packages/trpc/routers/users.ts. Domain-specific validation logic that extends beyond generic type checking lives in dedicated utility files such as packages/shared/utils/redirectUrl.ts for URL validation and packages/trpc/auth.ts for credential verification.

How does Karakeep handle validation errors in production?

When validation fails, Karakeep immediately returns a structured error response before executing any business logic. Zod validation errors propagate automatically through tRPC's error handling, while custom validators throw TRPCError instances with appropriate HTTP status codes. All errors funnel through packages/trpc/lib/eventLog.ts to ensure consistent formatting, preventing sensitive internal details from leaking to clients while maintaining debuggable server-side logs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →