Skip to content

security: audit remediation batch 2 — error redaction, auth gates, timing-safe comparisons, fail-closed access control#175

Closed
adm01-debug wants to merge 54 commits into
mainfrom
claude/system-audit-documentation-7hS0e
Closed

security: audit remediation batch 2 — error redaction, auth gates, timing-safe comparisons, fail-closed access control#175
adm01-debug wants to merge 54 commits into
mainfrom
claude/system-audit-documentation-7hS0e

Conversation

@adm01-debug
Copy link
Copy Markdown
Owner

@adm01-debug adm01-debug commented May 13, 2026

Summary

Continuing independent pre-production security audit remediation (batch 2).

Error Message Redaction (Information Disclosure)

  • Replaced all error.message / err.message leaks in JSON responses with generic codes ("internal_error", "query_failed", etc.) across 20+ edge functions
  • Affected: send-transactional-email, step-up-verify, bi-copilot, categories-api (public!), kit-identity-suggest, ownership-audit, ownership-repair, rls-matrix-export, rls-audit, rls-integration-tests, process-scheduled-reports, send-scheduled-reports, external-db-bridge, crm-db-bridge, bitrix-sync, external-db-inspect, webhook-dispatcher
  • All redacted messages still go to console.error() for server-side observability

Missing Auth Gates

  • bi-copilot: Was calling expensive AI API (Lovable AI Gateway) with no authentication. Added authorize() gate + bounded input validation (question ≤500 chars, context ≤8KB, history ≤20 items)
  • sync-quote-bitrix: Listed as authenticated in manifest but had no auth gate. Added authorize() gate

Input Validation (Zod Runtime Validation)

  • step-up-verify: Replaced unsafe (await req.json()) as RequestBody TypeScript cast with full Zod schema validation on all fields (step enum, action enum, challenge_id uuid, bounded strings)

Timing-Safe Secret Comparison

  • Added timingSafeStringEqual() and verifyServiceAuth() to _shared/security.ts
  • Replaced timing-unsafe !== CRON_SECRET comparisons in all 14 service/cron edge functions: process-scheduled-reports, connections-health-check, ownership-audit, send-notification, cleanup-novelties, quote-followup-reminders, process-queue, comparison-price-watcher, collections-watcher, send-scheduled-reports, cleanup-notifications, send-digest, favorites-watcher, connections-auto-test

Fail-Closed Access Control (Critical)

  • validate-access: Catch block was returning { allowed: true } on internal errors (fail-open). Changed to { allowed: false } — deny on error is the correct security posture for access control gates

Batch 1 (previous commits on this branch)

  • RLS policy fixes for cotacoes table
  • CSP hardening (removed unsafe-inline, hardened img-src)
  • CORS fixes
  • Webhook dispatcher shared-secret auth
  • XSS sanitization hardening
  • SSRF guard in analyze-logo-colors
  • IP spoofing fix in log-login-attempt
  • Rate limiting for detect-new-device
  • File upload security in secure-upload (MIME allowlist, size limit, path sanitization)
  • Zod import standardization (npm: → esm.sh:) across 15+ functions
  • Table list exposure fixes in crm-db-bridge and external-db-bridge
  • pg_cron schedules for 12 service functions

Test plan

  • Verify auth-required functions return 401 without JWT token
  • Verify bi-copilot returns 401 without authentication
  • Verify sync-quote-bitrix returns 401 without authentication
  • Verify step-up-verify returns 400 on invalid step value
  • Verify service cron functions return 401 without valid CRON_SECRET or service-role key
  • Verify validate-access returns { allowed: false } on DB failure (not allowed: true)
  • Verify no raw error messages appear in any edge function HTTP response body
  • Run node scripts/check-rls-dangerous-policies.mjs — expect 0 violations

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46

…h hardening

