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

> Learn how Karakeep handles user input validation with Zod schemas, helper functions, and fail-fast error propagation for robust type safety and security.

- Repository: [Karakeep App/karakeep](https://github.com/karakeep-app/karakeep)
- Tags: how-to-guide
- Published: 2026-07-07

---

**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`](https://github.com/karakeep-app/karakeep/blob/main/packages/trpc/routers/users.ts), the sign-up procedure extends a base schema to include optional fields while maintaining strict validation rules:

```typescript
// 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`](https://github.com/karakeep-app/karakeep/blob/main/packages/shared/utils/redirectUrl.ts) restricts URLs to safe patterns:

```typescript
// 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`](https://github.com/karakeep-app/karakeep/blob/main/packages/trpc/auth.ts) performs secure credential verification:

```typescript
// 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`](https://github.com/karakeep-app/karakeep/blob/main/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:

```typescript
// 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:

```typescript
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`](https://github.com/karakeep-app/karakeep/blob/main/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`](https://github.com/karakeep-app/karakeep/blob/main/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`](https://github.com/karakeep-app/karakeep/blob/main/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`](https://github.com/karakeep-app/karakeep/blob/main/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`](https://github.com/karakeep-app/karakeep/blob/main/packages/shared/utils/redirectUrl.ts) for URL validation and [`packages/trpc/auth.ts`](https://github.com/karakeep-app/karakeep/blob/main/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`](https://github.com/karakeep-app/karakeep/blob/main/packages/trpc/lib/eventLog.ts) to ensure consistent formatting, preventing sensitive internal details from leaking to clients while maintaining debuggable server-side logs.