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.mdLast 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.
| Protection | Implementation | Files |
|---|---|---|
| HSTS | Strict-Transport-Security: max-age=31536000; includeSubDomains | public/_headers |
| Clickjacking | X-Frame-Options: DENY | public/_headers |
| MIME sniffing | X-Content-Type-Options: nosniff | public/_headers |
| Referrer leakage | Referrer-Policy: strict-origin-when-cross-origin | public/_headers |
| Browser API lockdown | Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=() | public/_headers |
| Content Security Policy | Restricts script/style/connect/frame sources to known origins | public/_headers |
| CORS (API routes) | Server-side Access-Control-Allow-Origin: https://solanasis.com | src/lib/api-helpers.ts |
| Input length limits | Name: 200, email: 254, company: 200, phone: 30, message: 5000 chars | src/lib/api-helpers.ts |
| Email validation | RFC-aligned regex (rejects malformed addresses) | src/lib/api-helpers.ts |
| CAPTCHA | Cloudflare Turnstile on contact form + newsletter | src/components/ContactForm.astro, NewsletterSignup.astro |
| Honeypot | Hidden _url field catches bots | src/components/ContactForm.astro, src/pages/api/send-email.ts |
| HTML escaping | All user input escaped before email templates | src/lib/api-helpers.ts |
| Contact obfuscation | Email/phone assembled via JS on hover (not in HTML source) | src/components/Footer.astro |
| Secret separation | API keys in Cloudflare Pages secrets, not in code | wrangler.toml comments |
| CI/CD pinning | All GitHub Actions pinned to commit SHAs | .github/workflows/*.yml |
| TLS | TLS 1.3 with AES-256-GCM (Cloudflare-managed) | Automatic |
| HTTP→HTTPS | 301 redirect (Cloudflare-managed) | Automatic |
| E2E tests | 77 tests including security header validation | tests/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 Dashboard →
solanasis.com→ Security → WAF - Click Rate limiting rules → Create rule
- Configure:
- Rule name:
API rate limit - When incoming requests match: URI Path contains
/api/ - Rate:
10requests per1 minute - Counting expression: Same as rule expression
- Mitigation: Block for
60 seconds
- Rule name:
- 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 Brevo → Settings → Senders, Domains & Dedicated IPs → Domains tab
- Click on
solanasis.com(or add it if not there) - Brevo shows DKIM CNAME records to add — copy them
- Go to Cloudflare Dashboard →
solanasis.com→ DNS → Records - 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.comresolves through Brevo to a live DKIM keybrevo2._domainkey.solanasis.comresolves through Brevo to a live DKIM keygoogle._domainkey.solanasis.comalso 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 Dashboard →
solanasis.com→ Rules → Redirect 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
- Rule name:
- Click Deploy
- Verify:
curl -sI https://www.solanasis.com→ should return301withLocation: https://solanasis.com/
4. CAA DNS Record (MEDIUM)
Restricts which Certificate Authorities can issue certificates for your domain.
- Go to Cloudflare Dashboard →
solanasis.com→ DNS → Records - 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)
- Type:
- 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 Dashboard →
solanasis.com→ DNS → Records - Find the TXT record for
_dmarc.solanasis.com - Edit: change
p=nonetop=quarantine - Save
- After 2-4 weeks of monitoring (no legitimate emails quarantined), escalate to
p=reject - Verify:
nslookup -type=TXT _dmarc.solanasis.com→ showsp=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 Dashboard →
solanasis.com→ DNS → Records - Find the TXT record for
solanasis.comthat starts withv=spf1 - Edit: change
~allto-allat 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-controlCSP Reference (What Each Directive Allows)
| Directive | Allowed Origins | Why |
|---|---|---|
default-src | 'self' | Baseline: only same-origin resources |
script-src | 'self' 'unsafe-inline' + Cloudflare Turnstile + Cal.com + Umami | Inline 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 gateway | API calls to our server + 3rd-party verification/analytics |
frame-src | Turnstile + Cal.com | CAPTCHA widget + booking modal iframes |
Accepted risks:
unsafe-inlinein 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
| Secret | Where Stored | How Accessed |
|---|---|---|
BREVO_API_KEY | Cloudflare Pages secrets | locals.runtime.env at request time |
TURNSTILE_SECRET_KEY | Cloudflare Pages secrets | locals.runtime.env at request time |
BREVO_LIST_ID | Cloudflare Pages secrets | locals.runtime.env at request time |
CLOUDFLARE_API_TOKEN | GitHub repo secrets | CI/CD deploy workflow only |
CLOUDFLARE_ACCOUNT_ID | GitHub repo secrets | CI/CD deploy workflow only |
Setting a Cloudflare Pages secret:
npx wrangler pages secret put SECRET_NAME --project-name solanasis-siteSetting a GitHub repo secret:
gh secret set SECRET_NAME --body "value"Never put secrets in:
wrangler.toml(committed to git).envin git (use.gitignore)- Chat prompts or terminal history
Incident Response — Security
If credentials are leaked:
- Rotate ALL affected keys immediately (Brevo, Cloudflare, Turnstile)
- Update Cloudflare Pages secrets:
npx wrangler pages secret put <NAME> - Update GitHub secrets:
gh secret set <NAME> - Check Brevo logs for unauthorized email sends
- Check Cloudflare audit logs for unauthorized deploys
- See full procedure in
solanasis-credentials-security-playbook.md
If the site is defaced or compromised:
- Check GitHub commit history for unauthorized commits
- Roll back via Cloudflare Pages deployment history (Dashboard → Pages → Deployments)
- Rotate all secrets
- Review GitHub Actions audit log
Monitoring
| What | Where |
|---|---|
| Analytics | Umami Dashboard (cookie-free) |
| Uptime | Cloudflare → Analytics → Web Analytics |
| Deploys | GitHub Actions → Workflows |
| Email delivery | Brevo → Transactional → Logs |
| Security events | Cloudflare → Security → Events |
| SSL certificate | Auto-renewed by Cloudflare (90-day cycle) |