- fix(rls): isolate cotacoes SELECT per user_id + supervisor check (CRÍTICO-003)
- fix(rls): replace stale JWT role check with is_supervisor_or_above() in UPDATE policy
- fix(rls): restrict role_permissions write policies to 'authenticated' role only
- perf(db): add missing indexes on webhook_audit_log and empresas
- feat(ci): add RLS dangerous-policy audit step (check-rls-dangerous-policies.mjs)
- fix(ci): allowlist docs/adr and CONTRIBUTING.md in check-no-db-push guard
- fix(csp): remove unsafe-inline from script-src in public/_headers
- fix(cors): replace broken nonce placeholder in edge function CSP header
- fix(cors): narrow ALLOWED_ORIGIN_PATTERNS to project-specific lovable.app pattern
- feat(webhook): add WEBHOOK_DISPATCHER_SECRET shared-secret auth (fail-closed)
- fix(security): harden sanitizeHtml() — SVG, CSS injection, all event handler styles
- fix(access): explicit agente role case in checkAccess() to prevent bypass
- fix(autosave): scope localStorage key by userId to prevent cross-user data bleed
- fix(build): use sourcemap:'hidden' to enable Sentry uploads without serving maps

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
promo-gifts Ready Ready Preview, Comment May 13, 2026 6:54am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 03c4658a-639c-4f5c-ace6-fa5b0226021e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/system-audit-documentation-7hS0e

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines 12 to +25
return html
// Remove entire dangerous block-level tags including their content
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "")
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "")
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, "")
.replace(/on\w+="[^"]*"/gi, "") // remove event handlers like onclick
.replace(/on\w+='[^']*'/gi, "")
.replace(/href="javascript:[^"]*"/gi, "")
.replace(/src="javascript:[^"]*"/gi, "");
.replace(/<embed\b[^>]*\/?>/gi, "")
.replace(/<applet\b[^<]*(?:(?!<\/applet>)<[^<]*)*<\/applet>/gi, "")
// Strip SVG entirely (can contain onload and foreign objects)
.replace(/<svg\b[^<]*(?:(?!<\/svg>)<[^<]*)*<\/svg>/gi, "")
// Remove all event handler attributes (on* in any quote style or unquoted)
.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, "")
Comment on lines 12 to +17
return html
// Remove entire dangerous block-level tags including their content
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "")
…ize.ts SSOT

All 16 remaining edge functions that still imported from _shared/auth.ts
(authenticateRequest / authErrorResponse / requireDev) are now using the
_shared/authorize.ts SSOT (authorize()). This eliminates 16 divergent auth
implementations and closes gaps where error paths could leak auth exceptions
as 500s instead of proper 401/403 responses.

Changes per function:
- Import: authenticateRequest/authErrorResponse → authorize
- Call: await authenticateRequest(req) → await authorize(req); if (!auth.ok) return auth.response
- Property: auth.userId → auth.user.id, auth.localServiceClient → auth.supabaseAdmin
- connections-hub-audit: requireDev + nested try/catch → auth.role !== 'dev' check with 403
- rbac-edge-functions_test: update pattern to match new authorize API

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 2 commits May 13, 2026 01:52
Upgrades jsdom from ^20.0.3 to ^29.1.1, resolving:
- GHSA-vpq2-c234-7xj6: @tootallnate/once Incorrect Control Flow Scoping (low)
- http-proxy-agent 4.0.1-5.0.0 depends on vulnerable @tootallnate/once (low)
- jsdom 16.6.0-22.1.0 depends on vulnerable http-proxy-agent (low)

Remaining: 2 moderate (esbuild via vite dev-server — dev-only, no prod impact).
esbuild fix requires vite@8 (major breaking change, tracked separately).

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
The project had both sonner and react-hot-toast installed. Only one file used
react-hot-toast (FilterPanelHeader). Migrated to sonner and uninstalled the
duplicate library, reducing bundle size and dependency surface.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
- Remove 6 deleted edge functions from edge-authz-manifest.ts
  (bi-share-dossier, collections-public-react, comparisons-public-react,
   generate-mockup-nanobanana, kit-public-view, quote-public-view)
- Add cors-audit (dev-only) to edge-authz-manifest.ts
- Remove same 6 from LEGACY_ALLOWLIST in check-edge-structured-logging.mjs
  and update SNAPSHOT_SIZE from 84 to 78
- Rebuild cors-snapshot.json (77 functions, all via _shared/cors)

Fixes CI gate failures: edge-authorization and cors-snapshot-freshness.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 3 commits May 13, 2026 02:22
Replace lovable.app preview URLs with promogifts.com.br in:
- canonical link
- og:url and twitter:url
- JSON-LD structured data url

Also correct connections-hub-audit manifest category from 'service'
to 'dev' (code enforces auth.role === 'dev' inline).

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Implement useIdleTimeout hook that automatically signs out authenticated
users after 30 minutes of inactivity. Shows a warning toast 2 minutes
before logout. Activity events: mousemove, keydown, click, touchstart, scroll.

Wired into AppBootstrapContainer so it runs for all authenticated routes.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
…AIXO-002)

- Migration 20260513040000: insert dev entries in role_permissions by
  copying all admin permissions. Eliminates the need for the legacy
  admin→dev mapping in the frontend.
- useRBAC.tsx: toDbRole now returns 'dev' directly for the dev role
  instead of 'admin'. Type extended from 'admin|manager|vendedor' to
  include 'dev'.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Audit document:
