The ROI calculator accepted the email field with no validation at all and stored whatever arrived, which is where the spam submissions were putting shell-command payloads. Nothing was executable — there is no child_process or eval in the codebase and all writes are parameterized — but the junk was being persisted, and the field is the obvious place to stop it. Adds security.validateEmail(), deliberately stricter than RFC 5322: the local part is limited to the characters real addresses use, which excludes every shell metacharacter (; | & ` $ ( ) < > \ " ' space) and CSV-injection lead-ins. Also rejects control characters (including the CR/LF used for mail-header injection), caps lengths at 254/64/253, rejects non-strings, and normalizes to trimmed lowercase before storage. Applied to /api/calculate (optional field — empty is fine, present must be valid) and to /api/leads, replacing its much weaker regex. The client mirrors the check for immediate feedback; the server remains authoritative. Also hardens the /api/leads required-field checks, which called .trim() on unvalidated input and returned a 500 rather than a 400 when a bot posted a non-string. Trade-off: RFC-legal but vanishingly rare addresses (foo!bar$baz@x.com, a leading + in the local part) are rejected. Those characters are the injection surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Description
No description provided
Languages
HTML
79.6%
JavaScript
11.9%
CSS
7.8%
Shell
0.7%