fix(segurança): corrigir 10 vulnerabilidades do relatório de segurança

- tokens: remover aceite de expiresAt NULL e forçar TTL de 1 ano
- tokens: corrigir refresh que invalidava access token anterior
- xlsx: desabilitar parsing de fórmulas (CVE-2024-44294)
- csp: expandir Content-Security-Policy com origens explícitas
- headers: adicionar Referrer-Policy e X-Permitted-Cross-Domain-Policies
- api: retornar 401 JSON em vez de redirect 302 em rotas autenticadas
- health: remover version disclosure do /api/health
- robots.txt: simplificar para não expor rotas internas
- sitemap: corrigir URL com protocolo duplicado
- criar security.txt (RFC 9116)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-04 02:47:05 +00:00
parent fd4d90a53e
commit 10afef9fec
17 changed files with 113 additions and 52 deletions

View File

@@ -1,7 +1,7 @@
import { and, eq, gt, isNull } from "drizzle-orm";
import { NextResponse } from "next/server";
import { apiTokens } from "@/db/schema";
import { extractBearerToken, hashToken } from "@/shared/lib/auth/api-token";
import { extractBearerToken, verifyJwt } from "@/shared/lib/auth/api-token";
import { db } from "@/shared/lib/db";
export async function POST(request: Request) {
@@ -17,21 +17,21 @@ export async function POST(request: Request) {
);
}
// Validar token os_xxx via hash lookup
if (!token.startsWith("os_")) {
// Verificar JWT (assinatura + expiração)
const payload = verifyJwt(token);
if (!payload || payload.type !== "api_access") {
return NextResponse.json(
{ valid: false, error: "Formato de token inválido" },
{ valid: false, error: "Token inválido ou expirado" },
{ status: 401 },
);
}
// Hash do token para buscar no DB
const tokenHash = hashToken(token);
// Buscar token no banco
// Buscar token no banco por tokenId para checar revogação
const tokenRecord = await db.query.apiTokens.findFirst({
where: and(
eq(apiTokens.tokenHash, tokenHash),
eq(apiTokens.id, payload.tokenId),
eq(apiTokens.userId, payload.sub),
isNull(apiTokens.revokedAt),
gt(apiTokens.expiresAt, new Date()),
),
@@ -39,7 +39,7 @@ export async function POST(request: Request) {
if (!tokenRecord) {
return NextResponse.json(
{ valid: false, error: "Token inválido ou revogado" },
{ valid: false, error: "Token revogado ou não encontrado" },
{ status: 401 },
);
}