- docs/AUDIT_INDEPENDENT_2026-05-13.md — exhaustive fresh-eyes audit
  covering RLS, edge functions, cron jobs, storage policies, RBAC,
  CI/CD, and AI quota system

Fixes applied (highest severity first):

C-001 (CRÍTICO): Remove dead quote-public-view from config.toml
  - Function folder does not exist; orphaned entry caused confusion
  - supabase/config.toml: remove [functions.quote-public-view] block

C-002 (CRÍTICO): Fix cron job pointing to wrong Supabase project
  - Migrations 20260424154125 and 20260429163414 hardcoded project ref
    nmojwpihnslkssljowjh (dev/staging) instead of doufsxqlfjyuvxuezpln
  - Migration 20260513050000 reschedules connections-auto-test cron at
    the correct project URL with CRON_SECRET auth header

A-001 (ALTO): Add auth gate to connections-auto-test
  - Any caller could trigger expensive connection tests (no auth check)
  - Now requires x-cron-secret (CRON_SECRET env) or service-role JWT
  - Paired with migration 20260513050000 that updates cron to send secret

A-002 (ALTO): Fix semantic-search manifest classification
  - Was "public" but calls authorize() and returns 401 for anon users
  - Corrected to "authenticated" in edge-authz-manifest.ts

M-002 (MÉDIO): Fix quarantine storage policy using email matching
  - Old: auth.jwt() ->> 'email' LIKE '%admin%' (bypassable)
  - New: is_admin_or_above(auth.uid()) — role-based, not email-based
  - Migration 20260513060000 replaces all three quarantine policies

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 3 commits May 13, 2026 04:10
CRITICAL: Two DB helper functions existed only in the live database,
never captured in migrations. Fresh deploys would fail on 98+ policies.

is_admin_or_above() bug:
  - Old impl: has_role(dev) OR has_role(admin) — missing 'supervisor'
  - Effect: supervisors silently denied access to 98 RLS-protected resources
  - Fix: delegate to is_supervisor_or_above() (covers dev|supervisor|admin|manager)

is_coord_or_above() bug:
  - Old impl: has_role(dev) OR has_role(admin) OR has_role(coordenador)
  - 'coordenador' is not a valid app_role enum value — always false
  - Fix: delegate to is_supervisor_or_above()

Also updates quarantine storage policy migration to use
is_supervisor_or_above() directly (avoid double indirection).

Applied directly to production DB via MCP apply_migration.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Security definer hardening gate requires REVOKE EXECUTE FROM anon or
a '-- rls-helper:' comment for functions callable by RLS policies.
Added the marker since both functions are RLS helpers.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Both functions call authorize() internally and return 401 for unauthenticated
requests, but were classified as 'public' in the auth manifest.

Corrected to 'authenticated' so the manifest accurately reflects behavior.
3 functions total moved from public to authenticated (semantic-search,
visual-search, analyze-logo-colors).

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 3 commits May 13, 2026 04:21
…e to config

- product-webhook had fail-open auth: if N8N_PRODUCT_WEBHOOK_SECRET env var
  was not set, the secret check was skipped and any caller could invoke the
  webhook. Fixed to fail-closed: reject all requests when secret not configured.
- Added [functions.product-webhook] verify_jwt = false to config.toml so
  n8n callers (no Supabase JWT) can reach the function at all.
- Improved error log to structured JSON with failure reason.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
…omparison

- send-transactional-email, comparison-ai-advisor: replace deprecated
  `serve()` from deno.land/std@0.168.0 with built-in `Deno.serve()`.
  The std HTTP server shim is unmaintained; Deno.serve() is the
  Supabase-recommended runtime pattern.
- comparison-ai-advisor: unify zod import to esm.sh/zod@3.23.8 matching
  the rest of the codebase (was deno.land/x/zod@v3.22.4, a different
  module graph branch causing potential duplicate module loading).
- product-webhook: upgrade to timing-safe secret comparison to prevent
  theoretical timing oracle attacks on the webhook secret.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
…nctions

13 edge functions classified as "service" in the authz manifest had no
in-function auth check — any authenticated user's JWT could call them,
enabling notification injection, mass email triggers, and bulk data reads.

Fix: add CRON_SECRET || SERVICE_ROLE_KEY gate to each function + set
verify_jwt=false in config.toml so pg_cron can reach them without a user JWT.

Functions hardened: cleanup-notifications, cleanup-novelties,
collections-watcher, favorites-watcher, comparison-price-watcher,
process-queue, process-scheduled-reports, send-scheduled-reports,
send-digest, quote-followup-reminders, connections-health-check,
send-notification, ownership-audit.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
- webhook-dispatcher: add isAllowedWebhookUrl() to block RFC1918/loopback/
  link-local targets before fetch (defense-in-depth against SSRF via
  admin-created webhooks pointing to internal services)
