Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions supabase/functions/proxy-health/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,23 @@

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.49.1'

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
const DEFAULT_ALLOWED_ORIGIN = 'https://pronto-talk-suite.lovable.app'
const ALLOWED_ORIGINS = new Set(
(Deno.env.get('PROXY_HEALTH_ALLOWED_ORIGINS')
?? `${DEFAULT_ALLOWED_ORIGIN},http://localhost:5173,http://127.0.0.1:5173`)
.split(',')
Comment on lines +14 to +18
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function introduces its own allowed-origin parsing and CORS header construction, but the codebase already provides getCorsHeaders(req) / handleCors(req) in supabase/functions/_shared/validation.ts (with origin validation, localhost/preview handling, and security headers). Consider reusing/extending the shared helper to avoid CORS behavior diverging across edge functions.

Copilot uses AI. Check for mistakes.
.map((origin) => origin.trim())
.filter(Boolean),
)

function getCorsHeaders(req?: Request) {
const requestOrigin = req?.headers.get('origin') ?? ''
const allowOrigin = ALLOWED_ORIGINS.has(requestOrigin) ? requestOrigin : DEFAULT_ALLOWED_ORIGIN
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove hardcoded fallback origin from allowlist check

This fallback makes the allowlist non-authoritative: even if PROXY_HEALTH_ALLOWED_ORIGINS is explicitly set without https://pronto-talk-suite.lovable.app, requests from that origin still receive Access-Control-Allow-Origin equal to their origin because non-matches are forced to DEFAULT_ALLOWED_ORIGIN. In practice, operators cannot fully restrict CORS via env vars, and the same bypass pattern exists in proxy-metrics (getPromHeaders). For strict deployments, this unintentionally permits cross-origin reads from the hardcoded domain.

Useful? React with 👍 / 👎.

return {
'Access-Control-Allow-Origin': allowOrigin,
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCorsHeaders() omits Access-Control-Allow-Methods. For preflight requests, browsers validate Access-Control-Request-Method against Access-Control-Allow-Methods, and omitting it can cause CORS failures (even for GET requests when using Authorization/custom headers). Add an explicit allow-methods value (e.g., GET, OPTIONS) to this header set.

Suggested change
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'GET, OPTIONS',

Copilot uses AI. Check for mistakes.
Vary: 'Origin',
}
Comment on lines +25 to +30
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same fallback logic as proxy-metrics (ALLOWED_ORIGINS.has(requestOrigin) ? requestOrigin : DEFAULT_ALLOWED_ORIGIN) makes the default origin effectively always allowed, even if PROXY_HEALTH_ALLOWED_ORIGINS is configured to exclude it. If the goal is a configurable allowlist, consider making the default origin configurable too, or omitting Access-Control-Allow-Origin for disallowed origins.

Suggested change
const allowOrigin = ALLOWED_ORIGINS.has(requestOrigin) ? requestOrigin : DEFAULT_ALLOWED_ORIGIN
return {
'Access-Control-Allow-Origin': allowOrigin,
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
Vary: 'Origin',
}
const headers: Record<string, string> = {
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
Vary: 'Origin',
}
if (requestOrigin && ALLOWED_ORIGINS.has(requestOrigin)) {
headers['Access-Control-Allow-Origin'] = requestOrigin
}
return headers

Copilot uses AI. Check for mistakes.
}

// Limiares de alerta — ajustáveis via env vars.
Expand Down Expand Up @@ -167,6 +181,8 @@ export function evaluateAlerts(m: ComputedMetrics): AlertCandidate[] {
}

