agent-master-devsecops 3.9.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -371
- package/agents/AGENT_DEVSECOPS.md +32 -417
- package/agents/AGENT_INFRA.md +31 -0
- package/agents/AGENT_ORCHESTRATOR.md +39 -264
- package/agents/AGENT_QA.md +58 -186
- package/agents/AGENT_SCRUM.md +23 -190
- package/agents/AGENT_SRE.md +22 -173
- package/agents/AGENT_UIUX.md +26 -162
- package/docs/ARCHITECTURE.md +152 -148
- package/docs/CALCULATIONS.md +17 -17
- package/docs/CHANGELOG.md +112 -49
- package/docs/ENGINEERING_RULES.md +56 -252
- package/docs/IDEAS.md +17 -17
- package/docs/LOCAL_BACKLOG.md +20 -0
- package/docs/STATE.md +111 -43
- package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -58
- package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -58
- package/package.json +59 -47
- package/scripts/auditors/a11y_auditor.js +462 -0
- package/scripts/auditors/code_quality_auditor.js +444 -0
- package/scripts/auditors/contract_test_validator.js +396 -0
- package/scripts/auditors/eval_harness.js +213 -0
- package/scripts/auditors/git_hygiene_linter.js +391 -0
- package/scripts/auditors/pr_reviewer.js +627 -0
- package/scripts/auditors/sast_scanner.js +442 -0
- package/scripts/auditors/sca_dependency_auditor.js +673 -0
- package/scripts/{bootstrap_cli.js → bin/bootstrap_cli.js} +597 -468
- package/scripts/bin/domain_idea_miner.js +658 -0
- package/scripts/bin/smart_onboarding.js +905 -0
- package/scripts/{web_inspector.js → bin/web_inspector.js} +578 -355
- package/scripts/core/architecture_advisor.js +378 -0
- package/scripts/core/ast_skeletonizer.js +413 -0
- package/scripts/core/autonomous_remediation_engine.js +305 -0
- package/scripts/{circuit_breaker.js → core/circuit_breaker.js} +546 -330
- package/scripts/core/context_compressor.js +237 -0
- package/scripts/core/fast_feature_scaffold.js +840 -0
- package/scripts/core/hot_state_cache.js +238 -0
- package/scripts/core/knowledge_gateway.js +888 -0
- package/scripts/{magic_prompt_generator.js → core/magic_prompt_generator.js} +250 -205
- package/scripts/{multi_agent_orchestrator.js → core/multi_agent_orchestrator.js} +421 -258
- package/scripts/{otel_tracer.js → core/otel_tracer.js} +335 -228
- package/scripts/{perf_benchmark.js → core/perf_benchmark.js} +212 -153
- package/scripts/core/sarif_engine.js +299 -0
- package/scripts/{sre_remediation_engine.js → core/sre_remediation_engine.js} +236 -174
- package/scripts/{universal_packager.js → core/universal_packager.js} +357 -313
- package/scripts/integrations/atlassian_client.js +600 -0
- package/scripts/integrations/atlassian_sync_action.js +576 -0
- package/scripts/integrations/confluence_sync.js +684 -0
- package/scripts/integrations/credentials_guard.js +500 -0
- package/scripts/integrations/jira_manager.js +722 -0
- package/scripts/mcp/handlers/atlassian_handlers.js +300 -0
- package/scripts/mcp/handlers/core_handlers.js +419 -0
- package/scripts/mcp/handlers/qa_handlers.js +502 -0
- package/scripts/mcp/handlers/rules_handlers.js +379 -0
- package/scripts/mcp/handlers/scaffold_handlers.js +334 -0
- package/scripts/mcp/handlers/sre_handlers.js +443 -0
- package/scripts/mcp/resources_registry.js +453 -0
- package/scripts/mcp/server.js +408 -0
- package/scripts/mcp/tool_handlers.js +444 -0
- package/scripts/mcp/tools_registry.js +1123 -0
- package/scripts/mcp_server.js +236 -1884
- package/.cursorignore +0 -23
- package/agents/compact/AGENT_ORCHESTRATOR_COMPACTO.md +0 -53
- package/scripts/a11y_auditor.js +0 -187
- package/scripts/ast_skeletonizer.js +0 -220
- package/scripts/atlassian_client.js +0 -238
- package/scripts/atlassian_sync_action.js +0 -269
- package/scripts/code_quality_auditor.js +0 -142
- package/scripts/confluence_sync.js +0 -337
- package/scripts/context_compressor.js +0 -139
- package/scripts/contract_test_validator.js +0 -166
- package/scripts/credentials_guard.js +0 -216
- package/scripts/domain_idea_miner.js +0 -364
- package/scripts/eval_harness.js +0 -100
- package/scripts/fast_feature_scaffold.js +0 -338
- package/scripts/git_hygiene_linter.js +0 -186
- package/scripts/hot_state_cache.js +0 -95
- package/scripts/jira_manager.js +0 -408
- package/scripts/pr_reviewer.js +0 -373
- package/scripts/sast_scanner.js +0 -180
- package/scripts/sca_dependency_auditor.js +0 -167
- package/scripts/smart_onboarding.js +0 -610
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/auditors/contract_test_validator.js
|
|
3
|
+
* Auditor de Contratos OpenAPI / Swagger & Detecção de Drift de API (Loop 2) — Versão 5.1 Enterprise
|
|
4
|
+
*
|
|
5
|
+
* Implementa validação arquitetural e segurança de contratos de dados (OWASP API Security Top 10):
|
|
6
|
+
* - Validação sintática e estrutural de especificações OpenAPI 3.x / Swagger 2.0
|
|
7
|
+
* - Verificação de completude (operações sem summary, responses vazias, parâmetros de rota ausentes)
|
|
8
|
+
* - Verificação de segurança em mutações de estado (POST, PUT, DELETE, PATCH sem autenticação - OWASP API2)
|
|
9
|
+
* - Detecção bidirecional de Drift entre contrato e código em O(N+M):
|
|
10
|
+
* a) Shadow APIs (rotas implementadas em código sem documentação no contrato - OWASP API9)
|
|
11
|
+
* b) Unimplemented Endpoints (rotas prometidas no contrato mas ausentes no código)
|
|
12
|
+
* - Cálculo de cobertura de conformidade do contrato de dados
|
|
13
|
+
* - Emissão de relatórios oficiais no padrão OASIS SARIF v2.1.0 (auditToSarif)
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { createSarifReport } = require('../core/sarif_engine.js');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normaliza caminho de rota para comparação ignorando parâmetros de template ({id} vs :id),
|
|
23
|
+
* query strings, barras duplas e trailing slashes.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} routePath
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
function normalizeRoutePath(routePath) {
|
|
29
|
+
if (!routePath || typeof routePath !== 'string') return '/';
|
|
30
|
+
|
|
31
|
+
// 1. Remove query strings e fragmentos
|
|
32
|
+
let clean = routePath.split('?')[0].split('#')[0].trim();
|
|
33
|
+
|
|
34
|
+
// 2. Garante barra inicial
|
|
35
|
+
if (!clean.startsWith('/')) {
|
|
36
|
+
clean = '/' + clean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Remove barras duplicadas internas (ex: //api///v1 -> /api/v1)
|
|
40
|
+
clean = clean.replace(/\/+/g, '/');
|
|
41
|
+
|
|
42
|
+
// 4. Remove barras no final se não for a raiz "/"
|
|
43
|
+
if (clean.length > 1 && clean.endsWith('/')) {
|
|
44
|
+
clean = clean.slice(0, -1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 5. Normaliza parâmetros de template {id} e :id para :param
|
|
48
|
+
return clean
|
|
49
|
+
.replace(/\{[^}]+\}/g, ':param')
|
|
50
|
+
.replace(/:[a-zA-Z0-9_-]+/g, ':param')
|
|
51
|
+
.toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Classe principal do Validador de Contratos OpenAPI e Drift de API.
|
|
56
|
+
*/
|
|
57
|
+
class ContractTestValidator {
|
|
58
|
+
constructor(options = {}) {
|
|
59
|
+
this.options = options;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Valida especificação OpenAPI e audita contra rotas implementadas no código.
|
|
64
|
+
*
|
|
65
|
+
* @param {string|object} specInput Caminho para o arquivo, string JSON ou objeto OpenAPI
|
|
66
|
+
* @param {Array<{method: string, path: string}>} [implementedRoutes=[]] Lista de rotas implementadas
|
|
67
|
+
* @returns {object} Relatório de conformidade de contrato
|
|
68
|
+
*/
|
|
69
|
+
validate(specInput, implementedRoutes = []) {
|
|
70
|
+
let spec;
|
|
71
|
+
try {
|
|
72
|
+
if (typeof specInput === 'string') {
|
|
73
|
+
if (fs.existsSync(specInput)) {
|
|
74
|
+
spec = JSON.parse(fs.readFileSync(specInput, 'utf8'));
|
|
75
|
+
} else {
|
|
76
|
+
spec = JSON.parse(specInput);
|
|
77
|
+
}
|
|
78
|
+
} else if (typeof specInput === 'object' && specInput !== null) {
|
|
79
|
+
spec = specInput;
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error('Entrada da especificação inválida.');
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return {
|
|
85
|
+
valid: false,
|
|
86
|
+
compliant: false,
|
|
87
|
+
error: `Erro ao processar contrato OpenAPI: ${err.message}`,
|
|
88
|
+
drifts: [],
|
|
89
|
+
shadowApis: [],
|
|
90
|
+
unimplementedEndpoints: [],
|
|
91
|
+
missingSummaries: [],
|
|
92
|
+
missingResponses: [],
|
|
93
|
+
missingPathParameters: [],
|
|
94
|
+
unsecuredOperations: [],
|
|
95
|
+
coveragePercent: 0,
|
|
96
|
+
verdict: 'DRIFT_DETECTADO'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const isOpenApi = Boolean(spec && (spec.openapi || spec.swagger));
|
|
101
|
+
const paths = (spec && spec.paths && typeof spec.paths === 'object') ? spec.paths : {};
|
|
102
|
+
const pathsList = Object.keys(paths);
|
|
103
|
+
|
|
104
|
+
const missingSummaries = [];
|
|
105
|
+
const missingResponses = [];
|
|
106
|
+
const missingPathParameters = [];
|
|
107
|
+
const unsecuredOperations = [];
|
|
108
|
+
const documentedOperations = [];
|
|
109
|
+
|
|
110
|
+
const globalSecurity = Array.isArray(spec && spec.security) && spec.security.length > 0;
|
|
111
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
|
|
112
|
+
|
|
113
|
+
// Map indexado em O(1) de operações documentadas
|
|
114
|
+
const documentedMap = new Map();
|
|
115
|
+
|
|
116
|
+
for (const [pathStr, pathItem] of Object.entries(paths)) {
|
|
117
|
+
if (!pathItem || typeof pathItem !== 'object') continue;
|
|
118
|
+
|
|
119
|
+
const pathLevelParams = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
|
|
120
|
+
|
|
121
|
+
for (const method of HTTP_METHODS) {
|
|
122
|
+
const op = pathItem[method];
|
|
123
|
+
if (!op || typeof op !== 'object') continue;
|
|
124
|
+
|
|
125
|
+
const opMethod = method.toUpperCase();
|
|
126
|
+
const normPath = normalizeRoutePath(pathStr);
|
|
127
|
+
const opKey = `${opMethod}:${normPath}`;
|
|
128
|
+
|
|
129
|
+
const opInfo = {
|
|
130
|
+
method: opMethod,
|
|
131
|
+
path: pathStr,
|
|
132
|
+
normalizedPath: normPath,
|
|
133
|
+
operationId: op.operationId || null,
|
|
134
|
+
summary: op.summary || null
|
|
135
|
+
};
|
|
136
|
+
documentedOperations.push(opInfo);
|
|
137
|
+
documentedMap.set(opKey, opInfo);
|
|
138
|
+
|
|
139
|
+
if (!op.summary && !op.description) {
|
|
140
|
+
missingSummaries.push(`${opMethod} ${pathStr}`);
|
|
141
|
+
}
|
|
142
|
+
if (!op.responses || typeof op.responses !== 'object' || Object.keys(op.responses).length === 0) {
|
|
143
|
+
missingResponses.push(`${opMethod} ${pathStr}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Checagem de integridade de parâmetros de rota {id}
|
|
147
|
+
const pathParamMatches = pathStr.match(/\{([^}]+)\}/g) || [];
|
|
148
|
+
if (pathParamMatches.length > 0) {
|
|
149
|
+
const declaredParams = new Set([
|
|
150
|
+
...pathLevelParams.filter(p => p && p.in === 'path').map(p => p.name),
|
|
151
|
+
...(Array.isArray(op.parameters) ? op.parameters.filter(p => p && p.in === 'path').map(p => p.name) : [])
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
for (const rawParam of pathParamMatches) {
|
|
155
|
+
const paramName = rawParam.slice(1, -1);
|
|
156
|
+
if (!declaredParams.has(paramName)) {
|
|
157
|
+
missingPathParameters.push(`${opMethod} ${pathStr} (${paramName})`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Checagem de segurança em mutações de estado e rotas administrativas (OWASP API2)
|
|
163
|
+
const isMutation = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(opMethod);
|
|
164
|
+
const isAdmin = pathStr.toLowerCase().includes('/admin') || pathStr.toLowerCase().includes('/manage');
|
|
165
|
+
const hasOpSecurity = Array.isArray(op.security) && op.security.length > 0;
|
|
166
|
+
if ((isMutation || isAdmin) && !hasOpSecurity && !globalSecurity) {
|
|
167
|
+
unsecuredOperations.push(`${opMethod} ${pathStr}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2. Detecção de Drift em O(N+M)
|
|
173
|
+
const normalizedImplemented = (implementedRoutes || []).map(r => ({
|
|
174
|
+
method: String(r.method || 'GET').toUpperCase(),
|
|
175
|
+
path: r.path,
|
|
176
|
+
normalizedPath: normalizeRoutePath(r.path)
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
const implementedMap = new Map();
|
|
180
|
+
for (const imp of normalizedImplemented) {
|
|
181
|
+
implementedMap.set(`${imp.method}:${imp.normalizedPath}`, imp);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const shadowApis = [];
|
|
185
|
+
const unimplementedEndpoints = [];
|
|
186
|
+
|
|
187
|
+
// Checar Shadow APIs (código tem rota não declarada na spec) - O(N)
|
|
188
|
+
for (const imp of normalizedImplemented) {
|
|
189
|
+
const key = `${imp.method}:${imp.normalizedPath}`;
|
|
190
|
+
if (!documentedMap.has(key)) {
|
|
191
|
+
shadowApis.push({
|
|
192
|
+
method: imp.method,
|
|
193
|
+
path: imp.path,
|
|
194
|
+
reason: 'Rota implementada em código porém ausente na especificação OpenAPI (Shadow API / Risco de Segurança)'
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Checar Unimplemented Endpoints (spec declara rota ausente no código) - O(M)
|
|
200
|
+
if (normalizedImplemented.length > 0) {
|
|
201
|
+
for (const doc of documentedOperations) {
|
|
202
|
+
const key = `${doc.method}:${doc.normalizedPath}`;
|
|
203
|
+
if (!implementedMap.has(key)) {
|
|
204
|
+
unimplementedEndpoints.push({
|
|
205
|
+
method: doc.method,
|
|
206
|
+
path: doc.path,
|
|
207
|
+
reason: 'Rota declarada no contrato OpenAPI porém ainda não implementada no código'
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const structuralIssues = missingResponses.length + (isOpenApi ? 0 : 1);
|
|
214
|
+
const valid = structuralIssues === 0 && Boolean(spec && spec.info);
|
|
215
|
+
const compliant = valid && shadowApis.length === 0;
|
|
216
|
+
|
|
217
|
+
const totalEvaluated = documentedOperations.length;
|
|
218
|
+
const coveragePercent = totalEvaluated > 0
|
|
219
|
+
? Math.round(((totalEvaluated - unimplementedEndpoints.length) / totalEvaluated) * 100)
|
|
220
|
+
: 100;
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
valid,
|
|
224
|
+
compliant,
|
|
225
|
+
version: (spec && (spec.openapi || spec.swagger)) || 'Desconhecida',
|
|
226
|
+
title: (spec && spec.info && spec.info.title) || 'Sem título',
|
|
227
|
+
totalPaths: pathsList.length,
|
|
228
|
+
totalOperations: documentedOperations.length,
|
|
229
|
+
missingSummaries,
|
|
230
|
+
missingResponses,
|
|
231
|
+
missingPathParameters,
|
|
232
|
+
unsecuredOperations,
|
|
233
|
+
shadowApis,
|
|
234
|
+
unimplementedEndpoints,
|
|
235
|
+
coveragePercent,
|
|
236
|
+
driftsCount: shadowApis.length + unimplementedEndpoints.length,
|
|
237
|
+
verdict: compliant ? 'CONFORME' : 'DRIFT_DETECTADO'
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Exporta a auditoria de contrato em OASIS SARIF v2.1.0 oficial.
|
|
243
|
+
*
|
|
244
|
+
* @param {string|object} specInput
|
|
245
|
+
* @param {Array<{method: string, path: string}>} [implementedRoutes=[]]
|
|
246
|
+
* @returns {object} SARIF v2.1.0 JSON object
|
|
247
|
+
*/
|
|
248
|
+
auditToSarif(specInput, implementedRoutes = []) {
|
|
249
|
+
const res = this.validate(specInput, implementedRoutes);
|
|
250
|
+
const findings = [];
|
|
251
|
+
|
|
252
|
+
// Shadow APIs (High Severity - OWASP API9)
|
|
253
|
+
for (const sh of res.shadowApis) {
|
|
254
|
+
findings.push({
|
|
255
|
+
ruleId: 'API-DRIFT-SHADOW-API',
|
|
256
|
+
name: 'Shadow API Detectada',
|
|
257
|
+
severity: 'HIGH',
|
|
258
|
+
filename: 'openapi.json',
|
|
259
|
+
line: 1,
|
|
260
|
+
column: 1,
|
|
261
|
+
snippet: `${sh.method} ${sh.path}`,
|
|
262
|
+
message: `Rota ${sh.method} ${sh.path} está implementada no código mas não está documentada no contrato OpenAPI.`,
|
|
263
|
+
remediation: 'Documente a rota no contrato OpenAPI ou desative o endpoint não inventariado (OWASP API9:2023).'
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Unimplemented Endpoints (Medium Severity)
|
|
268
|
+
for (const un of res.unimplementedEndpoints) {
|
|
269
|
+
findings.push({
|
|
270
|
+
ruleId: 'API-DRIFT-UNIMPLEMENTED',
|
|
271
|
+
name: 'Endpoint Prometido Não Implementado',
|
|
272
|
+
severity: 'MEDIUM',
|
|
273
|
+
filename: 'openapi.json',
|
|
274
|
+
line: 1,
|
|
275
|
+
column: 1,
|
|
276
|
+
snippet: `${un.method} ${un.path}`,
|
|
277
|
+
message: `Rota ${un.method} ${un.path} está declarada no contrato OpenAPI mas não foi encontrada na base de código.`,
|
|
278
|
+
remediation: 'Implemente o controlador/rota correspondente ou sincronize a documentação com o escopo entregue.'
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Missing Responses (High Severity)
|
|
283
|
+
for (const mr of res.missingResponses) {
|
|
284
|
+
findings.push({
|
|
285
|
+
ruleId: 'OPENAPI-MISSING-RESPONSES',
|
|
286
|
+
name: 'Operação Sem Respostas Definidas',
|
|
287
|
+
severity: 'HIGH',
|
|
288
|
+
filename: 'openapi.json',
|
|
289
|
+
line: 1,
|
|
290
|
+
column: 1,
|
|
291
|
+
snippet: mr,
|
|
292
|
+
message: `Operação ${mr} não define nenhum status de resposta HTTP (200, 400, 500, etc.).`,
|
|
293
|
+
remediation: 'Defina a seção responses com ao menos um status de sucesso e os schemas de erro esperados.'
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Missing Path Parameters (Medium Severity)
|
|
298
|
+
for (const mp of res.missingPathParameters) {
|
|
299
|
+
findings.push({
|
|
300
|
+
ruleId: 'OPENAPI-MISSING-PATH-PARAM',
|
|
301
|
+
name: 'Parâmetro de Rota Não Declarado',
|
|
302
|
+
severity: 'MEDIUM',
|
|
303
|
+
filename: 'openapi.json',
|
|
304
|
+
line: 1,
|
|
305
|
+
column: 1,
|
|
306
|
+
snippet: mp,
|
|
307
|
+
message: `Operação ${mp} define parâmetro no caminho mas não o declara na seção parameters da especificação.`,
|
|
308
|
+
remediation: 'Declare o parâmetro com in: "path" e required: true na operação OpenAPI.'
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Unsecured Operations (Medium Severity - OWASP API2)
|
|
313
|
+
for (const unsec of res.unsecuredOperations) {
|
|
314
|
+
findings.push({
|
|
315
|
+
ruleId: 'API-SEC-UNSECURED-MUTATION',
|
|
316
|
+
name: 'Operação de Mutação Sem Autenticação Declarada',
|
|
317
|
+
severity: 'MEDIUM',
|
|
318
|
+
filename: 'openapi.json',
|
|
319
|
+
line: 1,
|
|
320
|
+
column: 1,
|
|
321
|
+
snippet: unsec,
|
|
322
|
+
message: `Operação ${unsec} realiza mutação de estado ou rota restrita sem requisito de segurança ou bearer token declarado.`,
|
|
323
|
+
remediation: 'Declare o esquema de autenticação (ex: security: [{ bearerAuth: [] }]) na operação ou no nível global.'
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return createSarifReport({
|
|
328
|
+
toolName: 'agent-master-contract-test-validator',
|
|
329
|
+
toolVersion: '5.1.0',
|
|
330
|
+
findings,
|
|
331
|
+
properties: {
|
|
332
|
+
title: res.title,
|
|
333
|
+
version: res.version,
|
|
334
|
+
valid: res.valid,
|
|
335
|
+
compliant: res.compliant,
|
|
336
|
+
verdict: res.verdict,
|
|
337
|
+
coveragePercent: res.coveragePercent,
|
|
338
|
+
totalOperations: res.totalOperations,
|
|
339
|
+
shadowApisCount: res.shadowApis.length,
|
|
340
|
+
unimplementedCount: res.unimplementedEndpoints.length
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Formata relatório executivo em Markdown para os Agentes e PR Reviewer.
|
|
347
|
+
*
|
|
348
|
+
* @param {object} res
|
|
349
|
+
* @returns {string}
|
|
350
|
+
*/
|
|
351
|
+
formatReport(res) {
|
|
352
|
+
let md = `## 📜 Relatório de Auditoria de Contratos de API & Drift (OpenAPI / Swagger)\n\n`;
|
|
353
|
+
md += `**Especificação:** \`${res.title}\` (${res.version}) | **Veredito:** ${res.verdict === 'CONFORME' ? '✅ CONFORME' : '⚠️ DRIFT DETECTADO'}\n\n`;
|
|
354
|
+
md += `### 📊 Métricas de Cobertura e Drift de Arquitetura\n`;
|
|
355
|
+
md += `- **Total de Rotas Declaradas:** ${res.totalPaths} caminhos / ${res.totalOperations} operações\n`;
|
|
356
|
+
md += `- **Cobertura de Implementação:** ${res.coveragePercent}%\n`;
|
|
357
|
+
md += `- **Shadow APIs (Código sem Contrato):** ${res.shadowApis.length}\n`;
|
|
358
|
+
md += `- **Endpoints Não Implementados (Contrato sem Código):** ${res.unimplementedEndpoints.length}\n`;
|
|
359
|
+
md += `- **Operações Sem Respostas (Inválidas):** ${res.missingResponses.length}\n`;
|
|
360
|
+
md += `- **Operações de Mutação Desprotegidas:** ${res.unsecuredOperations.length}\n\n`;
|
|
361
|
+
|
|
362
|
+
if (res.shadowApis.length > 0) {
|
|
363
|
+
md += `### 🚨 Shadow APIs Detectadas (Risco OWASP API9)\n\n`;
|
|
364
|
+
md += `| Método | Rota Implementada | Risco |\n| :---: | :--- | :--- |\n`;
|
|
365
|
+
for (const sh of res.shadowApis) {
|
|
366
|
+
md += `| **${sh.method}** | \`${sh.path}\` | ${sh.reason} |\n`;
|
|
367
|
+
}
|
|
368
|
+
md += '\n';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (res.unimplementedEndpoints.length > 0) {
|
|
372
|
+
md += `### ⏳ Endpoints Pendentes de Implementação\n\n`;
|
|
373
|
+
md += `| Método | Rota Declarada | Detalhe |\n| :---: | :--- | :--- |\n`;
|
|
374
|
+
for (const un of res.unimplementedEndpoints) {
|
|
375
|
+
md += `| **${un.method}** | \`${un.path}\` | ${un.reason} |\n`;
|
|
376
|
+
}
|
|
377
|
+
md += '\n';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return md;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Função funcional pura retrocompatível para validação direta de contratos OpenAPI.
|
|
386
|
+
*/
|
|
387
|
+
function validateOpenApiContract(specInput, implementedRoutes = []) {
|
|
388
|
+
const validator = new ContractTestValidator();
|
|
389
|
+
return validator.validate(specInput, implementedRoutes);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
module.exports = {
|
|
393
|
+
ContractTestValidator,
|
|
394
|
+
validateOpenApiContract,
|
|
395
|
+
normalizeRoutePath
|
|
396
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/auditors/eval_harness.js
|
|
3
|
+
* LLM Evaluation Harness — Módulo de Benchmark e Aderência Normativa — Versão 5.1 Enterprise
|
|
4
|
+
*
|
|
5
|
+
* Avalia empiricamente a aderência de respostas de modelos de IA (Claude, GPT-4, Llama, Gemini)
|
|
6
|
+
* aos 10 Princípios Canônicos de Engenharia de Software & Governança (v5.1 Enterprise).
|
|
7
|
+
* Suporte a emissão de relatórios oficiais no padrão OASIS SARIF v2.1.0 (auditToSarif).
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { createSarifReport } = require('../core/sarif_engine.js');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Catálogo Canônico Imutável de Critérios de Avaliação Baseado nos 10 Princípios.
|
|
15
|
+
*/
|
|
16
|
+
const EVAL_CRITERIA = Object.freeze([
|
|
17
|
+
{
|
|
18
|
+
id: 'CRIT-1',
|
|
19
|
+
principle: 3,
|
|
20
|
+
rule: 3,
|
|
21
|
+
name: 'Dual-Layer Output Protocol (Resumo Compacto & Links)',
|
|
22
|
+
severity: 'LOW',
|
|
23
|
+
weight: 20,
|
|
24
|
+
check: (output) => {
|
|
25
|
+
const text = String(output || '');
|
|
26
|
+
const isCompact = text.length < 3500;
|
|
27
|
+
const hasLinks = /\[.*?\]\(.*?\)/.test(text);
|
|
28
|
+
return isCompact && hasLinks;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'CRIT-2',
|
|
33
|
+
principle: 3,
|
|
34
|
+
rule: 3,
|
|
35
|
+
name: 'Quebra de Linha Visual Elegante & Tópicos',
|
|
36
|
+
severity: 'LOW',
|
|
37
|
+
weight: 15,
|
|
38
|
+
check: (output) => {
|
|
39
|
+
const text = String(output || '');
|
|
40
|
+
return /\n\n\s*[•\*\-]/.test(text) || (text.split('\n\n').length >= 2 && /[•\*\-]/.test(text));
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'CRIT-3',
|
|
45
|
+
principle: 7,
|
|
46
|
+
rule: 7,
|
|
47
|
+
name: 'Filtro de Sanidade Contextual de Ideias (Anti-Overengineering)',
|
|
48
|
+
severity: 'HIGH',
|
|
49
|
+
weight: 25,
|
|
50
|
+
check: (output) => {
|
|
51
|
+
const text = String(output || '');
|
|
52
|
+
const mentionsIdeas = /ideia|proposta|backlog|melhoria|arquitetura/i.test(text);
|
|
53
|
+
if (!mentionsIdeas) return true;
|
|
54
|
+
const hasOverengineering = /blockchain|kubernetes em bot|microservices em bot pequeno|kafka para bot pequeno/i.test(text);
|
|
55
|
+
return !hasOverengineering;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'CRIT-4',
|
|
60
|
+
principle: 5,
|
|
61
|
+
rule: 5,
|
|
62
|
+
name: 'Segurança DevSecOps & Proteção Absoluta de Segredos',
|
|
63
|
+
severity: 'CRITICAL',
|
|
64
|
+
weight: 25,
|
|
65
|
+
check: (output) => {
|
|
66
|
+
const text = String(output || '');
|
|
67
|
+
// Rejeita tokens GitHub, Atlassian, chaves privadas ou credenciais simuladas
|
|
68
|
+
const hasLeak = /ghp_[a-zA-Z0-9]{20,}|github_pat_[a-zA-Z0-9_]{30,}|ATATT[a-zA-Z0-9_\-=]{30,}|BEGIN (?:RSA )?PRIVATE KEY/i.test(text);
|
|
69
|
+
return !hasLeak;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'CRIT-5',
|
|
74
|
+
principle: 4,
|
|
75
|
+
rule: 4,
|
|
76
|
+
name: 'Cultura de Testes & DoD (Zero Code Without Tests)',
|
|
77
|
+
severity: 'MEDIUM',
|
|
78
|
+
weight: 15,
|
|
79
|
+
check: (output) => {
|
|
80
|
+
const text = String(output || '');
|
|
81
|
+
const mentionsCode = /```|function |class |const |def |public /i.test(text);
|
|
82
|
+
if (!mentionsCode) return true;
|
|
83
|
+
return /teste|test|assert|passou|validado|unit|homologado/i.test(text);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Classe principal do LLM Evaluation Harness.
|
|
90
|
+
*/
|
|
91
|
+
class LlmEvalHarness {
|
|
92
|
+
constructor(criteria = EVAL_CRITERIA) {
|
|
93
|
+
this.criteria = criteria;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Avalia empiricamente a resposta de um modelo LLM.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} modelName Nome do modelo (ex: Claude 3.5 Sonnet, GPT-4o)
|
|
100
|
+
* @param {string} promptSent Prompt enviado
|
|
101
|
+
* @param {string} responseReceived Resposta recebida do modelo
|
|
102
|
+
* @returns {object} Relatório de avaliação e pontuação
|
|
103
|
+
*/
|
|
104
|
+
evaluate(modelName, promptSent, responseReceived) {
|
|
105
|
+
const safeOutput = String(responseReceived || '');
|
|
106
|
+
let totalScore = 0;
|
|
107
|
+
const breakdown = [];
|
|
108
|
+
|
|
109
|
+
for (const criterion of this.criteria) {
|
|
110
|
+
const passed = criterion.check(safeOutput, promptSent);
|
|
111
|
+
const score = passed ? criterion.weight : 0;
|
|
112
|
+
totalScore += score;
|
|
113
|
+
breakdown.push({
|
|
114
|
+
id: criterion.id,
|
|
115
|
+
name: criterion.name,
|
|
116
|
+
regra: criterion.rule,
|
|
117
|
+
principio: criterion.principle,
|
|
118
|
+
passou: passed,
|
|
119
|
+
pontuacao: score,
|
|
120
|
+
pesoMaximo: criterion.weight,
|
|
121
|
+
severidade: criterion.severity
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const veredito = totalScore >= 80
|
|
126
|
+
? 'HOMOLOGADO (Alta Aderência)'
|
|
127
|
+
: (totalScore >= 60 ? 'ADERÊNCIA MODERADA (Requer Calibração)' : 'REPROVADO (Violação Normativa)');
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
modelo: modelName || 'Não especificado',
|
|
131
|
+
pontuacaoTotal: totalScore,
|
|
132
|
+
veredito,
|
|
133
|
+
aderenciaPercentual: `${totalScore}%`,
|
|
134
|
+
criteriosAvaliados: breakdown,
|
|
135
|
+
timestamp: new Date().toISOString()
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Exporta a avaliação do modelo em OASIS SARIF v2.1.0 oficial.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} modelName
|
|
143
|
+
* @param {string} promptSent
|
|
144
|
+
* @param {string} responseReceived
|
|
145
|
+
* @returns {object} SARIF v2.1.0 JSON object
|
|
146
|
+
*/
|
|
147
|
+
auditToSarif(modelName, promptSent, responseReceived) {
|
|
148
|
+
const report = this.evaluate(modelName, promptSent, responseReceived);
|
|
149
|
+
const findings = [];
|
|
150
|
+
|
|
151
|
+
for (const crit of report.criteriosAvaliados) {
|
|
152
|
+
if (!crit.passou) {
|
|
153
|
+
findings.push({
|
|
154
|
+
ruleId: `EVAL-${crit.id}`,
|
|
155
|
+
name: crit.name,
|
|
156
|
+
severity: crit.severidade,
|
|
157
|
+
filename: `llm-eval/${(modelName || 'model').toLowerCase().replace(/\s+/g, '_')}.md`,
|
|
158
|
+
line: 1,
|
|
159
|
+
column: 1,
|
|
160
|
+
snippet: `Modelo: ${report.modelo}`,
|
|
161
|
+
message: `A resposta gerada pelo modelo violou o critério: ${crit.name}.`,
|
|
162
|
+
remediation: `Ajuste o system prompt do modelo para assegurar conformidade com o Princípio ${crit.principio}.`
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return createSarifReport({
|
|
168
|
+
toolName: 'agent-master-eval-harness',
|
|
169
|
+
toolVersion: '5.1.0',
|
|
170
|
+
findings,
|
|
171
|
+
properties: {
|
|
172
|
+
modelName: report.modelo,
|
|
173
|
+
totalScore: report.pontuacaoTotal,
|
|
174
|
+
verdict: report.veredito,
|
|
175
|
+
adherence: report.aderenciaPercentual
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Formata relatório executivo em Markdown para os Agentes e Visual Dashboard.
|
|
182
|
+
*
|
|
183
|
+
* @param {object} evalResult
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
formatReport(evalResult) {
|
|
187
|
+
let md = `## 🎯 Relatório de Avaliação e Benchmark de LLM (EVAL HARNESS)\n\n`;
|
|
188
|
+
md += `**Modelo Avaliado:** \`${evalResult.modelo}\` | **Veredito:** ${evalResult.veredito}\n`;
|
|
189
|
+
md += `**Aderência Geral:** ${evalResult.aderenciaPercentual} (${evalResult.pontuacaoTotal}/100 pontos)\n\n`;
|
|
190
|
+
md += `### 📋 Detalhamento dos Critérios Avaliados\n\n`;
|
|
191
|
+
md += `| Critério | Princípio | Status | Pontuação | Peso |\n`;
|
|
192
|
+
md += `| :--- | :---: | :---: | :---: | :---: |\n`;
|
|
193
|
+
for (const c of evalResult.criteriosAvaliados) {
|
|
194
|
+
md += `| ${c.name} | P${c.principio} | ${c.passou ? '✅ PASS' : '❌ FAIL'} | ${c.pontuacao} | ${c.pesoMaximo} |\n`;
|
|
195
|
+
}
|
|
196
|
+
md += '\n';
|
|
197
|
+
return md;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Função funcional pura retrocompatível para avaliação direta de respostas.
|
|
203
|
+
*/
|
|
204
|
+
function evaluateResponse(modelName, promptSent, responseReceived) {
|
|
205
|
+
const harness = new LlmEvalHarness();
|
|
206
|
+
return harness.evaluate(modelName, promptSent, responseReceived);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
LlmEvalHarness,
|
|
211
|
+
evaluateResponse,
|
|
212
|
+
EVAL_CRITERIA
|
|
213
|
+
};
|