- commemorative-dates: redact raw DB error messages in public responses;
  log details server-side only
- rate-limit-check: redact error.message in catch response (public endpoint)
- elevenlabs-tts, voice-agent: upgrade zod from deno.land/x v3.22.4 to
  esm.sh v3.23.8 to match project canonical import

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Service functions (cleanup-notifications, cleanup-novelties,
collections-watcher, favorites-watcher, comparison-price-watcher,
process-queue, process-scheduled-reports, send-scheduled-reports,
send-digest, quote-followup-reminders, connections-health-check,
ownership-audit) were classified as "service/cron" in the authz
manifest but had no actual cron schedule calling them.

Migration creates all 12 schedules with:
- CRON_SECRET header when app.cron_secret is configured (secure)
- Graceful fallback scheduling with RAISE WARNING when secret not set

Schedules: hourly for reports, daily for watchers/cleanup/reminders,
every 10-15 min for queue and health-check, weekly for send-digest.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 2 commits May 13, 2026 05:41
… HTML

Add escapeHtml() and safeHref() helpers and apply them to all user-
controlled data interpolated into email templates. Previously, values like
recipient_name, quote_number, client_name, notes, order_number, valid_until
and approval_url were inserted as raw strings, enabling HTML injection that
could create phishing content or inject unexpected structure into the email.

safeHref() additionally validates that approval_url is an https:// URL,
rejecting javascript: URIs and relative paths that could execute code in
email clients that parse href attributes.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
- process-scheduled-reports: add escapeHtml() to report_name, summary
  keys/values in HTML email templates
- send-scheduled-reports: add escapeHtml() to report_name, quote/order
  field values (client_name, status, order_number) in HTML email tables
- validate-access: upgrade geolocation API call from http to https
- manage-users: block supervisors from assigning dev role during create
  (privilege escalation — dev role reserved for step-up promote_dev flow)
- market-intelligence-insights: suppress internal error message leak in
  catch block, log internally only
- magic-up-score: suppress internal error message leak in catch block
- voice-agent: suppress internal error message leak in catch block
- elevenlabs-tts: enforce voice ID allowlist (reject unapproved IDs);
  suppress error message leak
- elevenlabs-scribe-token: suppress error message leak

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
- quote-sync: add authorize(req) gate — unauthenticated callers could
  trigger CRM sync of any quote UUID including internal margin fields
- categories-api: add authorize(req) gate — was using external service
  role key without any authentication check
- generate-mockup, generate-ad-prompt, generate-ad-image, cnpj-lookup:
  suppress internal error message leaks in catch blocks (log internally)

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
- dropbox-list: add authorize(req) — function had no auth at all,
  any caller could enumerate company Dropbox files
- kit-identity-suggest: add authorize(req) + user-scoped rate limiting
  (previously only IP-based bot protection, JWT not verified)
- mcp-keys-issue/revoke/rotate/update: remove 'detail' field from
  client error responses — internal error messages were leaked in the
  JSON body even though only dev role can reach these functions

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 2 commits May 13, 2026 06:04
…ints

Remove `detail` fields from all client-facing error responses in
mcp-keys-issue and mcp-keys-revoke to prevent DB/RPC error messages
from leaking to callers (even dev-role holders should not receive raw
DB error strings via the API surface).

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Replace raw DB/auth error messages returned to clients with generic
messages (logged internally). Affected functions: block-ip-temporarily,
rls-matrix-export, ownership-repair, manage-users. Even admin-only
endpoints should not expose internal DB schema hints via error strings.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 4 commits May 13, 2026 06:22
…hook and comparison-price-watcher

- product-webhook: add .max() constraints to unbounded string fields in
  colors (name, hex, group) and kit_items (productId, productName, sku)
- comparison-price-watcher: replace String(e) error leak in 500 response
  with generic "internal_error" code; log internally instead

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
bi-copilot, kit-ai-builder, comparison-ai-advisor, and
market-intelligence-insights now run runBotProtection with
customIdentifier: user:<id> after JWT auth, preventing API key
abuse and per-user AI cost exhaustion attacks.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
…function

