From d256814b51d7488328b07efdbb8d45217f6d2ede Mon Sep 17 00:00:00 2001 From: adm01-debug Date: Mon, 27 Apr 2026 09:06:05 -0300 Subject: [PATCH] fix(security): remove wildcard cors from proxy observability functions --- supabase/functions/proxy-health/index.ts | 17 +++++++++----- supabase/functions/proxy-metrics/index.ts | 27 +++++++++++++---------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/supabase/functions/proxy-health/index.ts b/supabase/functions/proxy-health/index.ts index 7b36c98cd..cc5980e39 100644 --- a/supabase/functions/proxy-health/index.ts +++ b/supabase/functions/proxy-health/index.ts @@ -10,10 +10,13 @@ // Ideal para chamar via cron a cada 5 minutos com evaluate=1. import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.49.1' +import { getCorsHeaders } from '../_shared/validation.ts' -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +function buildJsonHeaders(req: Request): Record { + return { + ...getCorsHeaders(req), + 'Content-Type': 'application/json', + } } // Limiares de alerta — ajustáveis via env vars. @@ -167,8 +170,10 @@ export function evaluateAlerts(m: ComputedMetrics): AlertCandidate[] { } Deno.serve(async (req) => { + const headers = buildJsonHeaders(req) + if (req.method === 'OPTIONS') { - return new Response('ok', { headers: corsHeaders }) + return new Response('ok', { headers }) } const url = new URL(req.url) @@ -192,7 +197,7 @@ Deno.serve(async (req) => { if (error) { return new Response(JSON.stringify({ error: error.message }), { - status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 500, headers, }) } @@ -259,6 +264,6 @@ Deno.serve(async (req) => { alert_candidates: candidates, fired_alerts: firedAlerts, }, null, 2), { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + headers, }) }) diff --git a/supabase/functions/proxy-metrics/index.ts b/supabase/functions/proxy-metrics/index.ts index 2e5e97a4b..b7c62b916 100644 --- a/supabase/functions/proxy-metrics/index.ts +++ b/supabase/functions/proxy-metrics/index.ts @@ -15,17 +15,18 @@ // Output: text/plain; version=0.0.4 (Prometheus exposition format). import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.45.0' +import { getCorsHeaders } from '../_shared/validation.ts' 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 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 buildPromHeaders(req: Request): Record { + return { + ...getCorsHeaders(req), + 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8', + 'Cache-Control': 'no-store', + } } type MetricRow = { @@ -194,24 +195,26 @@ function buildExposition(rows: MetricRow[], windowKey: WindowKey, generatedAtMs: } Deno.serve(async (req) => { + const headers = buildPromHeaders(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) @@ -236,7 +239,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 @@ -245,5 +248,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 }) })