fix: harden Edge Function notification path per final review
Fixes issues raised by the whole-branch review before merge: - Critical: the previous migration hardcoded the live production functions URL and anon key as the committed default, so every local `supabase db reset` silently pointed dev/test triggers at the real project. Adding a new migration instead of editing the already-applied one (checksum/drift safety), with local dev values as the committed default; production values are applied out-of-band, never committed. - Important: verify_jwt=true plus sending the anon key as a bearer token didn't actually stop anyone, since the anon key is public (ships in the frontend bundle) — confirmed empirically, a request with a valid anon key but no shared secret now gets a real 401. Added a random shared secret known only to the database and the Edge Functions' secret store. - Important: notification emails interpolated raw contact fields into HTML with no escaping. Added escapeHtml() and tests proving injected markup renders as literal text, not live markup. - Important: the URL/key/secret functions moved from public to a private schema with EXECUTE revoked from anon/authenticated, closing an unintended PostgREST RPC endpoint (verified 404 after the fix). Also adds a test:functions npm script now that Deno is confirmed available and the shared email builders' tests pass (12 total incl. the new escaping cases). Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
This commit is contained in:
@@ -47,3 +47,34 @@ Deno.test('buildInquiryNotificationEmail omits the notes paragraph when notes ar
|
||||
})
|
||||
if (html.includes('Notes:')) throw new Error('expected no Notes: paragraph')
|
||||
})
|
||||
|
||||
Deno.test('buildBookingNotificationEmail escapes HTML in contact fields so they cannot inject markup', () => {
|
||||
const { html } = buildBookingNotificationEmail({
|
||||
serviceName: 'Knotless Braids',
|
||||
date: '2026-09-01',
|
||||
time: '10:00 AM',
|
||||
contact: {
|
||||
name: '<a href="https://evil.example">Click here</a>',
|
||||
phone: '555-0100',
|
||||
email: 'jane@example.com',
|
||||
notes: '<img src=x onerror=alert(1)>',
|
||||
},
|
||||
})
|
||||
if (html.includes('<a href="https://evil.example">')) throw new Error('expected contact name to be escaped')
|
||||
if (html.includes('<img src=x onerror=alert(1)>')) throw new Error('expected notes to be escaped')
|
||||
assertStringIncludes(html, '<a href="https://evil.example">Click here</a>')
|
||||
assertStringIncludes(html, '<img src=x onerror=alert(1)>')
|
||||
})
|
||||
|
||||
Deno.test('buildInquiryNotificationEmail escapes HTML in contact fields so they cannot inject markup', () => {
|
||||
const { html } = buildInquiryNotificationEmail({
|
||||
productName: 'Silk Edge Control',
|
||||
contact: {
|
||||
name: '<script>alert(1)</script>',
|
||||
email: 'nadia@example.com',
|
||||
notes: '',
|
||||
},
|
||||
})
|
||||
if (html.includes('<script>alert(1)</script>')) throw new Error('expected contact name to be escaped')
|
||||
assertStringIncludes(html, '<script>alert(1)</script>')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user