Prevents AI cost exhaustion attacks on the trends-insights endpoint
by adding runBotProtection with customIdentifier after JWT verification.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
The endpoint can validate arbitrary MCP keys and return their owner
(created_by) and scopes. Require is_dev() before allowing access to
prevent information disclosure to regular authenticated users.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 5 commits May 13, 2026 06:28
The function creates and deletes records in production tables (quotes,
orders, discount_approval_requests) as a side-effect of RLS testing.
Restrict to supervisors to prevent regular vendors from triggering
write operations in core business tables.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
connection-tester and external-db-inspect were checking for the
legacy 'admin' role which no longer exists after the role hierarchy
migration (20260426010557). Migrate both to use the
is_supervisor_or_above() RPC so supervisors and devs can access
these diagnostic tools again.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
…or_above()

After the role hierarchy migration (20260426010557), the 'admin' role
no longer exists in user_roles — all admin users were migrated to
'supervisor'. Functions checking for role='admin' would always deny
access or send no notifications.

Fixes:
- connections-health-check: notify supervisors+devs, not just 'admin'
- block-ip-temporarily: use is_supervisor_or_above() RPC
- rls-integration-tests: use is_dev() RPC (dev-only operation)
- github-credentials-test: use is_supervisor_or_above() RPC

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
- useDiscountApproval: query supervisor/dev/admin instead of admin-only
  (admin role removed by migration 20260426010557, no notifications sent)
- rls-matrix-export: replace has_role("admin") + has_role("dev") with
  is_supervisor_or_above() so supervisors can export RLS matrix
- cnpj-lookup: add user-scoped bot protection (20 req/min) to protect
  paid CNPJá API from cost-exhaustion abuse

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
- secure-upload: strip <script>, event handlers and javascript: hrefs
  from SVG files before storing to prevent stored XSS via user uploads
- image-proxy: add X-Content-Type-Options: nosniff to all responses;
  add Content-Security-Policy: script-src 'none' for SVG responses to
  prevent script execution when proxied SVG is opened directly

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
After migration 20260426010557, all admin/manager users were migrated to
supervisor, but is_manager_or_admin() still checked role IN ('admin','manager')
— silently denying supervisors access to RLS-protected resources.

Redefine both overloads to delegate to is_supervisor_or_above() which
correctly covers dev | supervisor | admin | manager (including legacy enum
values kept for backward compatibility).

Same pattern already applied to is_admin_or_above() in 20260513070000.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Comment on lines +102 to +104
const svgText = new TextDecoder().decode(fileBuffer)
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, "")
Comment on lines +102 to +103
const svgText = new TextDecoder().decode(fileBuffer)
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
// Strip scripts and event handlers from SVG uploads to prevent XSS when served.
if (file.type === "image/svg+xml") {
const svgText = new TextDecoder().decode(fileBuffer)
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
…sor_or_above()

After migration 20260426010557 removed 'admin' and 'manager' from user_roles,
multiple DB functions and RLS policies still checked has_role('admin') which
always returned false, silently locking supervisors out of critical operations.

Fixes:
- can_view_all_sales(): supervisors could not view all quotes/orders (CRITICAL)
- integration_credentials RLS: supervisors locked out of credentials table
- get_app_health_summary(): supervisors could not access health dashboard
- lookup_request_id(): supervisors could not use cross-layer request timeline
- ownership_repair_logs SELECT policy: supervisors could not read repair logs
- repair_ownership_orphans(): supervisors blocked from running ownership repair
- audit_rls_matrix(): supervisors could not call RLS audit function
- audit_ownership_orphans(): supervisors blocked from ownership audit

All guards replaced with is_supervisor_or_above() which covers dev, supervisor,
and legacy admin/manager enum values.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
claude added 2 commits May 13, 2026 06:52
…o get-visitor-info

authorize.ts: ROLE_RANK used AppRole type which excluded 'vendedor'. Since
user_roles stores 'vendedor' (not 'agente'), ROLE_RANK['vendedor'] was
undefined. In JavaScript, undefined < 2 evaluates to false, so vendedor users
passed supervisor role checks silently. Fixed by:
  - Adding vendedor, admin, manager to ROLE_RANK with correct values
  - Adding ?? 0 fallback on userRank to handle any unknown future role values

get-visitor-info: called ip-api.com (45 req/min free tier) with no rate limit.
Added runBotProtection at 30 req/min per IP to prevent exhausting the free tier
and protect against scraping abuse.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
…th of authorize.ts

Previously the enforceServerSide path made two has_role() calls for supervisor
check: first has_role('supervisor'), then has_role('dev') as fallback. This
could fail for users with legacy 'admin'/'manager' role enum values.

Replaced with single is_supervisor_or_above() call which covers all cases via
SECURITY DEFINER, and is_dev() for the dev-only path. Reduces RTT from 2 RPC
calls to 1 when the caller has a legacy role value.

https://claude.ai/code/session_018hD2KUr3SruH7P8vbagj46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants