🛡️ Sentinel: [HIGH] Implement strict email validation

- Implemented `isValidEmail` utility with strict regex validation (rejects `<` and `>`) to prevent XSS vectors.
- Updated `Contact.tsx` to use `isValidEmail` instead of weak regex.
- Added comprehensive tests for `isValidEmail` in `src/utils/security.test.ts`.
- Fixed flaky test in `src/pages/__tests__/Contact.test.tsx` by clearing `localStorage` in `afterEach`.
- Added test case for invalid email submission.
- Documented findings in `.jules/sentinel.md`.

Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot]
2026-01-27 01:56:08 +00:00
parent 9223331ee9
commit 57f7c5667f
5 changed files with 109 additions and 2 deletions

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "../i18n";
import { useRateLimit } from "../hooks";
import { config } from "../config";
import { Button, Input, Textarea } from "../components/ui";
import { sanitizeInput } from "../utils/security";
import { sanitizeInput, isValidEmail } from "../utils/security";
import styles from "./Contact.module.css";
const NAME_MAX_LENGTH = 100;
@@ -53,7 +53,7 @@ export function Contact() {
if (!formData.email.trim()) {
newErrors.email = "Required";
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
} else if (!isValidEmail(formData.email)) {
newErrors.email = "Invalid email";
}

View File

@@ -63,6 +63,7 @@ describe('Contact Page', () => {
afterEach(() => {
cleanup();
document.body.innerHTML = '';
localStorage.clear();
});
it('submits the form with correct parameters', async () => {
@@ -170,4 +171,26 @@ describe('Contact Page', () => {
// EmailJS should NOT be called
expect(emailjs.send).not.toHaveBeenCalled();
});
it('shows error when email contains invalid characters', async () => {
const { container } = render(<Contact />);
// Fill out the form with invalid email (XSS vector)
fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'John Doe' } });
fireEvent.change(screen.getByLabelText('Email'), { target: { value: '<script>@example.com' } });
fireEvent.change(screen.getByLabelText('Subject'), { target: { value: 'Test Subject' } });
fireEvent.change(screen.getByLabelText('Message'), { target: { value: 'Hello world' } });
// Submit via form submit event to bypass browser validation (jsdom/browser would block this otherwise)
// This ensures our application-level validation logic (isValidEmail) is tested
const form = container.querySelector('form');
if (form) fireEvent.submit(form);
// Validation error should appear
const errorMessage = await screen.findByText('Invalid email');
expect(errorMessage).toBeTruthy();
// EmailJS should NOT be called
expect(emailjs.send).not.toHaveBeenCalled();
});
});