For: Lucy From: Mike (with a little help from his AI agent) Date: April 29, 2026
Hey Lucy! Huge congrats on the Honeystack launch — the site looks great and the build choices are really solid. As a friendly favor, I ran a security pass on honeystack.agency since you guys are about to be handling nonprofit data and email lists. Most of what I found is "you're already doing the right thing" — but there's one urgent fix in the newsletter API that's worth doing before this thing gets shared more widely.
This document is written so you can hand each section directly to Claude Code in the Honeystack repo and have it do the work. No security background needed.
Your newsletter signup form is wide open to bots.
You have all the right pieces installed — Turnstile (the Cloudflare CAPTCHA) and a hidden honeypot field. But your backend (the Cloudflare Worker that processes signups) isn't actually checking either of them before sending the email to Resend. I confirmed this by sending requests with no CAPTCHA token, with a fake CAPTCHA token, with the honeypot tripped, and from a forged origin — all four were accepted as valid signups.
What that means in practice: anyone can write a 4-line script that mass-subscribes thousands of email addresses to your list. This is a known harassment pattern (it's been used to flood activists' and journalists' inboxes), and at minimum it'll burn through your Resend credits and tank your sender reputation. For a brand whose business is trust with nonprofits, that's the wrong kind of attention.
The good news: this is a one-hour fix in your Worker repo.
Open the Honeystack API repo (the one with the Cloudflare Worker — the URL honeystack-api.ted-cd0.workers.dev tells me that's where it lives), then paste this into Claude Code:
I need to harden my newsletter subscribe Worker. Right now it's
accepting requests without validating the Turnstile token or the
honeypot field, and it leaks Resend's error responses verbatim to
the client. Please update the Worker so it does ALL of the following
before forwarding to Resend:
1. Check the request's Origin header against an allowlist of
["https://honeystack.agency", "https://www.honeystack.agency"].
Reject anything else with HTTP 403.
2. Parse the FormData body inside a try/catch. If parsing fails,
return HTTP 400 with {"ok":false,"error":"bad_body"}.
3. If the honeypot field "b28-ft" is non-empty, return HTTP 200
with {"ok":true} (silently drop the bot).
4. Validate the Turnstile token by calling
https://challenges.cloudflare.com/turnstile/v0/siteverify
with the secret stored in env.TURNSTILE_SECRET, the token from
the "cf-turnstile-response" form field, and the IP from the
"CF-Connecting-IP" header. If the response's "success" field
is not true, return HTTP 403 with
{"ok":false,"error":"turnstile_failed"}.
5. Validate the email locally before forwarding: trim whitespace,
reject empty, reject anything longer than 254 characters,
reject anything that doesn't match a basic email regex.
6. Wrap the Resend API call so that on any non-2xx response from
Resend, we log the full Resend error to console (server-side
only) but return ONLY a generic error to the client:
- Resend 429 → HTTP 503 {"ok":false,"error":"temporarily_unavailable"}
- Resend 422 → HTTP 400 {"ok":false,"error":"email_invalid"}
- Anything else → HTTP 502 {"ok":false,"error":"upstream_error"}
7. Keep the existing CORS headers and OPTIONS handler.
8. After the change, also add a Wrangler secret named
TURNSTILE_SECRET. Tell me the exact wrangler command to set it
so I can paste my Cloudflare Turnstile widget secret in.
After Claude Code makes the change, you'll need to:
- Find your Turnstile secret in the Cloudflare dashboard (Cloudflare → Turnstile → your site → Settings — note: it's called the secret, NOT the sitekey that's already in your HTML)
- Run the
wrangler secret put TURNSTILE_SECRETcommand Claude Code gives you, paste the secret wrangler deployto ship it
That's the urgent fix. Everything below is "do over the next week or two."
While I was confirming the bug above, I sent about 10 test signups to the API using email addresses that all start with mike+honeystack- (they all go to my inbox, no real victims). Could you log into Resend → your audience → search for mike+honeystack- → bulk delete those entries? They're all me. Sorry for the mess — I needed to confirm the gap was real before flagging it.
Plain English: Right now, anyone can send email that looks like it's from lucy@honeystack.agency to your customers, and Gmail/Outlook will deliver it. You have SPF and DKIM set up (the other two email-auth pieces), but DMARC is the one that tells mailbox providers "if a message claims to be from us but didn't come through our servers, throw it out." Without DMARC, you're inviting brand-impersonation phishing against your own customer base.
The fix: add one DNS record in Cloudflare.
- Cloudflare dashboard → honeystack.agency → DNS → Records → Add record
- Type: TXT
- Name:
_dmarc - Content:
v=DMARC1; p=none; rua=mailto:dmarc-reports@honeystack.agency; pct=100; adkim=s; aspf=s - Save.
That deploys in "monitor only" mode. After a week, if no legitimate email is being mistakenly flagged in the aggregate reports, change p=none to p=quarantine. After another month, change to p=reject. (p= is the policy strength — none = watch, quarantine = junk-folder spoofs, reject = block them outright.)
If you want a free tool that emails you a weekly summary instead of raw report XML, sign up for Postmark DMARC Digests (postmarkapp.com/dmarc) and use that email as your rua=.
Plain English: You're collecting emails (newsletter), running PostHog (which sets visitor IDs in cookies) and Fathom (analytics), and using Cloudflare Turnstile (which sets cookies). That's enough to require a privacy disclosure under GDPR (any EU visitor) and California's CCPA. PostHog's own terms of service also explicitly require you to disclose its use. And — practically — most nonprofit IT departments will refuse to engage with you until you have one published.
The fix (fastest path):
- Sign up at Termly or Iubenda — both have free tiers that generate a passable starter policy.
- Walk through their wizard. The data points to declare:
- Data collected: email address (newsletter), IP address, browser/device fingerprint, behavioral analytics events (PostHog)
- Sub-processors / third parties: Google Workspace (mail), Cloudflare (hosting + bot defense + Turnstile), PostHog (product analytics, US region), Fathom (privacy-friendly site analytics), Resend (email delivery)
- Retention: "we retain newsletter subscriber emails until you unsubscribe; analytics data is retained for 12 months"
- User rights: access, correction, deletion (point at a
privacy@honeystack.agencymailbox)
- Get the generated HTML/markdown.
- In Claude Code in your Honeystack site repo:
Please add /privacy and /terms pages to my Astro site, using the content from the policy generator I'll paste below. Match the visual style of the existing /about page. Then add "Privacy" and "Terms" links to the footer of the global layout. [paste the policy text here] - Set up
privacy@honeystack.agencyas a Workspace alias forwarding to whoever handles inquiries.
For the more polished version (when you onboard your first paying client), have an attorney with nonprofit-sector experience do a custom pass. They'll also draft a Data Processing Agreement template, which funders will start asking for.
These are quality-of-life security wins. None are urgent. All are short Claude Code tasks.
Please add a public/_headers file to my Astro site that sets the
following security response headers on all routes. Use the syntax
that Cloudflare Pages reads. Start the Content-Security-Policy in
report-only mode for one week:
- Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
- X-Content-Type-Options: nosniff
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
- Content-Security-Policy-Report-Only: a policy that allows scripts
from 'self', cdn.usefathom.com, us.i.posthog.com, us-assets.i.posthog.com,
challenges.cloudflare.com (with 'unsafe-inline' for the inline PostHog
bootstrap), connect-src to those plus my newsletter API
(currently honeystack-api.ted-cd0.workers.dev), and frame-src for
challenges.cloudflare.com.
Ask me before submitting to the HSTS preload list — that has to wait
until we've watched the policy in production for 30 days.
After it's been live for a week and you've confirmed no broken pages or console errors, switch Content-Security-Policy-Report-Only to Content-Security-Policy.
- Cloudflare → honeystack.agency → DNS → Settings → DNSSEC → Enable
- Cloudflare shows you a DS record with key tag, algorithm, etc.
- Log into your registrar (looks like Namecheap based on your DNS setup) → domain settings → Advanced DNS → DNSSEC → paste the DS record values
- Save. Wait ~30 minutes for it to propagate.
This protects your domain against a class of DNS spoofing attacks. It's a one-time setup with no ongoing cost.
Right now your form posts to honeystack-api.ted-cd0.workers.dev. That works, but:
- It's a shared Cloudflare subdomain — anyone can guess at neighboring Workers.
- It looks like a phishing URL to security-conscious nonprofit IT teams.
- Your zone-level Cloudflare features (rate-limiting, WAF, bot management) don't apply to the workers.dev domain.
The fix:
- Cloudflare dashboard → Workers & Pages → click into your subscribe Worker → Settings → Domains & Routes → Add Custom Domain →
api.honeystack.agency - Cloudflare auto-creates the DNS and provisions TLS. Wait a couple of minutes.
- In Claude Code in your Honeystack site repo:
Please find every reference to honeystack-api.ted-cd0.workers.dev in this site repo and change it to api.honeystack.agency. Show me the diff before committing. - Deploy the site.
- Once the new domain works, also add a Cloudflare Rate Limiting Rule:
- Cloudflare → honeystack.agency → Security → WAF → Rate limiting rules
- Match: hostname
api.honeystack.agencyAND path/subscribeAND methodPOST - Threshold: 3 requests per 1 minute per IP
- Action: Block for 10 minutes
- This is your last line of defense in case any of the Worker checks slip.
For confidence — most of your stack is genuinely well-configured:
- TLS is valid and rotates automatically (Google Trust Services, 90-day cycle)
- HTTPS is enforced;
wwwcorrectly redirects to the apex - DKIM is set up (Google selector active)
- SPF is correctly configured for Google Workspace
- No source maps, .env files, .git folders, or build artifacts are exposed — this is a really common mistake on early launches and you avoided it
- No admin or login surfaces are publicly reachable — your static site has minimal attack surface by design
- All exposed JavaScript keys (PostHog, Fathom, Turnstile sitekey) are intended public client keys — no secrets are leaked anywhere
- Cloudflare Turnstile is correctly wired into the front-end form (it just needs the server-side validation; see Critical fix above)
- Astro static build + Cloudflare Pages is a great architectural choice for a marketing site — you've kept the attack surface tiny on purpose
- Cloudflare's AI-bot blocklist is on in your robots.txt (blocks GPTBot/ClaudeBot/CCBot)
| Priority | Item | Who/How | Time |
|---|---|---|---|
| 🚨 Today | Server-side Turnstile + origin + honeypot + email-length validation in the Worker | Claude Code (paste prompt above) | 30-60 min |
| 🚨 Today | Generic Resend errors instead of leaking detail | Same fix as above | included |
| 🧹 Today | Delete mike+honeystack-* test contacts from Resend audience |
Lucy / Resend dashboard | 2 min |
| 🟠 This week | DMARC DNS record | Cloudflare dashboard | 15 min |
| 🟠 This week | Privacy policy + Terms of Service | Termly + Claude Code | 1-2 hr |
| 🟡 This month | Security response headers (_headers file) |
Claude Code | 30 min |
| 🟡 This month | Enable DNSSEC | Cloudflare + registrar | 5 min |
| 🟡 This month | Move API to api.honeystack.agency + rate limit | Claude Code + dashboard | 20 min |
| 🟢 Nice-to-have | security.txt for security researchers |
Claude Code | 5 min |
| 🟢 Nice-to-have | Fix /thanks redirect (currently 404s on the worker subdomain) |
Claude Code | 10 min |
If anything in here is unclear or the Claude Code prompts don't quite work in your setup, ping Mike and he'll loop me back in. Happy to do a deeper pass once these are in — including looking at your Resend account setup, the Wrangler secrets, and the founder-account hygiene stuff (MFA / FIDO keys / etc.) that becomes more important as you grow.
Congrats again. The launch is real, the product positioning is sharp, and the engineering taste shows. Just close that one Critical gap before this thing gets boosted somewhere it'll attract the wrong kind of attention. 🍯
— Mike (& his agent hex)