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>
27 KiB
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
# Script para identificar queries vulneráveis
grep -r "pool.query(\`" src/tools/ > vulnerable-queries.txt
Ficheiros Prioritários:
documents.ts(19 tools) - 2 diascollections.ts(14 tools) - 1.5 diasusers.ts(9 tools) - 1 diaadvanced-search.ts(6 tools) - 1 diaanalytics.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
/**
* 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):
const query = `
SELECT * FROM documents
WHERE title ILIKE '%${args.query}%'
AND collectionId = '${args.collection_id}'
`;
const result = await pool.query(query);
Depois (SEGURO):
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
{
"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 toolsdesk-sync.ts- 2 toolsexport-import.ts- 2 toolscollections.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
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):
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):
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
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
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:
/**
* 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
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_collectionsdocuments.ts:530-577- list_draftsanalytics.ts- várias queries
2.1.2 Refactoring com JOINs
Antes (N+1):
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):
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
-- 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:
## Í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
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
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
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
{
"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
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
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
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
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
/**
* 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:
// 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
- Aprovar este plano ✅
- Criar branch
security-fixes - Iniciar Fase 1.1.1: Auditar queries vulneráveis
- Daily progress tracking em
task.md
Plano criado em 2026-01-31 | MCP Outline PostgreSQL v1.2.1 → v2.0.0