Deno.serve(async (req) => {
const corsHeaders = getCorsHeaders(req)

if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
Expand Down
39 changes: 27 additions & 12 deletions supabase/functions/proxy-metrics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,26 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.45.0'
const SUPABASE_URL = Deno.env.get('SUPABASE_URL')!
const SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
const SCRAPE_TOKEN = Deno.env.get('PROXY_METRICS_TOKEN') ?? ''
const DEFAULT_ALLOWED_ORIGIN = 'https://pronto-talk-suite.lovable.app'
const ALLOWED_ORIGINS = new Set(
(Deno.env.get('PROXY_METRICS_ALLOWED_ORIGINS')
?? `${DEFAULT_ALLOWED_ORIGIN},http://localhost:5173,http://127.0.0.1:5173`)
.split(',')
.map((origin) => origin.trim())
.filter(Boolean),
)

const PROM_HEADERS = {
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
'Cache-Control': 'no-store',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
function getPromHeaders(req?: Request) {
const requestOrigin = req?.headers.get('origin') ?? ''
const allowOrigin = ALLOWED_ORIGINS.has(requestOrigin) ? requestOrigin : DEFAULT_ALLOWED_ORIGIN
return {
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
Comment on lines +31 to +35
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a local CORS/origin allowlist implementation, but the repo already has a shared, origin-validated getCorsHeaders(req) helper in supabase/functions/_shared/validation.ts (and notes not to use ad-hoc CORS in new code). Duplicating CORS logic in each function increases drift risk (e.g., preview origins / localhost port handling / security headers); consider reusing/extending the shared helper and layering Prometheus-specific headers (Content-Type, etc.) on top.

Copilot uses AI. Check for mistakes.
'Cache-Control': 'no-store',
'Access-Control-Allow-Origin': allowOrigin,
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
Vary: 'Origin',
}
Comment on lines +33 to +41
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback logic ALLOWED_ORIGINS.has(requestOrigin) ? requestOrigin : DEFAULT_ALLOWED_ORIGIN means the default origin will still be CORS-allowed even if PROXY_METRICS_ALLOWED_ORIGINS is configured to exclude it (requests from the default origin will hit the fallback and still get Access-Control-Allow-Origin set). If the intent is a fully configurable allowlist, consider making the default origin configurable via env too, or omitting Access-Control-Allow-Origin when the request origin is not allowed.

Suggested change
const allowOrigin = ALLOWED_ORIGINS.has(requestOrigin) ? requestOrigin : DEFAULT_ALLOWED_ORIGIN
return {
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
'Cache-Control': 'no-store',
'Access-Control-Allow-Origin': allowOrigin,
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
Vary: 'Origin',
}
const headers: Record<string, string> = {
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
'Cache-Control': 'no-store',
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
Vary: 'Origin',
}
if (requestOrigin && ALLOWED_ORIGINS.has(requestOrigin)) {
headers['Access-Control-Allow-Origin'] = requestOrigin
}
return headers

Copilot uses AI. Check for mistakes.
}

type MetricRow = {
Expand Down Expand Up @@ -194,24 +207,26 @@ function buildExposition(rows: MetricRow[], windowKey: WindowKey, generatedAtMs:
}

Deno.serve(async (req) => {
const headers = getPromHeaders(req)

if (req.method === 'OPTIONS') {
return new Response('ok', { headers: PROM_HEADERS })
return new Response('ok', { headers })
}
if (req.method !== 'GET') {
return new Response('Method not allowed\n', { status: 405, headers: PROM_HEADERS })
return new Response('Method not allowed\n', { status: 405, headers })
}

// Auth — fail-closed if no token configured.
if (!SCRAPE_TOKEN) {
return new Response(
'# proxy-metrics: PROXY_METRICS_TOKEN secret is not configured.\n',
{ status: 503, headers: PROM_HEADERS },
{ status: 503, headers },
)
}
const auth = req.headers.get('Authorization') ?? ''
const provided = auth.startsWith('Bearer ') ? auth.slice(7) : auth
if (provided !== SCRAPE_TOKEN) {
return new Response('# unauthorized\n', { status: 401, headers: PROM_HEADERS })
return new Response('# unauthorized\n', { status: 401, headers })
}

const url = new URL(req.url)
Expand All @@ -236,7 +251,7 @@ Deno.serve(async (req) => {
if (error) {
return new Response(
`# error fetching proxy_metrics: ${error.message.replace(/\n/g, ' ')}\n`,
{ status: 500, headers: PROM_HEADERS },
{ status: 500, headers },
)
}
if (!data || data.length === 0) break
Expand All @@ -245,5 +260,5 @@ Deno.serve(async (req) => {
}

const body = buildExposition(rows, windowKey, Date.now())
return new Response(body, { status: 200, headers: PROM_HEADERS })
return new Response(body, { status: 200, headers })
})
Loading