From 43697b4fd24498f41eedefa26cec812b55fc340e Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Tue, 7 Apr 2026 13:49:23 +0000 Subject: [PATCH] fix(csp): mover CSP para proxy.ts para leitura em runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Content-Security-Policy estava em next.config.ts (build time), então S3_ENDPOINT nunca era incluído no connect-src ao buildar via Docker no CI. Movido para proxy.ts que avalia em runtime. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 6 ++++++ next.config.ts | 14 -------------- package.json | 2 +- src/proxy.ts | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1aff79..ebe6579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR ## [Unreleased] +## [2.3.5] - 2026-04-07 + +### Corrigido + +- CSP: movido `Content-Security-Policy` do `next.config.ts` (build time) para `proxy.ts` (runtime), corrigindo bloqueio de upload de anexos quando `S3_ENDPOINT` não estava disponível durante o build do Docker + ## [2.3.4] - 2026-04-05 ### Corrigido diff --git a/next.config.ts b/next.config.ts index 872995b..145c047 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,8 +4,6 @@ import type { NextConfig } from "next"; // Carregar variáveis de ambiente explicitamente dotenv.config(); -const isDev = process.env.NODE_ENV === "development"; - const nextConfig: NextConfig = { output: "standalone", cacheComponents: true, @@ -44,18 +42,6 @@ const nextConfig: NextConfig = { key: "X-Frame-Options", value: "DENY", }, - { - key: "Content-Security-Policy", - value: [ - "default-src 'self'", - `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} https://umami.felipecoutinho.com`, - "style-src 'self' 'unsafe-inline'", - "img-src 'self' https://lh3.googleusercontent.com data: blob:", - "font-src 'self'", - `connect-src 'self' https://umami.felipecoutinho.com ${process.env.S3_ENDPOINT ? new URL(process.env.S3_ENDPOINT).origin : ""}`.trim(), - "frame-ancestors 'none'", - ].join("; "), - }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin", diff --git a/package.json b/package.json index 3ce65d2..7df4dd3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmonetis", - "version": "2.3.4", + "version": "2.3.5", "private": true, "packageManager": "pnpm@10.33.0", "scripts": { diff --git a/src/proxy.ts b/src/proxy.ts index 5561a42..011a964 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -21,6 +21,38 @@ const PROTECTED_ROUTES = [ // Rotas públicas (não requerem autenticação) const PUBLIC_AUTH_ROUTES = ["/login", "/signup"]; +function buildCsp(): string { + const isDev = process.env.NODE_ENV === "development"; + + const s3Origin = (() => { + try { + return process.env.S3_ENDPOINT + ? new URL(process.env.S3_ENDPOINT).origin + : ""; + } catch { + return ""; + } + })(); + + const connectExtras = ["https://umami.felipecoutinho.com", s3Origin] + .filter(Boolean) + .join(" "); + + const imgExtras = ["https://lh3.googleusercontent.com", s3Origin] + .filter(Boolean) + .join(" "); + + return [ + "default-src 'self'", + `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} https://umami.felipecoutinho.com`, + "style-src 'self' 'unsafe-inline'", + `img-src 'self' ${imgExtras} data: blob:`, + "font-src 'self'", + `connect-src 'self' ${connectExtras}`, + "frame-ancestors 'none'", + ].join("; "); +} + export default async function proxy(request: NextRequest) { const { pathname } = request.nextUrl; @@ -63,7 +95,9 @@ export default async function proxy(request: NextRequest) { return NextResponse.redirect(new URL("/login", request.url)); } - return NextResponse.next(); + const response = NextResponse.next(); + response.headers.set("Content-Security-Policy", buildCsp()); + return response; } export const config = {