Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .Jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 2 additions & 4 deletions backend/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
63 changes: 63 additions & 0 deletions backend/src/tests/auth-middleware.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { requireAdmin, AuthRequest } from '../middleware/auth';
import { Response, NextFunction } from 'express';

describe('requireAdmin Middleware', () => {
let mockReq: Partial<AuthRequest>;
let mockRes: Partial<Response>;
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);
});
});
Loading