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)
Without DKIM, emails from hi@solanasis.com (auto-replies, contact confirmations) may land in spam.
- 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 (typically
mail._domainkey) - Go back to Brevo → click Verify (allow 5-30 minutes for DNS propagation)
- Verify: green checkmark appears next to DKIM in Brevo
- Current status:
nslookup -type=CNAME mail._domainkey.solanasis.comreturns empty — DKIM is NOT configured
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 — After DKIM)
Current DMARC policy is p=none (monitoring only, no enforcement). Spoofed emails pass through.
- Wait until DKIM is verified (step 2 above)
- 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 — After DKIM)
Current SPF ends with ~all (softfail). Changing to -all (hardfail) makes spoofed emails more likely to be rejected.
- Wait until DKIM is verified (step 2 above)
- 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 record (should NOT be empty)
nslookup -type=CNAME mail._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) |