Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 0 additions & 20 deletions .eslintrc.tailwind.js

This file was deleted.

2 changes: 1 addition & 1 deletion scripts/ds-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const DS_CONFIG = {
],
FORBIDDEN_PATTERNS: [
{ pattern: /(?:bg|text|border)-(?:\[(?:#(?:[0-9a-fA-F]{3,6})|rgb|hsl|rgba|hsla)\])/, label: 'Arbitrary Color' },
{ pattern: /(?:bg|text|border)-(?:white|black(?:(?!\-[0-9]))|(?:red|blue|green|yellow|slate|gray|zinc|neutral|stone|orange|amber|lime|emerald|teal|cyan|sky|indigo|violet|purple|fuchsia|pink|rose)-[0-9]+)\b/, label: 'Literal Color' },
{ pattern: /(?:bg|text|border)-(?:white|black(?:(?!-[0-9]))|(?:red|blue|green|yellow|slate|gray|zinc|neutral|stone|orange|amber|lime|emerald|teal|cyan|sky|indigo|violet|purple|fuchsia|pink|rose)-[0-9]+)\b/, label: 'Literal Color' },
{ pattern: /font-(?:inter|sans|mono|serif)\b/, label: 'Literal Font' },
{ pattern: /(?<!linear-gradient\(|radial-gradient\(|conic-gradient\()#([0-9a-fA-F]{3,6})\b/, label: 'Raw Hex' },
],
Expand Down
3 changes: 2 additions & 1 deletion src/components/contacts/ContactsRichView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,15 @@ export const ContactsRichView: React.FC<ContactsRichViewProps> = () => {
setIsAddDialogOpen(true);
toast.info("Atalho: Novo Registro", { duration: 1000 });
break;
case 'f':
case 'f': {
e.preventDefault();
const searchInput = document.querySelector('input[placeholder*="Buscar"]') as HTMLInputElement;
if (searchInput) {
searchInput.focus();
toast.info("Atalho: Focar Busca", { duration: 1000 });
}
break;
}
case 'g':
e.preventDefault();
state.setViewMode('grid');
Expand Down
2 changes: 1 addition & 1 deletion src/components/security/RateLimitRealtimeAlerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function RateLimitRealtimeAlerts() {
const audio = new Audio('/notification.mp3');
audio.volume = 0.5;
audio.play().catch(() => {});
} catch (e) {}
} catch { /* intentionally empty */ }
};

const handleDismiss = async (alertId: string) => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/auth/components/PasswordStrengthMeter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const requirements: PasswordRequirement[] = [
{ label: 'Letra maiúscula (A-Z)', test: (p) => /[A-Z]/.test(p), weight: 1 },
{ label: 'Letra minúscula (a-z)', test: (p) => /[a-z]/.test(p), weight: 1 },
{ label: 'Número (0-9)', test: (p) => /\d/.test(p), weight: 1 },
{ label: 'Caractere especial (!@#$%)', test: (p) => /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(p), weight: 1 },
{ label: 'Caractere especial (!@#$%)', test: (p) => /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(p), weight: 1 },
];

// Simple hash function for k-anonymity check
Expand Down
2 changes: 1 addition & 1 deletion src/features/connections/hooks/useConnectionsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export function useConnectionsManager() {
} else {
sessionStorage.setItem(QR_STORAGE_KEY, JSON.stringify(qrCodeDialog));
}
} catch {}
} catch { /* intentionally empty */ }
}, [qrCodeDialog]);

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/inbox/components/chat/MarkdownPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function formatWhatsAppText(text: string): string {
// Code blocks (```...```) — must come before inline
.replace(/```([\s\S]*?)```/g, '<code class="bg-muted/50 px-1.5 py-0.5 rounded text-xs ">$1</code>')
// Bold (*...*) — not greedy, avoid matching **
.replace(/\*([^\*]+)\*/g, '<strong>$1</strong>')
.replace(/\*([^*]+)\*/g, '<strong>$1</strong>')
// Italic (_..._)
.replace(/_((?!_)[^_]+)_/g, '<em>$1</em>')
// Strikethrough (~...~)
Expand Down
2 changes: 1 addition & 1 deletion src/features/inbox/components/linkPreviewUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// URL regex pattern
export const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
export const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi;

const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp'];
const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg', '.mov'];
Expand Down
3 changes: 1 addition & 2 deletions src/features/inbox/hooks/useScheduledMediaUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export function useScheduledMediaUpload() {
const uploadScheduledMedia = useCallback(
async (attachment: File): Promise<UploadResult> => {
const fileName = `scheduled_${Date.now()}_${attachment.name}`;
let mediaUrl: string | undefined;
let messageType = 'text';

const { error: uploadError } = await supabase.storage
Expand All @@ -49,7 +48,7 @@ export function useScheduledMediaUpload() {
.from('whatsapp-media')
.createSignedUrl(fileName, 604800);

mediaUrl = signedData?.signedUrl;
const mediaUrl = signedData?.signedUrl;

messageType = attachment.type.startsWith('audio')
? 'audio'
Expand Down
2 changes: 1 addition & 1 deletion src/features/inbox/hooks/useSipClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function useSipClient() {

const findContactByPhone = useCallback(async (phone: string): Promise<string | null> => {
try {
const n = phone.replace(/[\s\-\(\)]/g, '');
const n = phone.replace(/[\s\-()]/g, '');
const { data, error } = await supabase.from('contacts').select('id').or(`phone.eq.${n},phone.eq.+${n},phone.ilike.%${n.slice(-8)}%`).limit(1).maybeSingle();
Comment on lines +44 to 45

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verifica onde a string .or(...) está sendo montada com interpolação direta
rg -nP --type=ts -C3 '\.or\(`phone\.eq\.\$\{n\},phone\.eq\.\+\$\{n\},phone\.ilike\.\%\$\{n\.slice\(-8\)\}\%`\)'
rg -nP --type=ts -C3 '\bfindContactByPhone\s*\('

Repository: adm01-debug/zapp-web

Length of output: 1480


🏁 Script executed:

# Lê o arquivo completo para entender o contexto
cat -n src/features/inbox/hooks/useSipClient.ts | head -80

Repository: adm01-debug/zapp-web

Length of output: 4749


🏁 Script executed:

# Procura por outras instâncias de phone normalization ou queries diretas ao Supabase
rg -n 'phone\s*=' src/features/inbox/hooks/useSipClient.ts -A 2

Repository: adm01-debug/zapp-web

Length of output: 46


🏁 Script executed:

# Verifica se há migrations ou schema que defina o tipo do campo phone
fd -e sql -e ts | xargs rg -l 'contacts.*phone' | head -5

Repository: adm01-debug/zapp-web

Length of output: 315


🏁 Script executed:

# Procura por outros usos de .or() com interpolação de strings
rg -n '\.or\(`' src/features/inbox/hooks/ -A 1

Repository: adm01-debug/zapp-web

Length of output: 1226


🏁 Script executed:

# Procura por migrations que definem o campo phone
fd -e sql | xargs grep -l 'phone' 2>/dev/null | head -10

Repository: adm01-debug/zapp-web

Length of output: 824


🏁 Script executed:

# Verifica policies de RLS para contacts
fd -e sql | xargs grep -A 3 'policy.*contacts' 2>/dev/null | head -20

Repository: adm01-debug/zapp-web

Length of output: 2142


🏁 Script executed:

# Verifica se há sanitização em useNewConversation que também tem o mesmo padrão
sed -n '40,60p' src/features/inbox/hooks/useNewConversation.ts

Repository: adm01-debug/zapp-web

Length of output: 765


🏁 Script executed:

# Procura pela definição mais recente do campo phone nas migrations
rg -A 5 'CREATE TABLE.*contacts|ALTER TABLE.*contacts' supabase/migrations/ | head -40

Repository: adm01-debug/zapp-web

Length of output: 4262


🏁 Script executed:

# Verifica o tipo do campo phone
rg 'phone\s+(text|varchar|character|numeric|bigint)' supabase/migrations/

Repository: adm01-debug/zapp-web

Length of output: 1293


🏁 Script executed:

# Verifica se há função ou trigger que normaliza phone
rg -i 'normalize.*phone|phone.*format' supabase/

Repository: adm01-debug/zapp-web

Length of output: 5370


Sanitização insuficiente permite injeção de filtro PostgREST

O regex atual /[\s\-()]/g deixa passar caracteres especiais (vírgula, aspas, ponto, etc.) que quebram a sintaxe do filtro .or(). Uma entrada como 123",phone.eq.456,phone.ilike.% passa intacta, corrompendo a query.

Use /\D/g para manter apenas dígitos (números de telefone só precisam disso):

-      const n = phone.replace(/[\s\-()]/g, '');
+      const n = phone.replace(/\D/g, '');
+      if (!n) return null;
       const { data, error } = await supabase.from('contacts').select('id').or(`phone.eq.${n},phone.eq.+${n},phone.ilike.%${n.slice(-8)}%`).limit(1).maybeSingle();

⚠️ Mesmo problema existe em useNewConversation.ts:51: searchQuery não tem sanitização e passa direto para .or(\name.ilike.%${searchQuery}%,phone.ilike.%${searchQuery}%`)`. Adicione validação lá também.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const n = phone.replace(/[\s\-()]/g, '');
const { data, error } = await supabase.from('contacts').select('id').or(`phone.eq.${n},phone.eq.+${n},phone.ilike.%${n.slice(-8)}%`).limit(1).maybeSingle();
const n = phone.replace(/\D/g, '');
if (!n) return null;
const { data, error } = await supabase.from('contacts').select('id').or(`phone.eq.${n},phone.eq.+${n},phone.ilike.%${n.slice(-8)}%`).limit(1).maybeSingle();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/inbox/hooks/useSipClient.ts` around lines 44 - 45, The phone
sanitization in useSipClient.ts currently uses phone.replace(/[\s\-()]/g, '')
which still allows special characters into the PostgREST .or() filter and can
enable injection; change the cleaning to remove all non-digits (usePhone ->
create n from phone by stripping using a non-digit regex) so the value passed to
supabase.from('contacts').select(...).or(...) is only digits, and fail/return
early if result is empty; also fix the analogous issue in useNewConversation.ts
where searchQuery is interpolated into
.or(`name.ilike.%${searchQuery}%,phone.ilike.%${searchQuery}%`) by
validating/sanitizing searchQuery (either allow a safe subset of characters or
escape/normalize percent/quote characters, or reject unsafe input) before
building the query.

return data?.id || null;
} catch { return null; }
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useActionFeedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function useActionFeedback() {
return new Promise((resolve) => {
const { description, undoDuration = 5000, onUndo, onConfirm } = options;
let undone = false;
// eslint-disable-next-line prefer-const -- closure: assigned at line 57 after toast definition uses it
let timeoutId: NodeJS.Timeout;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verifica se o projeto usa NodeJS.Timeout em código de frontend e como o TS está configurado.

echo "== Procurando tsconfig* =="
fd -i '^tsconfig.*\.json$'

echo
echo "== libs/types nos tsconfig* =="
fd -i '^tsconfig.*\.json$' -x sh -c 'echo "--- $1"; sed -n "1,220p" "$1" | rg -n "\"lib\"|\"types\"|dom|node" -n -C2' sh {}

echo
echo "== Ocorrências de NodeJS.Timeout em src =="
rg -n --type ts --type tsx '\bNodeJS\.Timeout\b' src

echo
echo "== Padrão recomendado já existente (ReturnType<typeof setTimeout>) =="
rg -n --type ts --type tsx 'ReturnType<\s*typeof\s+setTimeout\s*>' src

Repository: adm01-debug/zapp-web

Length of output: 856


🏁 Script executed:

cat -n src/hooks/useActionFeedback.ts | sed -n '48,62p'

Repository: adm01-debug/zapp-web

Length of output: 1153


🏁 Script executed:

#!/bin/bash
echo "== Buscando setTimeout/setInterval/clearTimeout em src =="
rg '(setTimeout|setInterval|clearTimeout|clearInterval)' src -B2 -A2 --no-heading | head -100

echo
echo "== Procurando tipagens de timeout em src =="
rg 'Timeout|ReturnType.*setTimeout' src -n --no-heading | head -50

Repository: adm01-debug/zapp-web

Length of output: 10241


Padronize tipagem de timeout com ReturnType<typeof setTimeout> (linha 53)

O projeto estabelece padrão em src/lib/debounce.ts e src/pages/VerifyEmail.tsx usando ReturnType<typeof setTimeout>. Alinhando useActionFeedback.ts mantém consistência de tipagem cross-runtime sem acoplamento a @types/node em ambiente web.

-      let timeoutId: NodeJS.Timeout;
+      let timeoutId: ReturnType<typeof setTimeout>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// eslint-disable-next-line prefer-const -- closure: assigned at line 57 after toast definition uses it
let timeoutId: NodeJS.Timeout;
// eslint-disable-next-line prefer-const -- closure: assigned at line 57 after toast definition uses it
let timeoutId: ReturnType<typeof setTimeout>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useActionFeedback.ts` around lines 52 - 53, The timeoutId variable
in useActionFeedback is typed as NodeJS.Timeout which couples to `@types/node`;
change its type to ReturnType<typeof setTimeout> to match the project's
cross-runtime pattern (as used in src/lib/debounce.ts and
src/pages/VerifyEmail.tsx). Update the declaration of timeoutId (and any related
setTimeout/clearTimeout uses inside the useActionFeedback hook) to use
ReturnType<typeof setTimeout> and remove any NodeJS-specific typing so the hook
remains runtime-agnostic.

const toastResult = showFeedback('info', {
description, duration: undoDuration,
Expand Down
6 changes: 4 additions & 2 deletions src/hooks/useCSAT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ export function useCSAT(period: 'today' | 'week' | 'month' = 'month') {
switch (period) {
case 'today':
return new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
case 'week':
case 'week': {
const weekAgo = new Date(now);
weekAgo.setDate(weekAgo.getDate() - 7);
return weekAgo.toISOString();
case 'month':
}
case 'month': {
const monthAgo = new Date(now);
monthAgo.setMonth(monthAgo.getMonth() - 1);
return monthAgo.toISOString();
}
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useThemeAudit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const useThemeAudit = () => {
try {
const parsed = JSON.parse(saved);
presetId = parsed.preset || DEFAULT_PRESET_ID;
} catch (_e) {}
} catch { /* intentionally empty */ }
}

const preset = PRESETS.find(p => p.id === presetId);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/csvUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function getCsvFilename(prefix: string, suffix?: string): string {
const date = new Date().toISOString().slice(0, 10);
const parts = [prefix, suffix, date].filter(Boolean).join('-');
// Strip unsafe filename chars
return parts.replace(/[^a-zA-Z0-9_\-\.]/g, '_') + '.csv';
return parts.replace(/[^a-zA-Z0-9_\-.]/g, '_') + '.csv';
}

// ── Compat wrappers ──────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion src/lib/evolutionDirectClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function getDirectConfig(): DirectEvoConfig | null {
if (!raw) return null;
const cfg = JSON.parse(raw) as DirectEvoConfig;
if (cfg.evolution_api_url && cfg.evolution_api_key) return cfg;
} catch {}
} catch { /* intentionally empty */ }
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function sanitizeDisplayName(name: string, maxLength = 100): string {
export function sanitizeJid(jid: string): string | null {
// Individual: 5511999999999@s.whatsapp.net
// Group: 120363XXXX@g.us
const jidPattern = /^[\d\-]+@(s\.whatsapp\.net|g\.us|lid|newsletter)$/;
const jidPattern = /^[\d-]+@(s\.whatsapp\.net|g\.us|lid|newsletter)$/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Regex de JID permite entradas inválidas sem dígitos.

Com ^[\d-]+...$, valores como ---@s.whatsapp.net passam na validação, embora o formato esperado no próprio docstring exija dígitos (ou digits-timestamp para grupos). Isso pode deixar JID inválido seguir no fluxo.

Diff sugerido
-  const jidPattern = /^[\d-]+@(s\.whatsapp\.net|g\.us|lid|newsletter)$/;
+  const jidPattern = /^(?:\d+@s\.whatsapp\.net|\d+-\d+@g\.us|\d+@(?:lid|newsletter))$/;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const jidPattern = /^[\d-]+@(s\.whatsapp\.net|g\.us|lid|newsletter)$/;
const jidPattern = /^(?:\d+@s\.whatsapp\.net|\d+-\d+@g\.us|\d+@(?:lid|newsletter))$/;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/security.ts` at line 116, A constante jidPattern aceita strings sem
dígitos porque permite sequências de hífens; atualize jidPattern (a constante
jidPattern) para exigir explicitamente uma sequência inicial de um ou mais
dígitos, opcionalmente seguida de um hífen e outra sequência de um ou mais
dígitos (ou seja, "digits" ou "digits-hyphen-digits"), antes do @ e do sufixo
(s.whatsapp.net | g.us | lid | newsletter), para impedir entradas como
"---@s.whatsapp.net".

const trimmed = jid.trim();

if (jidPattern.test(trimmed)) {
Expand Down
3 changes: 1 addition & 2 deletions src/pages/admin/AdminStressTestPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export default function AdminStressTestPage() {
type: StressTaskType; idx: number; phone: string; instance: string;
}) => {
const sample = await sampleFor(type, idx);
let messageId: string | undefined;
let result: any;
let accessibility: StressResult['accessibility'] | undefined;

Expand Down Expand Up @@ -162,7 +161,7 @@ export default function AdminStressTestPage() {
} as any);
break;
}
messageId = result?.key?.id ?? result?.messageId ?? result?.id;
const messageId = result?.key?.id ?? result?.messageId ?? result?.id;
return { messageId, detail: sample.detail, accessibility };
}, [evo]);

Expand Down
Loading