Deno Deploy Security Guide for Vibe Coders

Share

Deno Deploy Security Guide for Vibe Coders

Published on January 23, 2026 - 10 min read

TL;DR

Deno Deploy provides a secure-by-default runtime with explicit permissions, but you still need to handle secrets and user data carefully. Store secrets in environment variables via the dashboard (not in code). Use Deno KV with proper key scoping to prevent data leaks between users. Validate all request input. Deno's permission model doesn't apply in Deploy (all permissions are granted), so security is entirely in your code.

Why Deno Deploy Security Matters for Vibe Coding

Deno Deploy runs your TypeScript/JavaScript at the edge globally. While Deno is known for its security-first permission model, those permissions don't apply in Deploy. Your code has full network and environment access, making secure coding practices essential.

AI tools generating Deno code often assume the CLI permission model applies. In Deploy, you're responsible for all security validation.

Environment Secrets

Store all sensitive configuration in environment variables through the Deno Deploy dashboard.

Setting Secrets

# Via deployctl CLI
deployctl env set API_KEY=sk-your-secret-key
deployctl env set DATABASE_URL=postgres://...
deployctl env set JWT_SECRET=your-jwt-secret

# Or set in the Deno Deploy dashboard:
# Project Settings > Environment Variables

Accessing Secrets in Code

// Access environment variables
const apiKey = Deno.env.get("API_KEY");
const databaseUrl = Deno.env.get("DATABASE_URL");

if (!apiKey) {
  throw new Error("API_KEY environment variable not set");
}

// Use in your handler
Deno.serve(async (req) => {
  // apiKey is available here
  const response = await fetch("https://api.example.com", {
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  return response;
});

Never Hardcode Secrets

// DANGEROUS: Secrets in code
const API_KEY = "sk-abc123";  // Committed to git!

// SAFE: Use environment variables
const API_KEY = Deno.env.get("API_KEY");

Request Validation

Validate all incoming requests before processing:

import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";

const CreateUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(1).max(100),
});

Deno.serve(async (req) => {
  const url = new URL(req.url);

  if (url.pathname === "/api/users" && req.method === "POST") {
    // Validate content type
    const contentType = req.headers.get("content-type");
    if (!contentType?.includes("application/json")) {
      return new Response("Invalid content type", { status: 415 });
    }

    // Parse and validate body
    let body: unknown;
    try {
      body = await req.json();
    } catch {
      return new Response("Invalid JSON", { status: 400 });
    }

    const result = CreateUserSchema.safeParse(body);
    if (!result.success) {
      return Response.json(
        { error: "Validation failed", details: result.error.flatten() },
        { status: 400 }
      );
    }

    // Safe to use result.data
    const { email, name } = result.data;

    // Process the request...
    return Response.json({ success: true });
  }

  return new Response("Not found", { status: 404 });
});

Deno KV Security

Deno KV is a built-in key-value store. Secure it with proper key scoping:

const kv = await Deno.openKv();

Deno.serve(async (req) => {
  const userId = await getUserId(req); // Your auth logic

  // SAFE: User-scoped keys
  const userPrefsKey = ["users", userId, "preferences"];
  const prefs = await kv.get(userPrefsKey);

  // SAFE: Setting user data
  await kv.set(["users", userId, "profile"], {
    name: "User Name",
    updatedAt: Date.now(),
  });

  // DANGEROUS: User-controlled key
  const key = new URL(req.url).searchParams.get("key");
  // const data = await kv.get([key]); // User could access any key!

  // SAFE: Validate and scope
  if (key && /^[a-z0-9-]+$/.test(key)) {
    const scopedKey = ["users", userId, key];
    const data = await kv.get(scopedKey);
    return Response.json(data.value);
  }

  return Response.json(prefs.value);
});

KV Atomic Operations

// Atomic operations for data integrity
const kv = await Deno.openKv();

