Solanasis Website — Security Hardening Guide

What this is: Post-deployment security checklist and configuration reference. Covers both code-level hardening (handled by Claude Code) and infrastructure hardening (requires dashboard access).

Companion docs:

  • Human setup guide: 01-human-setup-guide.md
  • Build plan: 02-claude-build-plan.md
  • Security hardening plan (code changes): solanasis-site/SECURITY-HARDENING-PLAN.md
  • Credentials playbook: solanasis-credentials-security-playbook.md

Last updated: 2026-03-08


What’s Already Done (Code-Level — Automated)

These are implemented in the codebase and deployed automatically with every push. No manual action needed.

ProtectionImplementationFiles
HSTSStrict-Transport-Security: max-age=31536000; includeSubDomainspublic/_headers
ClickjackingX-Frame-Options: DENYpublic/_headers
MIME sniffingX-Content-Type-Options: nosniffpublic/_headers
Referrer leakageReferrer-Policy: strict-origin-when-cross-originpublic/_headers
Browser API lockdownPermissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()public/_headers
Content Security PolicyRestricts script/style/connect/frame sources to known originspublic/_headers
CORS (API routes)Server-side Access-Control-Allow-Origin: https://solanasis.comsrc/lib/api-helpers.ts
Input length limitsName: 200, email: 254, company: 200, phone: 30, message: 5000 charssrc/lib/api-helpers.ts
Email validationRFC-aligned regex (rejects malformed addresses)src/lib/api-helpers.ts
CAPTCHACloudflare Turnstile on contact form + newslettersrc/components/ContactForm.astro, NewsletterSignup.astro
HoneypotHidden _url field catches botssrc/components/ContactForm.astro, src/pages/api/send-email.ts
HTML escapingAll user input escaped before email templatessrc/lib/api-helpers.ts
Contact obfuscationEmail/phone assembled via JS on hover (not in HTML source)src/components/Footer.astro
Secret separationAPI keys in Cloudflare Pages secrets, not in codewrangler.toml comments
CI/CD pinningAll GitHub Actions pinned to commit SHAs.github/workflows/*.yml
TLSTLS 1.3 with AES-256-GCM (Cloudflare-managed)Automatic
HTTP→HTTPS301 redirect (Cloudflare-managed)Automatic
E2E tests77 tests including security header validationtests/e2e/security-headers.spec.ts

Manual Hardening Checklist (Dashboard Access Required)

These steps require human access to Cloudflare Dashboard and/or Brevo Dashboard. They cannot be automated via code.

1. Cloudflare WAF Rate Limiting (HIGH PRIORITY)

Protects API endpoints from abuse and Brevo quota exhaustion.

  • Go to Cloudflare Dashboardsolanasis.comSecurityWAF
  • Click Rate limiting rulesCreate rule
  • Configure:
    • Rule name: API rate limit
    • When incoming requests match: URI Path contains /api/
    • Rate: 10 requests per 1 minute
    • Counting expression: Same as rule expression
    • Mitigation: Block for 60 seconds
  • Click Deploy
  • Verify: submit the contact form rapidly 11+ times — the 11th should be blocked

2. DKIM for Brevo Email (HIGH PRIORITY)

Brevo DKIM is now live on solanasis.com, but this section is still the right verification pattern for future changes or domain migrations.

  • Go to BrevoSettingsSenders, Domains & Dedicated IPsDomains tab
  • Click on solanasis.com (or add it if not there)
  • Brevo shows DKIM CNAME records to add — copy them
  • Go to Cloudflare Dashboardsolanasis.comDNSRecords
  • Add each CNAME record Brevo provides. Do not assume mail._domainkey; use the exact selectors Brevo shows.
  • Go back to Brevo → click Verify (allow 5-30 minutes for DNS propagation)
  • Verify: green checkmark appears next to DKIM in Brevo
  • Current status (verified 2026-03-28):
    • brevo1._domainkey.solanasis.com resolves through Brevo to a live DKIM key
    • brevo2._domainkey.solanasis.com resolves through Brevo to a live DKIM key
    • google._domainkey.solanasis.com also publishes a DKIM key for Google Workspace

3. www → Apex Redirect (MEDIUM)

Currently www.solanasis.com serves full content instead of redirecting to solanasis.com. This causes SEO dilution.

  • Go to Cloudflare Dashboardsolanasis.comRulesRedirect Rules
  • Click Create rule
  • Configure:
    • Rule name: www to apex
    • When incoming requests match: Hostname equals www.solanasis.com
    • Then: Dynamic redirect
    • Expression: concat("https://solanasis.com", http.request.uri.path)
    • Status code: 301 (Permanent)
    • Preserve query string: Yes
  • Click Deploy
  • Verify: curl -sI https://www.solanasis.com → should return 301 with Location: https://solanasis.com/

4. CAA DNS Record (MEDIUM)

Restricts which Certificate Authorities can issue certificates for your domain.

  • Go to Cloudflare Dashboardsolanasis.comDNSRecords
  • Add two CAA records:
    • Type: CAA, Name: @, Tag: issue, Value: pki.goog (Google Trust Services — current cert issuer)
    • Type: CAA, Name: @, Tag: issue, Value: letsencrypt.org (Cloudflare’s backup CA)
  • Verify: nslookup -type=CAA solanasis.com → shows both issuers

5. DMARC Escalation (MEDIUM)

Current DMARC policy is p=none (monitoring only, no enforcement). Spoofed emails pass through.

  • DKIM is already verified as of 2026-03-28; use that as the prerequisite checkpoint
  • Go to Cloudflare Dashboardsolanasis.comDNSRecords
  • Find the TXT record for _dmarc.solanasis.com
  • Edit: change p=none to p=quarantine
  • Save
  • After 2-4 weeks of monitoring (no legitimate emails quarantined), escalate to p=reject
  • Verify: nslookup -type=TXT _dmarc.solanasis.com → shows p=quarantine

6. SPF Hardfail (LOW)

Current SPF ends with ~all (softfail). Changing to -all (hardfail) makes spoofed emails more likely to be rejected.

  • DKIM is already verified as of 2026-03-28; use that as the prerequisite checkpoint
  • Go to Cloudflare Dashboardsolanasis.comDNSRecords
  • Find the TXT record for solanasis.com that starts with v=spf1
  • Edit: change ~all to -all at the end
  • Save
  • Verify: nslookup -type=TXT solanasis.com → SPF record ends with -all

Current DNS Security Status

Run these commands to verify the current state:

# TLS certificate (should be TLS 1.3, Google Trust Services)
echo | openssl s_client -connect solanasis.com:443 -servername solanasis.com 2>/dev/null | grep "Protocol\|Cipher"
 
# HSTS header
curl -sI https://solanasis.com | grep strict-transport
 
# SPF record (should include spf.brevo.com)
nslookup -type=TXT solanasis.com | grep spf
 
# DMARC record (check enforcement level)
nslookup -type=TXT _dmarc.solanasis.com
 
# DKIM records (Brevo + Google)
nslookup -type=CNAME brevo1._domainkey.solanasis.com
nslookup -type=CNAME brevo2._domainkey.solanasis.com
nslookup -type=TXT google._domainkey.solanasis.com
 
# CAA records (restrict cert issuers)
nslookup -type=CAA solanasis.com
 
# Security headers
curl -sI https://solanasis.com | grep -iE "strict-transport|permissions-policy|x-frame|x-content|referrer|content-security"
 
# API CORS (should NOT be wildcard *)
curl -s -X POST https://solanasis.com/api/send-email -H "Content-Type: application/json" -d '{}' -D - -o /dev/null 2>/dev/null | grep access-control

CSP Reference (What Each Directive Allows)

DirectiveAllowed OriginsWhy
default-src'self'Baseline: only same-origin resources
script-src'self' 'unsafe-inline' + Cloudflare Turnstile + Cal.com + UmamiInline scripts needed for Astro build output + 3rd-party integrations
style-src'self' 'unsafe-inline'Inline styles needed for Cal.com modal Shadow DOM
img-src'self' data:Self-hosted images + data URIs for small inline images
connect-src'self' + Turnstile + Cal.com API + Umami API gatewayAPI calls to our server + 3rd-party verification/analytics
frame-srcTurnstile + Cal.comCAPTCHA widget + booking modal iframes

Accepted risks:

  • unsafe-inline in script-src/style-src is required for Astro’s build output and Cal.com’s Shadow DOM styling. Nonce-based CSP would require Astro middleware (tracked in FUTURE-SUGGESTIONS.md).
  • No SRI (Subresource Integrity) on third-party scripts because Cal.com and Turnstile update their scripts frequently, which would break integrity hashes.

Secrets Management

SecretWhere StoredHow Accessed
BREVO_API_KEYCloudflare Pages secretslocals.runtime.env at request time
TURNSTILE_SECRET_KEYCloudflare Pages secretslocals.runtime.env at request time
BREVO_LIST_IDCloudflare Pages secretslocals.runtime.env at request time
CLOUDFLARE_API_TOKENGitHub repo secretsCI/CD deploy workflow only
CLOUDFLARE_ACCOUNT_IDGitHub repo secretsCI/CD deploy workflow only

Setting a Cloudflare Pages secret:

npx wrangler pages secret put SECRET_NAME --project-name solanasis-site

Setting a GitHub repo secret:

gh secret set SECRET_NAME --body "value"

Never put secrets in:

  • wrangler.toml (committed to git)
  • .env in git (use .gitignore)
  • Chat prompts or terminal history

Incident Response — Security

If credentials are leaked:

  1. Rotate ALL affected keys immediately (Brevo, Cloudflare, Turnstile)
  2. Update Cloudflare Pages secrets: npx wrangler pages secret put <NAME>
  3. Update GitHub secrets: gh secret set <NAME>
  4. Check Brevo logs for unauthorized email sends
  5. Check Cloudflare audit logs for unauthorized deploys
  6. See full procedure in solanasis-credentials-security-playbook.md

If the site is defaced or compromised:

  1. Check GitHub commit history for unauthorized commits
  2. Roll back via Cloudflare Pages deployment history (Dashboard → Pages → Deployments)
  3. Rotate all secrets
  4. Review GitHub Actions audit log

Monitoring

WhatWhere
AnalyticsUmami Dashboard (cookie-free)
UptimeCloudflare → Analytics → Web Analytics
DeploysGitHub Actions → Workflows
Email deliveryBrevo → Transactional → Logs
Security eventsCloudflare → Security → Events
SSL certificateAuto-renewed by Cloudflare (90-day cycle)