fix(server): bloqueia SSRF e path traversal na sessão e segue redirects

- SSRF: /api/queue aceitava qualquer URL, inclusive localhost, rede
  privada e 169.254.169.254. Agora só http/https e, por padrão, nada de
  endereço local/privado (checagem na fila e no conector do undici, que
  cobre DNS rebinding e redirects). ALLOW_PRIVATE_NETWORK=true libera.
- O header x-session-id virava nome de diretório sem validação; com ".."
  a expiração faria rmSync em server/. Agora só UUID é aceito.
- Respostas 3xx eram salvas como se fossem o arquivo; agora segue até 5
  redirects e trata 3xx restante como erro.
- decodeURIComponent inválido no Content-Disposition derrubava o download.
- web: useCallback em refreshTasks (corrige o warning exhaustive-deps).
This commit is contained in:
evandro committed 2026-10-10 03:59:44 -03:00
1 parent bc9a43d6de
commit dee0771ac5
7 files changed
+414 -18

No files matched your search

+4 -4
View File
@@ -1,4 +1,4 @@
import React, { createContext, useContext, useEffect, useState } from "react";
import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
type SessionData = {
sessionId: string;
@@ -63,7 +63,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
}, [sessionId]);
// Poll Tasks
const refreshTasks = async () => {
const refreshTasks = useCallback(async () => {
if (!sessionId) return;
try {
const res = await fetch(`${API_URL}/api/tasks`, {
@@ -76,7 +76,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
} catch (err) {
console.error("Failed to fetch tasks", err);
}
};
}, [sessionId]);
useEffect(() => {
if (!sessionId) return;
@@ -87,7 +87,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
// Poll every 2 seconds
const interval = setInterval(refreshTasks, 2000);
return () => clearInterval(interval);
}, [sessionId]);
}, [sessionId, refreshTasks]);
const queueDownload = async (url: string) => {
if (!sessionId) throw new Error("Session not ready, try again in a moment");