async function transferCredits(fromUserId: string, toUserId: string, amount: number) {
  const fromKey = ["users", fromUserId, "credits"];
  const toKey = ["users", toUserId, "credits"];

  // Get current values
  const fromEntry = await kv.get<number>(fromKey);
  const toEntry = await kv.get<number>(toKey);

  const fromCredits = fromEntry.value ?? 0;
  const toCredits = toEntry.value ?? 0;

  if (fromCredits < amount) {
    throw new Error("Insufficient credits");
  }

  // Atomic update with version checks
  const result = await kv.atomic()
    .check(fromEntry) // Fails if value changed
    .check(toEntry)
    .set(fromKey, fromCredits - amount)
    .set(toKey, toCredits + amount)
    .commit();

  if (!result.ok) {
    throw new Error("Transaction failed, please retry");
  }

  return { success: true };
}

Authentication Patterns

import { verify } from "https://deno.land/x/djwt@v3.0.1/mod.ts";

const JWT_SECRET = Deno.env.get("JWT_SECRET");

async function getUser(req: Request) {
  const authHeader = req.headers.get("authorization");

  if (!authHeader?.startsWith("Bearer ")) {
    return null;
  }

  const token = authHeader.slice(7);

  try {
    const key = await crypto.subtle.importKey(
      "raw",
      new TextEncoder().encode(JWT_SECRET),
      { name: "HMAC", hash: "SHA-256" },
      false,
      ["verify"]
    );

    const payload = await verify(token, key);
    return payload;
  } catch {
    return null;
  }
}

Deno.serve(async (req) => {
  const user = await getUser(req);

  if (!user) {
    return new Response("Unauthorized", { status: 401 });
  }

  // User is authenticated
  return Response.json({ userId: user.sub });
});

Deno Deploy Security Checklist

  • All secrets stored in environment variables
  • No hardcoded API keys or credentials in code
  • Request input validated with Zod or similar
  • Authentication checked on protected routes
  • Deno KV keys scoped to authenticated user
  • Atomic operations used for critical data updates
  • Error responses don't leak sensitive information
  • CORS configured appropriately
  • Rate limiting on sensitive endpoints
  • No eval() or dynamic code execution with user input

File Serving Security

Deno.serve(async (req) => {
  const url = new URL(req.url);
  let path = url.pathname;

  // Prevent directory traversal
  if (path.includes("..") || path.includes("//")) {
    return new Response("Invalid path", { status: 400 });
  }

  // Normalize path
  path = path.replace(/^\/+/, "/");

  // Only serve from specific directory
  const safePath = `/static${path}`;

  // Check file exists and serve
  try {
    const file = await Deno.readFile(safePath);
    return new Response(file);
  } catch {
    return new Response("Not found", { status: 404 });
  }
});

Does Deno's permission model apply in Deploy?

No. In Deno Deploy, your code has full permissions. The --allow-net, --allow-env, etc. flags from the CLI don't apply. Security is entirely dependent on how you write your code.

::

Is Deno KV data encrypted?

Yes, Deno KV data is encrypted at rest. However, you should still scope keys properly to prevent users from accessing each other's data. For highly sensitive data, consider application-level encryption.

How do I handle different environments?

Create separate projects in Deno Deploy for development, staging, and production. Each project has its own environment variables. Never share secrets between environments.

Can I use npm packages securely?

Yes, Deno supports npm packages via npm: specifiers. However, audit your dependencies as you would in Node.js. Prefer well-maintained packages and pin versions to avoid supply chain attacks.

::

What CheckYourVibe Detects

When scanning your Deno Deploy project, CheckYourVibe identifies:

  • Hardcoded secrets and API keys
  • Missing request validation
  • Deno KV keys without user scoping
  • Path traversal vulnerabilities
  • Missing authentication on protected routes
  • Unsafe use of eval() or dynamic imports

Run npx checkyourvibe scan to catch these issues before they reach production.

Tool & Platform Guides

Deno Deploy Security Guide for Vibe Coders