Skip to content

Security: DailybotHQ/deepworkplan-website

Security

docs/SECURITY.md

Security Guide

Security best practices for deepworkplan.com, a static site built with Astro.

Overview

As a static site, deepworkplan.com has a different security profile than dynamic web applications. The main concerns are:

  1. Build-time secrets - Protecting sensitive data during build
  2. Client-side exposure - What data reaches the browser
  3. Third-party dependencies - Supply chain security
  4. Content security - Protecting against XSS in user-generated content

Security Principles

1. No Secrets in Client Code

Static sites ship all client-side code to users. Never include secrets in:

  • Astro component scripts
  • Svelte component logic
  • Client-side JavaScript
// ❌ BAD - Secret exposed to client
const API_KEY = 'sk_live_xxxxx';

// ✅ GOOD - Only public data on client
const SITE_URL = import.meta.env.PUBLIC_SITE_URL;

2. Build-Time vs Runtime

Astro runs code at build time (server-side) and optionally at runtime (API routes). Understand the difference:

Context Secrets Safe? Example
Build-time (.astro frontmatter) ⚠️ Careful Fetching data for static pages
API Routes (src/pages/api/) ✅ Yes Server-side endpoints
Client-side (Svelte with client:*) ❌ No Interactive components

3. Minimal Attack Surface

As a static site:

  • No database to protect
  • No user authentication
  • No session management
  • Limited server-side logic

Environment Variables

Configuration

Use .env files for environment variables:

# .env (local development - DO NOT COMMIT)
PUBLIC_SITE_URL=http://localhost:5555
PRIVATE_API_KEY=sk_xxxxx

# .env.production
PUBLIC_SITE_URL=https://deepworkplan.com

Naming Convention

  • PUBLIC_* - Safe to expose to client (e.g., PUBLIC_SITE_URL)
  • No prefix - Server-only, never reaches client
// Server-side only (build time or API routes)
const privateKey = import.meta.env.PRIVATE_API_KEY;

// Available on client
const siteUrl = import.meta.env.PUBLIC_SITE_URL;

Security Rules

  • Never commit .env files with secrets
  • Use .env.example for documentation
  • Rotate secrets if accidentally exposed
  • Use CI/CD environment variables for builds

Content Security

User-Generated Content

Blog posts are authored in Markdown/MDX. While you control the content, follow these practices:

<!-- ✅ Safe - standard markdown -->
# My Post
This is safe content.

<!-- ⚠️ Careful with raw HTML in MDX -->
<script>alert('This would execute!')</script>

Content Collection Validation

Zod schemas validate content at build time:

// src/content.config.ts
const blog = defineCollection({
  schema: z.object({
    title: z.string().max(200),  // Limit length
    description: z.string().max(500),
    // Validates structure, prevents malformed data
  }),
});

External Links

When linking to external sites:

<!-- Add rel attributes for security -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
  External Link
</a>

API Route Security

Current Endpoints

The site has minimal API routes:

Endpoint Purpose Security
/api/posts-en.json Blog search index (EN shard) Public, cached
/api/posts-es.json Blog search index (ES shard) Public, cached
/api/posts.json Blog search index (compatibility) Public, cached
/rss.xml RSS feed Public

API Route Best Practices

// src/pages/api/posts-en.json.ts (same pattern for posts-es.json)
import type { APIRoute } from 'astro';

export const GET: APIRoute = async () => {
  try {
    // Validate and sanitize any inputs
    // Return only necessary data
    const data = await getPublicPosts();
    
    return new Response(JSON.stringify(data), {
      status: 200,
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'public, max-age=3600',
      },
    });
  } catch (error) {
    // Don't expose error details
    console.error('API error:', error);
    return new Response(JSON.stringify({ error: 'Server error' }), {
      status: 500,
    });
  }
};

Dependency Security

Regular Audits

# Check for known vulnerabilities
npm audit

# Fix automatically where safe
npm audit fix

# Check for outdated packages
pnpm run ncu:check

Package Selection

When adding dependencies:

  • Check package popularity and maintenance
  • Review recent security advisories
  • Prefer well-maintained packages
  • Minimize dependencies when possible

Lock Files

Always commit pnpm-lock.yaml to ensure reproducible builds. The lockfile is consumed by corepack pnpm install --frozen-lockfile in CI and Cloudflare Pages.

Build Security

Cloudflare Pages Deployment

The site deploys to Cloudflare Pages from the dist/ folder:

pnpm run build

Security considerations:

  • Build output (docs/) contains only public content
  • No .env files in build output
  • No source maps with sensitive paths
  • HTTPS enforced by Cloudflare

Build-Time Secrets

If you need secrets during build (e.g., fetching from a CMS):

# In CI/CD, set environment variables
PRIVATE_CMS_TOKEN=xxx pnpm run build

The secret is used at build time but not included in output.

Headers and CSP

Cloudflare Pages allows custom headers. For enhanced security, consider:

Meta Tags

<!-- src/components/BaseHead.astro -->
<meta http-equiv="X-Content-Type-Options" content="nosniff" />
<meta http-equiv="X-Frame-Options" content="SAMEORIGIN" />

Future Consideration

If moving to a host with header control (Vercel, Netlify):

# Example _headers file
/*
  X-Content-Type-Options: nosniff
  X-Frame-Options: SAMEORIGIN
  Referrer-Policy: strict-origin-when-cross-origin
  Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';

Security Checklist

Before Committing

  • No secrets in code
  • No .env files committed
  • External links have rel="noopener noreferrer"
  • No unnecessary data exposed

Before Deployment

  • npm audit shows no critical vulnerabilities
  • Build output contains only public content
  • Environment variables properly configured
  • Dependencies are up to date

Periodic Review

  • Audit dependencies monthly
  • Review API routes for data exposure
  • Check for new security best practices
  • Update packages regularly

Incident Response

If a secret is accidentally committed:

  1. Rotate immediately - Generate new credentials
  2. Remove from history - Use git filter-branch or BFG Repo-Cleaner
  3. Audit usage - Check if secret was used maliciously
  4. Update documentation - Prevent recurrence

Resources

There aren't any published security advisories