A form that echoes visitor text inside your signed email is a phishing relay
# against YOUR staging site,with an inbox you own as the addresscurl -s -X POST https: / / staging. yoursite. com/ api/ contact -H "Content-Type: application/json" \ -d '{"email": "you+relay@yourdomain. com", "message": "click here: http: / / evil. example", "website": ""}'# if that inbox gets your branded email with the link inside, it is a relay
Get every Fix AI Slop Code episode
The code it wrote, the code it should have written and a check, for every episode
What is going on
If a contact endpoint accepts a recipient and a message, and sends a branded, DKIM-signed email containing that message, anyone can send anything to anyone from your domain. A honeypot field stops bots that fill every input. It does not stop a person with curl.
Where it bit
Our own contact form echoed visitor text in a branded email to any address they supplied, guarded only by a honeypot. An older auto-reply worker was worse: its check was "if a secret is configured," and the forms never sent one, so the endpoint was open. Both are closed now: the echo drops links, and the endpoint checks where the request came from and limits it at the edge.
The practice
Recipients are never a request parameter. Visitor text goes to you, and the confirmation to them is a fixed template with no echoed body. Turnstile or an equivalent on anything that sends email. Signature check on every inbound webhook, with timingSafeEqual.
Get this check as a script you can run tonight

The coding agent never holds production credentials
An agent with a production database URL will sooner or later run a migration or a cleanup against it, so production secrets never enter its env
Keys go in headers, never in URLs
A URL is written to server logs, CDN logs, browser history and error trackers, so an API key in a query string is a key in five places
Backups the app cannot reach, and one restore you have actually done
A backup on the same account the app or an agent can delete from is not a backup, and a restore you have never run is a number you do not have
If this check came back with more than you expected, that is worth a conversation