Files
mcp-outline-postgresql/docs/audits/2026-01-31-v1.2.1/PLANO-MELHORIAS.md
Emanuel Almeida 7c83a9e168 fix(security): Resolve 21 SQL injection vulnerabilities and add transactions
Security fixes (v1.2.2):
- Fix SQL injection in analytics.ts (16 occurrences)
- Fix SQL injection in advanced-search.ts (1 occurrence)
- Fix SQL injection in search-queries.ts (1 occurrence)
- Add validateDaysInterval(), isValidISODate(), validatePeriod() to security.ts
- Use make_interval(days => N) for safe PostgreSQL intervals
- Validate UUIDs BEFORE string construction

Transaction support:
- bulk-operations.ts: 6 atomic operations with withTransaction()
- desk-sync.ts: 2 operations with transactions
- export-import.ts: 1 operation with transaction

Rate limiting:
- Add automatic cleanup of expired entries (every 5 minutes)

Audit:
- Archive previous audit docs to docs/audits/2026-01-31-v1.2.1/
- Create new AUDIT-REQUEST.md for v1.2.2 verification

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:47:41 +00:00

1197 lines
27 KiB
Markdown

# Plano de Melhorias - MCP Outline PostgreSQL v1.2.1
**Data:** 2026-01-31
**Projecto:** MCP Outline PostgreSQL
**Versão Actual:** 1.2.1
**Versão Alvo:** 2.0.0 (Production-Ready)
---
## 🎯 Objectivo
Transformar o MCP Outline PostgreSQL de um protótipo funcional (7.2/10) num servidor production-ready (9.5/10) através de correcções de segurança críticas, optimizações de performance e melhorias de qualidade.
---
## 📋 Resumo das Melhorias
| Fase | Foco | Duração | Prioridade | Tarefas |
|------|------|---------|------------|---------|
| **Fase 1** | Segurança Crítica | 2 semanas | 🔴 P0 | 12 |
| **Fase 2** | Performance | 1 semana | 🟡 P1 | 10 |
| **Fase 3** | Qualidade | 2 semanas | 🟢 P2 | 15 |
| **Fase 4** | Funcionalidades | Ongoing | 🟢 P3 | 15 |
**Total:** 52 tarefas | 5 semanas de trabalho core
---
## 🔴 FASE 1: Segurança Crítica (P0)
**Duração:** 2 semanas
**Prioridade:** CRÍTICA
**Bloqueante:** SIM - Não deploy sem esta fase completa
### Objectivos
- ✅ Eliminar 100% das vulnerabilidades de SQL Injection
- ✅ Implementar transacções em todas as operações críticas
- ✅ Adicionar validação robusta de inputs
- ✅ Implementar audit logging básico
---
### Tarefas Detalhadas
#### 1.1 Corrigir SQL Injection (Semana 1)
**Problema:** 164 tools com potencial SQL injection via string concatenation.
**Solução:** Converter todas as queries para prepared statements parametrizados.
##### 1.1.1 Auditar e Catalogar Queries Vulneráveis
```bash
# Script para identificar queries vulneráveis
grep -r "pool.query(\`" src/tools/ > vulnerable-queries.txt
```
**Ficheiros Prioritários:**
- [ ] `documents.ts` (19 tools) - 2 dias
- [ ] `collections.ts` (14 tools) - 1.5 dias
- [ ] `users.ts` (9 tools) - 1 dia
- [ ] `advanced-search.ts` (6 tools) - 1 dia
- [ ] `analytics.ts` (6 tools) - 1 dia
- [ ] Restantes 27 ficheiros - 2 dias
**Total:** 8.5 dias
##### 1.1.2 Criar Função Helper para Queries Seguras
**Ficheiro:** `src/utils/query-builder.ts`
```typescript
/**
* Query builder seguro com prepared statements
*/
export class SafeQueryBuilder {
private params: any[] = [];
private paramIndex = 1;
/**
* Adiciona parâmetro e retorna placeholder
*/
addParam(value: any): string {
this.params.push(value);
return `$${this.paramIndex++}`;
}
/**
* Constrói WHERE clause com ILIKE seguro
*/
buildILike(column: string, value: string): string {
return `${column} ILIKE ${this.addParam(`%${sanitizeInput(value)}%`)}`;
}
/**
* Constrói IN clause seguro
*/
buildIn(column: string, values: any[]): string {
const placeholders = values.map(v => this.addParam(v)).join(', ');
return `${column} IN (${placeholders})`;
}
getParams(): any[] {
return this.params;
}
}
```
**Estimativa:** 0.5 dias
##### 1.1.3 Refactoring de Queries
**Antes (VULNERÁVEL):**
```typescript
const query = `
SELECT * FROM documents
WHERE title ILIKE '%${args.query}%'
AND collectionId = '${args.collection_id}'
`;
const result = await pool.query(query);
```
**Depois (SEGURO):**
```typescript
const qb = new SafeQueryBuilder();
const query = `
SELECT * FROM documents
WHERE title ILIKE ${qb.addParam(`%${sanitizeInput(args.query)}%`)}
AND collectionId = ${qb.addParam(args.collection_id)}
`;
const result = await pool.query(query, qb.getParams());
```
**Estimativa:** 8 dias (todos os ficheiros)
##### 1.1.4 Adicionar Linting Rule
**Ficheiro:** `.eslintrc.json`
```json
{
"rules": {
"no-template-curly-in-string": "error",
"no-restricted-syntax": [
"error",
{
"selector": "TemplateLiteral[parent.callee.property.name='query']",
"message": "Use parameterized queries to prevent SQL injection"
}
]
}
}
```
**Estimativa:** 0.5 dias
---
#### 1.2 Implementar Transacções (Semana 2)
**Problema:** Operações multi-write sem atomicidade.
**Solução:** Envolver operações críticas em transacções.
##### 1.2.1 Identificar Operações Críticas
**Ficheiros Afectados:**
- [ ] `bulk-operations.ts` - 6 tools
- [ ] `desk-sync.ts` - 2 tools
- [ ] `export-import.ts` - 2 tools
- [ ] `collections.ts` - memberships (4 tools)
- [ ] `documents.ts` - create/update com memberships (2 tools)
**Total:** 16 tools
##### 1.2.2 Criar Transaction Helper
**Ficheiro:** `src/utils/transaction.ts`
```typescript
import { Pool, PoolClient } from 'pg';
/**
* Executa operação em transacção com retry automático
*/
export async function withTransaction<T>(
pool: Pool,
callback: (client: PoolClient) => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const client = await pool.connect();
try {
await client.query('BEGIN');
const result = await callback(client);
await client.query('COMMIT');
return result;
} catch (error) {
await client.query('ROLLBACK');
lastError = error as Error;
// Retry apenas em deadlocks
if (error instanceof Error && error.message.includes('deadlock')) {
await new Promise(resolve => setTimeout(resolve, 100 * attempt));
continue;
}
throw error;
} finally {
client.release();
}
}
throw lastError!;
}
```
**Estimativa:** 0.5 dias
##### 1.2.3 Refactoring de Operações Bulk
**Antes (SEM TRANSACÇÃO):**
```typescript
handler: async (args, pgClient) => {
const pool = pgClient.getPool();
for (const id of args.document_ids) {
await pool.query('UPDATE documents SET archivedAt = NOW() WHERE id = $1', [id]);
}
}
```
**Depois (COM TRANSACÇÃO):**
```typescript
handler: async (args, pgClient) => {
const pool = pgClient.getPool();
return await withTransaction(pool, async (client) => {
const results = [];
for (const id of args.document_ids) {
const result = await client.query(
'UPDATE documents SET archivedAt = NOW() WHERE id = $1 RETURNING *',
[id]
);
results.push(result.rows[0]);
}
return results;
});
}
```
**Estimativa:** 2 dias (16 tools)
##### 1.2.4 Testes de Rollback
**Ficheiro:** `tests/transactions.test.ts`
```typescript
describe('Transaction Rollback', () => {
it('should rollback on error in bulk operations', async () => {
const invalidIds = ['valid-uuid', 'INVALID'];
await expect(
bulkArchiveDocuments({ document_ids: invalidIds }, pgClient)
).rejects.toThrow();
// Verificar que nenhum documento foi arquivado
const result = await pool.query(
'SELECT * FROM documents WHERE archivedAt IS NOT NULL'
);
expect(result.rows).toHaveLength(0);
});
});
```
**Estimativa:** 1 dia
---
#### 1.3 Validação de Inputs (Semana 2)
##### 1.3.1 Implementar Validação Automática
**Ficheiro:** `src/utils/validation.ts`
```typescript
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
/**
* Valida args contra inputSchema
*/
export function validateToolInput<T>(
args: unknown,
schema: object
): T {
const validate = ajv.compile(schema);
if (!validate(args)) {
const errors = validate.errors?.map(e => `${e.instancePath} ${e.message}`).join(', ');
throw new Error(`Invalid input: ${errors}`);
}
return args as T;
}
/**
* Middleware para validação automática
*/
export function withValidation<T>(
tool: BaseTool<T>
): BaseTool<T> {
const originalHandler = tool.handler;
return {
...tool,
handler: async (args, pgClient) => {
const validatedArgs = validateToolInput<T>(args, tool.inputSchema);
return originalHandler(validatedArgs, pgClient);
}
};
}
```
**Estimativa:** 1 dia
##### 1.3.2 Adicionar Validações Específicas
**Melhorias em `src/utils/security.ts`:**
```typescript
/**
* Valida array de UUIDs
*/
export function validateUUIDs(uuids: string[]): void {
const invalid = uuids.filter(uuid => !isValidUUID(uuid));
if (invalid.length > 0) {
throw new Error(`Invalid UUIDs: ${invalid.join(', ')}`);
}
}
/**
* Valida enum value
*/
export function validateEnum<T>(
value: string,
allowedValues: T[],
fieldName: string
): T {
if (!allowedValues.includes(value as T)) {
throw new Error(
`Invalid ${fieldName}: ${value}. Allowed: ${allowedValues.join(', ')}`
);
}
return value as T;
}
/**
* Valida tamanho de string
*/
export function validateStringLength(
value: string,
min: number,
max: number,
fieldName: string
): void {
if (value.length < min || value.length > max) {
throw new Error(
`${fieldName} must be between ${min} and ${max} characters`
);
}
}
```
**Estimativa:** 0.5 dias
---
#### 1.4 Audit Logging Básico (Semana 2)
##### 1.4.1 Criar Sistema de Audit Log
**Ficheiro:** `src/utils/audit.ts`
```typescript
import { Pool } from 'pg';
export interface AuditLogEntry {
userId?: string;
action: string;
resourceType: string;
resourceId: string;
metadata?: Record<string, any>;
}
/**
* Regista operação em audit log
*/
export async function logAudit(
pool: Pool,
entry: AuditLogEntry
): Promise<void> {
await pool.query(
`INSERT INTO events (name, actorId, modelId, data, createdAt)
VALUES ($1, $2, $3, $4, NOW())`,
[
entry.action,
entry.userId || null,
entry.resourceId,
JSON.stringify({
resourceType: entry.resourceType,
...entry.metadata
})
]
);
}
/**
* Middleware para audit logging automático
*/
export function withAuditLog<T>(
tool: BaseTool<T>,
getResourceInfo: (args: T) => { type: string; id: string }
): BaseTool<T> {
const originalHandler = tool.handler;
return {
...tool,
handler: async (args, pgClient) => {
const result = await originalHandler(args, pgClient);
// Log apenas operações de escrita
if (['create', 'update', 'delete'].some(op => tool.name.includes(op))) {
const resource = getResourceInfo(args);
await logAudit(pgClient.getPool(), {
action: tool.name,
resourceType: resource.type,
resourceId: resource.id
});
}
return result;
}
};
}
```
**Estimativa:** 1 dia
---
### Checklist Fase 1
- [ ] **SQL Injection**
- [ ] Auditar 164 tools
- [ ] Criar SafeQueryBuilder
- [ ] Refactoring completo
- [ ] Adicionar linting rule
- [ ] Testar manualmente cada módulo
- [ ] **Transacções**
- [ ] Identificar operações críticas
- [ ] Criar transaction helper
- [ ] Refactoring bulk operations
- [ ] Implementar testes de rollback
- [ ] **Validação**
- [ ] Implementar validação automática
- [ ] Adicionar validações específicas
- [ ] Aplicar a todos os tools
- [ ] **Audit Log**
- [ ] Criar sistema de logging
- [ ] Integrar com tools de escrita
- [ ] Testar logging
### Métricas de Sucesso Fase 1
- ✅ 0 queries com string concatenation
- ✅ 100% queries parametrizadas
- ✅ 16 operações críticas com transacções
- ✅ 100% tools com validação de input
- ✅ Audit log funcional em operações de escrita
---
## 🟡 FASE 2: Performance (P1)
**Duração:** 1 semana
**Prioridade:** ALTA
**Bloqueante:** NÃO - Mas recomendado antes de produção
### Objectivos
- ✅ Eliminar N+1 queries
- ✅ Criar índices necessários
- ✅ Optimizar connection pool
- ✅ Implementar cursor-based pagination
---
### Tarefas Detalhadas
#### 2.1 Eliminar N+1 Queries (2 dias)
##### 2.1.1 Identificar N+1 Queries
**Ficheiros Afectados:**
- `collections.ts:1253-1280` - export_all_collections
- `documents.ts:530-577` - list_drafts
- `analytics.ts` - várias queries
##### 2.1.2 Refactoring com JOINs
**Antes (N+1):**
```typescript
const collections = await pool.query('SELECT * FROM collections');
for (const collection of collections.rows) {
const docs = await pool.query(
'SELECT * FROM documents WHERE collectionId = $1',
[collection.id]
);
collection.documents = docs.rows;
}
```
**Depois (JOIN):**
```typescript
const result = await pool.query(`
SELECT
c.*,
json_agg(
json_build_object(
'id', d.id,
'title', d.title,
'createdAt', d.createdAt
)
) FILTER (WHERE d.id IS NOT NULL) as documents
FROM collections c
LEFT JOIN documents d ON d.collectionId = c.id AND d.deletedAt IS NULL
GROUP BY c.id
`);
```
**Estimativa:** 2 dias
---
#### 2.2 Criar Índices (1 dia)
##### 2.2.1 Documentar Índices Necessários
**Ficheiro:** `migrations/001_indexes.sql`
```sql
-- Full-text search
CREATE INDEX IF NOT EXISTS idx_documents_search
ON documents USING gin(to_tsvector('english', title || ' ' || COALESCE(text, '')));
-- Queries comuns
CREATE INDEX IF NOT EXISTS idx_documents_collection_id
ON documents(collectionId) WHERE deletedAt IS NULL;
CREATE INDEX IF NOT EXISTS idx_documents_published
ON documents(publishedAt DESC) WHERE deletedAt IS NULL AND publishedAt IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_documents_created
ON documents(createdAt DESC) WHERE deletedAt IS NULL;
-- Memberships
CREATE INDEX IF NOT EXISTS idx_collection_memberships_lookup
ON collection_memberships(collectionId, userId);
CREATE INDEX IF NOT EXISTS idx_group_memberships_lookup
ON group_memberships(groupId, userId);
-- Stars, Pins, Views
CREATE INDEX IF NOT EXISTS idx_stars_user_document
ON stars(userId, documentId) WHERE deletedAt IS NULL;
CREATE INDEX IF NOT EXISTS idx_pins_collection_document
ON pins(collectionId, documentId) WHERE deletedAt IS NULL;
CREATE INDEX IF NOT EXISTS idx_views_document_user
ON views(documentId, userId, createdAt DESC);
-- Events (audit log)
CREATE INDEX IF NOT EXISTS idx_events_actor_created
ON events(actorId, createdAt DESC);
CREATE INDEX IF NOT EXISTS idx_events_model_created
ON events(modelId, createdAt DESC);
```
**Estimativa:** 0.5 dias
##### 2.2.2 Documentar em SPEC
**Adicionar secção em `SPEC-MCP-OUTLINE.md`:**
```markdown
## Índices Recomendados
Para performance optimal, execute as migrations em `migrations/001_indexes.sql`.
### Índices Críticos
| Índice | Tabela | Tipo | Impacto |
|--------|--------|------|---------|
| idx_documents_search | documents | GIN | Full-text search 10-100x faster |
| idx_documents_collection_id | documents | B-tree | List documents 5-10x faster |
| idx_collection_memberships_lookup | collection_memberships | B-tree | Permission checks 10x faster |
```
**Estimativa:** 0.5 dias
---
#### 2.3 Optimizar Connection Pool (1 dia)
##### 2.3.1 Tuning de Pool
**Ficheiro:** `src/config/database.ts`
```typescript
export interface DatabaseConfig {
// ... existing fields
// Pool tuning
max?: number; // Default: 20
min?: number; // Default: 5
idleTimeoutMillis?: number; // Default: 30000
connectionTimeoutMillis?: number; // Default: 5000
maxUses?: number; // Default: 7500 (recycle connections)
// Performance
statementTimeout?: number; // Default: 30000 (30s)
queryTimeout?: number; // Default: 10000 (10s)
}
export function getDefaultConfig(): DatabaseConfig {
return {
max: parseInt(process.env.DB_POOL_MAX || '20', 10),
min: parseInt(process.env.DB_POOL_MIN || '5', 10),
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
maxUses: 7500,
statementTimeout: 30000,
queryTimeout: 10000
};
}
```
**Estimativa:** 0.5 dias
##### 2.3.2 Adicionar Pool Monitoring
**Ficheiro:** `src/utils/monitoring.ts`
```typescript
import { Pool } from 'pg';
export function monitorPool(pool: Pool): void {
setInterval(() => {
logger.info('Pool stats', {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount
});
// Alert se pool saturado
if (pool.waitingCount > 5) {
logger.warn('Pool saturation detected', {
waiting: pool.waitingCount,
total: pool.totalCount
});
}
}, 60000); // A cada minuto
}
```
**Estimativa:** 0.5 dias
---
#### 2.4 Cursor-Based Pagination (1 dia)
##### 2.4.1 Implementar Cursor Pagination
**Ficheiro:** `src/utils/pagination.ts`
```typescript
export interface CursorPaginationArgs {
limit?: number;
cursor?: string; // Base64 encoded timestamp ou ID
}
export interface CursorPaginationResult<T> {
items: T[];
nextCursor?: string;
hasMore: boolean;
}
/**
* Cursor-based pagination (mais eficiente que OFFSET)
*/
export async function paginateWithCursor<T>(
pool: Pool,
baseQuery: string,
cursorField: string,
args: CursorPaginationArgs
): Promise<CursorPaginationResult<T>> {
const limit = Math.min(args.limit || 25, 100);
let query = baseQuery;
const params: any[] = [limit + 1]; // +1 para detectar hasMore
if (args.cursor) {
const cursorValue = Buffer.from(args.cursor, 'base64').toString();
query += ` AND ${cursorField} < $2`;
params.push(cursorValue);
}
query += ` ORDER BY ${cursorField} DESC LIMIT $1`;
const result = await pool.query<T>(query, params);
const hasMore = result.rows.length > limit;
const items = hasMore ? result.rows.slice(0, -1) : result.rows;
const nextCursor = hasMore && items.length > 0
? Buffer.from(String((items[items.length - 1] as any)[cursorField])).toString('base64')
: undefined;
return { items, nextCursor, hasMore };
}
```
**Estimativa:** 1 dia
---
### Checklist Fase 2
- [ ] **N+1 Queries**
- [ ] Identificar todas as ocorrências
- [ ] Refactoring com JOINs
- [ ] Testar performance
- [ ] **Índices**
- [ ] Criar migrations/001_indexes.sql
- [ ] Documentar em SPEC
- [ ] Testar impacto
- [ ] **Connection Pool**
- [ ] Tuning de configuração
- [ ] Adicionar monitoring
- [ ] Testar sob carga
- [ ] **Pagination**
- [ ] Implementar cursor-based
- [ ] Migrar tools principais
- [ ] Benchmark vs OFFSET
### Métricas de Sucesso Fase 2
- ✅ 0 N+1 queries em hot paths
- ✅ Queries < 100ms (p95)
- ✅ Índices criados e documentados
- ✅ Pool utilization < 80%
- ✅ Cursor pagination em listagens principais
---
## 🟢 FASE 3: Qualidade (P2)
**Duração:** 2 semanas
**Prioridade:** MÉDIA
**Bloqueante:** NÃO - Melhoria contínua
### Objectivos
- ✅ Implementar testes unitários
- ✅ Adicionar testes de integração
- ✅ Configurar CI/CD
- ✅ Refactoring de código duplicado
- ✅ Melhorar documentação
---
### Tarefas Detalhadas
#### 3.1 Testes Unitários (1 semana)
##### 3.1.1 Setup de Testing
**Ficheiro:** `package.json`
```json
{
"devDependencies": {
"vitest": "^1.0.0",
"@vitest/coverage-v8": "^1.0.0",
"testcontainers": "^10.0.0"
},
"scripts": {
"test": "vitest",
"test:coverage": "vitest --coverage",
"test:ui": "vitest --ui"
}
}
```
**Ficheiro:** `vitest.config.ts`
```typescript
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['dist/', 'tests/', '**/*.test.ts']
}
}
});
```
**Estimativa:** 0.5 dias
##### 3.1.2 Testes de Utils
**Ficheiro:** `tests/utils/security.test.ts`
```typescript
import { describe, it, expect } from 'vitest';
import {
isValidUUID,
isValidEmail,
sanitizeInput,
validatePagination
} from '../../src/utils/security';
describe('Security Utils', () => {
describe('isValidUUID', () => {
it('should validate correct UUIDs', () => {
expect(isValidUUID('123e4567-e89b-12d3-a456-426614174000')).toBe(true);
});
it('should reject invalid UUIDs', () => {
expect(isValidUUID('not-a-uuid')).toBe(false);
expect(isValidUUID('')).toBe(false);
});
});
describe('sanitizeInput', () => {
it('should remove null bytes', () => {
expect(sanitizeInput('test\0data')).toBe('testdata');
});
it('should trim whitespace', () => {
expect(sanitizeInput(' test ')).toBe('test');
});
});
describe('validatePagination', () => {
it('should enforce max limit', () => {
const result = validatePagination(1000, 0);
expect(result.limit).toBe(100);
});
it('should use defaults', () => {
const result = validatePagination();
expect(result.limit).toBe(25);
expect(result.offset).toBe(0);
});
});
});
```
**Estimativa:** 2 dias (todos os utils)
##### 3.1.3 Testes de Tools
**Ficheiro:** `tests/tools/documents.test.ts`
```typescript
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { GenericContainer, StartedTestContainer } from 'testcontainers';
import { Pool } from 'pg';
import { documentsTools } from '../../src/tools/documents';
describe('Documents Tools', () => {
let container: StartedTestContainer;
let pool: Pool;
beforeAll(async () => {
// Start PostgreSQL container
container = await new GenericContainer('postgres:15')
.withEnvironment({ POSTGRES_PASSWORD: 'test' })
.withExposedPorts(5432)
.start();
pool = new Pool({
host: container.getHost(),
port: container.getMappedPort(5432),
user: 'postgres',
password: 'test',
database: 'postgres'
});
// Setup schema
await pool.query(/* schema SQL */);
});
afterAll(async () => {
await pool.end();
await container.stop();
});
it('should list documents', async () => {
const result = await documentsTools[0].handler({}, { getPool: () => pool });
expect(result.content[0].text).toBeDefined();
});
});
```
**Estimativa:** 3 dias (tools principais)
---
#### 3.2 CI/CD (2 dias)
##### 3.2.1 GitHub Actions
**Ficheiro:** `.github/workflows/ci.yml`
```yaml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
- run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
```
**Estimativa:** 1 dia
---
#### 3.3 Refactoring (3 dias)
##### 3.3.1 Eliminar Duplicação
**Criar:** `src/utils/tool-factory.ts`
```typescript
/**
* Factory para criar tools com padrão consistente
*/
export function createTool<T>(config: {
name: string;
description: string;
inputSchema: object;
handler: (args: T, pool: Pool) => Promise<any>;
requiresTransaction?: boolean;
auditLog?: boolean;
}): BaseTool<T> {
return {
name: config.name,
description: config.description,
inputSchema: config.inputSchema,
handler: async (args, pgClient) => {
try {
// Validação automática
const validatedArgs = validateToolInput<T>(args, config.inputSchema);
const pool = pgClient.getPool();
// Executar com ou sem transacção
const result = config.requiresTransaction
? await withTransaction(pool, client => config.handler(validatedArgs, client))
: await config.handler(validatedArgs, pool);
// Audit log automático
if (config.auditLog) {
await logAudit(pool, {
action: config.name,
resourceType: extractResourceType(config.name),
resourceId: extractResourceId(result)
});
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
logger.error(`Tool ${config.name} failed`, { error });
throw error;
}
}
};
}
```
**Estimativa:** 2 dias
##### 3.3.2 Aplicar Factory
**Refactoring de tools para usar factory:**
```typescript
// Antes
const listDocuments: BaseTool<DocumentArgs> = {
name: 'list_documents',
description: '...',
inputSchema: { /* ... */ },
handler: async (args, pgClient) => {
try {
const pool = pgClient.getPool();
// ... lógica
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
} catch (error) {
// ...
}
}
};
// Depois
const listDocuments = createTool<DocumentArgs>({
name: 'list_documents',
description: '...',
inputSchema: { /* ... */ },
handler: async (args, pool) => {
// ... apenas lógica de negócio
return result;
}
});
```
**Estimativa:** 1 dia
---
### Checklist Fase 3
- [ ] **Testes**
- [ ] Setup Vitest + Testcontainers
- [ ] Testes de utils (100% coverage)
- [ ] Testes de tools principais (>80% coverage)
- [ ] Testes de integração
- [ ] **CI/CD**
- [ ] GitHub Actions
- [ ] Codecov integration
- [ ] Automated releases
- [ ] **Refactoring**
- [ ] Tool factory
- [ ] Eliminar duplicação
- [ ] Melhorar type safety
### Métricas de Sucesso Fase 3
- ✅ Code coverage > 80%
- ✅ 0 linting errors
- ✅ CI passing
- ✅ Duplicação < 5%
---
## 🟢 FASE 4: Funcionalidades (P3)
**Duração:** Ongoing
**Prioridade:** BAIXA
**Bloqueante:** NÃO - Melhorias incrementais
### Tarefas
#### 4.1 Rate Limiting Distribuído
- [ ] Integrar Redis
- [ ] Implementar rate limiting distribuído
- [ ] Adicionar CAPTCHA para operações sensíveis
#### 4.2 Autorização
- [ ] Implementar RBAC
- [ ] Verificar permissões antes de operações
- [ ] Adicionar testes de autorização
#### 4.3 Monitoring
- [ ] Integrar Prometheus
- [ ] Adicionar métricas de performance
- [ ] Dashboard Grafana
#### 4.4 Documentação
- [ ] API documentation (OpenAPI)
- [ ] Guia de deployment
- [ ] Troubleshooting guide
---
## 📊 Métricas Globais de Sucesso
### Segurança
- ✅ 0 vulnerabilidades críticas
- ✅ 0 vulnerabilidades altas
- ✅ 100% queries parametrizadas
- ✅ 100% operações críticas com transacções
### Performance
- ✅ Queries < 100ms (p95)
- ✅ Throughput > 1000 req/s
- ✅ Pool utilization < 80%
- ✅ 0 N+1 queries
### Qualidade
- ✅ Code coverage > 80%
- ✅ 0 linting errors
- ✅ CI passing
- ✅ Duplicação < 5%
---
## 📅 Timeline
```
Semana 1: SQL Injection
Semana 2: Transacções + Validação + Audit
Semana 3: Performance (N+1, Índices, Pool)
Semana 4: Testes Unitários
Semana 5: CI/CD + Refactoring
Semana 6+: Funcionalidades (ongoing)
```
---
## 🎯 Próximos Passos Imediatos
1. **Aprovar este plano**
2. **Criar branch `security-fixes`**
3. **Iniciar Fase 1.1.1: Auditar queries vulneráveis**
4. **Daily progress tracking em `task.md`**
---
*Plano criado em 2026-01-31 | MCP Outline PostgreSQL v1.2.1 → v2.0.0*