From 16bdbc3e16adfacc6a8d2b1671fc20b29a986a09 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 17:28:33 +0000 Subject: [PATCH] feat(security): remove hardcoded admin email backdoor - Removed hardcoded 'gustavo.caetano@gmail.com' from default admin list in `backend/src/middleware/auth.ts` - Added `backend/src/tests/auth-middleware.test.ts` to verify access control relies solely on environment variables - Updated security journal in `.Jules/sentinel.md` This fixes a critical vulnerability where a specific email address was granted admin privileges regardless of configuration. Access is now controlled strictly via the `ADMIN_EMAILS` environment variable. Co-authored-by: criptogus <128640021+criptogus@users.noreply.github.com> --- .Jules/sentinel.md | 5 ++ backend/src/middleware/auth.ts | 6 +-- backend/src/tests/auth-middleware.test.ts | 63 +++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 backend/src/tests/auth-middleware.test.ts diff --git a/.Jules/sentinel.md b/.Jules/sentinel.md index f1766d577..c9573356d 100644 --- a/.Jules/sentinel.md +++ b/.Jules/sentinel.md @@ -17,3 +17,8 @@ **Vulnerability:** A Stored XSS vulnerability was identified in `src/components/preview/viewers/MarkdownViewer.tsx`. The component used a custom regex-based Markdown parser that failed to validate URL protocols in links, allowing payloads like `[click](javascript:alert(1))` to execute arbitrary JavaScript. **Learning:** Custom regex parsers for complex formats like Markdown are notoriously prone to security bypasses. Validating input structure via regex is insufficient for preventing XSS in HTML output; context-aware sanitization is required. **Prevention:** Integrated `DOMPurify` to sanitize the HTML output of the custom parser. Configured it to allow necessary tags and attributes (like `class` for styling) while stripping dangerous content (like `javascript:` URIs). + +## 2026-02-24 - [Hardcoded Admin Privilege] +**Vulnerability:** A specific email address (`gustavo.caetano@gmail.com`) was hardcoded in `backend/src/middleware/auth.ts` to always grant administrator privileges, regardless of environment configuration. +**Learning:** Hardcoding privileges ("backdoors") for convenience during development is a persistent risk that often survives into production. Code reviews should specifically flag any specific identifiers (emails, IDs, usernames) used in authorization logic. +**Prevention:** Removed the hardcoded email and enforced strict usage of the `ADMIN_EMAILS` environment variable. Added a regression test to verify that the specific email is denied access unless explicitly configured in the environment. diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts index 386ac3344..f8859901b 100644 --- a/backend/src/middleware/auth.ts +++ b/backend/src/middleware/auth.ts @@ -120,10 +120,8 @@ export async function requireAdmin(req: AuthRequest, res: Response, next: NextFu return res.status(401).json({ error: 'Usuário não autenticado' }); } - // Lista de emails de admin (gustavo.caetano@gmail.com + variável de ambiente) - const defaultAdminEmails = ['gustavo.caetano@gmail.com']; - const envAdminEmails = (process.env.ADMIN_EMAILS || '').split(',').map(e => e.trim().toLowerCase()).filter(e => e); - const adminEmails = [...defaultAdminEmails, ...envAdminEmails].map(e => e.toLowerCase()); + // Lista de emails de admin (variável de ambiente) + const adminEmails = (process.env.ADMIN_EMAILS || '').split(',').map(e => e.trim().toLowerCase()).filter(e => e); const userEmail = req.user.email?.toLowerCase(); diff --git a/backend/src/tests/auth-middleware.test.ts b/backend/src/tests/auth-middleware.test.ts new file mode 100644 index 000000000..9fbff1323 --- /dev/null +++ b/backend/src/tests/auth-middleware.test.ts @@ -0,0 +1,63 @@ +import { requireAdmin, AuthRequest } from '../middleware/auth'; +import { Response, NextFunction } from 'express'; + +describe('requireAdmin Middleware', () => { + let mockReq: Partial; + let mockRes: Partial; + let mockNext: NextFunction; + const originalEnv = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...originalEnv }; + process.env.ADMIN_EMAILS = ''; // Ensure clean state + + mockReq = { + user: { + id: '123', + email: 'test@example.com' + } + }; + mockRes = { + status: jest.fn().mockReturnThis(), + json: jest.fn() + }; + mockNext = jest.fn(); + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it('should DENY previously hardcoded email "gustavo.caetano@gmail.com" when env is empty', async () => { + mockReq.user!.email = 'gustavo.caetano@gmail.com'; + + await requireAdmin(mockReq as AuthRequest, mockRes as Response, mockNext); + + expect(mockNext).not.toHaveBeenCalled(); + expect(mockRes.status).toHaveBeenCalledWith(403); + expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({ + code: 'ADMIN_REQUIRED' + })); + }); + + it('should ALLOW access if email is in ADMIN_EMAILS env var', async () => { + process.env.ADMIN_EMAILS = 'gustavo.caetano@gmail.com,another@admin.com'; + mockReq.user!.email = 'gustavo.caetano@gmail.com'; + + await requireAdmin(mockReq as AuthRequest, mockRes as Response, mockNext); + + expect(mockNext).toHaveBeenCalled(); + expect(mockRes.status).not.toHaveBeenCalled(); + }); + + it('should deny other emails even if ADMIN_EMAILS is set', async () => { + process.env.ADMIN_EMAILS = 'admin@example.com'; + mockReq.user!.email = 'user@example.com'; + + await requireAdmin(mockReq as AuthRequest, mockRes as Response, mockNext); + + expect(mockNext).not.toHaveBeenCalled(); + expect(mockRes.status).toHaveBeenCalledWith(403); + }); +});