fix: enable JWT verification and harden new_user edge function#1240
fix: enable JWT verification and harden new_user edge function#1240sebastiondev wants to merge 1 commit intoe2b-dev:mainfrom
Conversation
…ge function - Set verify_jwt = true in config.toml so unauthenticated requests are rejected. Supabase database webhooks already include the service_role JWT, so legitimate webhook calls continue to work. - Validate the email field before forwarding to Slack and Loops to prevent abuse with malformed data. - Sanitize the email string before embedding in Slack mrkdwn to prevent message injection via special characters.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11493b10da
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| function sanitizeForSlack(text: string): string { | ||
| // Escape Slack mrkdwn special characters to prevent injection | ||
| return text.replace(/[&<>*_~`|]/g, (ch) => `&#${ch.charCodeAt(0)};`) |
There was a problem hiding this comment.
Use Slack-supported escaping for sanitized emails
sanitizeForSlack replaces formatting characters with numeric entities (for example _ → _), but Slack mrkdwn only decodes &, <, and >; other entities are rendered literally. In practice, common addresses like first_last@example.com will be posted as first_last@example.com, which makes signup alerts inaccurate and harder to use even for legitimate users. Restrict escaping to Slack-supported control chars (&, <, >) or disable mrkdwn for this message.
Useful? React with 👍 / 👎.
Vulnerability Summary
CWE: CWE-200 (Exposure of Sensitive Information) / OWASP API2:2023 (Broken Authentication)
Severity: High
Affected file:
supabase/functions/new_user/index.tsandsupabase/config.tomlData Flow
The
new_userSupabase Edge Function is deployed to the public internet via the CI pipeline in.github/workflows/supabase.yml. On themainbranch,verify_jwt = falseinconfig.tomlmeans no authentication is required to invoke this function.An unauthenticated attacker can:
https://<project-ref>.supabase.co/functions/v1/new_user#user-signupschannel (mrkdwn injection via unsanitizedemailfield — bold, links,@channelmentions)The function name (
new_user) is visible in this public repository, and Supabase project URLs follow a predictable pattern, making discovery trivial.Fix Description
This PR makes three changes, all scoped to the
supabase/directory:1. Enable JWT verification (
config.toml)This is the primary fix. With
verify_jwt = true, Supabase's API gateway requires a valid JWT before the function is invoked. Database webhooks (the intended caller for user signup events) include theservice_roleJWT automatically, so existing functionality is preserved.2. Input validation (
index.ts)Added email format validation so malformed or missing input is rejected with a
400:3. Slack mrkdwn sanitization (
index.ts)Added output encoding to prevent Slack formatting injection:
Rationale
supabase/. The lockfile change is from a co-landedhandlebarspatch bump (pre-existing PR chore(deps): bump handlebars from 4.7.8 to 4.7.9 in the npm_and_yarn group across 1 directory #1239).service_rolekey.Test Results
verify_jwt = trueblocks requests without valid Bearer token)recordfield → 400emailfield → 400email→ 400*bold*→ escaped<link|text>→ escaped@channel→ passes (not mrkdwn special char)@channelis a Slack server-side expansion, not a mrkdwn construct; full mitigation requires JWT gateDisprove Analysis
We systematically attempted to disprove this finding. Here are the results:
Authentication Check
No authentication exists in the original code. The function is a raw
serve()handler that immediately parsesreq.json(). The only auth-related code is an outboundAuthorization: Bearerheader for the Loops.so API — that's the function authenticating to Loops, not verifying inbound callers.verify_jwt = falseconfirms Supabase's gateway does not check for a JWT.Network Accessibility
The CI workflow (
.github/workflows/supabase.yml) deploys to hosted Supabase usingsupabase functions deploy --project-ref "$SUPABASE_PROJECT_ID". Supabase Edge Functions are directly internet-facing. No reverse proxy, VPN, or service mesh is used. Thelocalhost:3000reference inconfig.tomlis only for local dev auth redirects.Caller Trace
The intended caller is a Supabase Database Webhook (triggered on user signup), but with
verify_jwt = false, any HTTP client can call it. The function name is visible in this public repository.Input Validation
Zero input validation in the original code.
userRecord.emailis used directly without type checks, format validation, or sanitization.Prior Reports
No prior security reports or fixes found for this function. No
SECURITY.mdexists in the repository.Fix Adequacy
verify_jwt = trueblocks all unauthenticated callers (primary fix)service_roleJWT, so the fix preserves the intended workflowExploit Sketch (for validation only)
This would inject formatted content into the
#user-signupsSlack channel and create a fake Loops.so contact.Verdict
CONFIRMED_VALID — High confidence. The vulnerability is unambiguous, the exploit is trivial, and the fix directly addresses the root cause.
Note
The CI trigger in
.github/workflows/supabase.ymlwatchessupabase/functions/**but notsupabase/config.toml. Since this PR also changesindex.ts, it will trigger the deploy pipeline. However, a future config-only change would not be deployed automatically — this is a pre-existing CI gap not introduced by this fix.