agent-master-devsecops 0.0.0-stage → 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 +204 -2
- package/agents/AGENT_DEVSECOPS.md +32 -0
- package/agents/AGENT_INFRA.md +31 -0
- package/agents/AGENT_ORCHESTRATOR.md +39 -0
- package/agents/AGENT_QA.md +58 -0
- package/agents/AGENT_SCRUM.md +23 -0
- package/agents/AGENT_SRE.md +22 -0
- package/agents/AGENT_UIUX.md +26 -0
- package/docs/ARCHITECTURE.md +152 -0
- package/docs/CALCULATIONS.md +17 -0
- package/docs/CHANGELOG.md +112 -0
- package/docs/ENGINEERING_RULES.md +56 -0
- package/docs/IDEAS.md +17 -0
- package/docs/LOCAL_BACKLOG.md +20 -0
- package/docs/STATE.md +111 -0
- package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -0
- package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -0
- package/package.json +57 -4
- 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/bin/bootstrap_cli.js +597 -0
- package/scripts/bin/domain_idea_miner.js +658 -0
- package/scripts/bin/smart_onboarding.js +905 -0
- package/scripts/bin/web_inspector.js +578 -0
- 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/core/circuit_breaker.js +546 -0
- 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/core/magic_prompt_generator.js +250 -0
- package/scripts/core/multi_agent_orchestrator.js +421 -0
- package/scripts/core/otel_tracer.js +335 -0
- package/scripts/core/perf_benchmark.js +212 -0
- package/scripts/core/sarif_engine.js +299 -0
- package/scripts/core/sre_remediation_engine.js +236 -0
- package/scripts/core/universal_packager.js +357 -0
- 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 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/integrations/credentials_guard.js
|
|
4
|
+
*
|
|
5
|
+
* Guardião Canônico Unificado de Credenciais, Healthcheck e Imutabilidade de Requisitos (Regras 15 e 17).
|
|
6
|
+
* Consolida o Healthcheck interativo de credenciais Atlassian/GitHub e a validação inviolável da Regra 15.
|
|
7
|
+
* Garante obtenção ativa, pré-validação compulsória, mascaramento de segredos e zero falhas silenciosas em todos os vetores.
|
|
8
|
+
*
|
|
9
|
+
* Versão: 5.1.0 Enterprise
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
getAtlassianCredentials,
|
|
19
|
+
checkGithubAuth,
|
|
20
|
+
getGitRepositoryUrl,
|
|
21
|
+
getGitMetadata,
|
|
22
|
+
AtlassianClient,
|
|
23
|
+
auditToSarif: auditAtlassianToSarifBase
|
|
24
|
+
} = require('./atlassian_client.js');
|
|
25
|
+
|
|
26
|
+
const { createSarifReport } = require('../core/sarif_engine.js');
|
|
27
|
+
|
|
28
|
+
class InviolableRuleError extends Error {
|
|
29
|
+
constructor(message, missingRequirements = [], remediationSteps = []) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'InviolableRuleError';
|
|
32
|
+
this.rule = 'Regra 15 - Imutabilidade de Requisitos & Validação Compulsória de Segredos';
|
|
33
|
+
this.missingRequirements = missingRequirements;
|
|
34
|
+
this.remediationSteps = remediationSteps;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Utilitário puro de mascaramento defensivo de segredos (evita CWE-798 / CWE-200 em logs e relatórios).
|
|
40
|
+
* @param {string} secret
|
|
41
|
+
* @param {number} [visibleChars=4]
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function maskSecret(secret, visibleChars = 4) {
|
|
45
|
+
if (!secret || typeof secret !== 'string') return '';
|
|
46
|
+
if (secret.length <= visibleChars) return '****';
|
|
47
|
+
const visible = secret.slice(-visibleChars);
|
|
48
|
+
return `${'*'.repeat(Math.min(12, secret.length - visibleChars))}${visible}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Helpers funcionais puros para merge seguro de .env
|
|
53
|
+
*/
|
|
54
|
+
function parseEnvLines(content) {
|
|
55
|
+
if (!content || typeof content !== 'string') return [];
|
|
56
|
+
return content.split(/\r?\n/);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mergeEnvUpdates(existingContent, updates = {}) {
|
|
60
|
+
const lines = parseEnvLines(existingContent);
|
|
61
|
+
const updatedKeys = new Set();
|
|
62
|
+
const validUpdates = Object.entries(updates).filter(([_, val]) => val !== undefined && val !== null && val !== '');
|
|
63
|
+
|
|
64
|
+
const newLines = lines.map(line => {
|
|
65
|
+
const trimmed = line.trim();
|
|
66
|
+
if (!trimmed || trimmed.startsWith('#')) return line;
|
|
67
|
+
for (const [key, val] of validUpdates) {
|
|
68
|
+
if (trimmed.startsWith(`${key}=`)) {
|
|
69
|
+
updatedKeys.add(key);
|
|
70
|
+
return `${key}=${val}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return line;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
for (const [key, val] of validUpdates) {
|
|
77
|
+
if (!updatedKeys.has(key)) {
|
|
78
|
+
newLines.push(`${key}=${val}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return newLines.filter(line => line !== undefined && line !== null).join('\n').trim() + '\n';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ensureGitignoreProtected(existingContent, entries = ['.env', '.env.local']) {
|
|
86
|
+
const content = typeof existingContent === 'string' ? existingContent : '';
|
|
87
|
+
const lines = content.split(/\r?\n/).map(l => l.trim());
|
|
88
|
+
const missingEntries = entries.filter(entry => !lines.includes(entry));
|
|
89
|
+
|
|
90
|
+
if (missingEntries.length === 0) {
|
|
91
|
+
return { modified: false, content };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let updatedContent = content.trimEnd();
|
|
95
|
+
if (updatedContent.length > 0) updatedContent += '\n';
|
|
96
|
+
updatedContent += '\n# Segurança - Credenciais e Segredos (Regra 15)\n';
|
|
97
|
+
updatedContent += missingEntries.join('\n') + '\n';
|
|
98
|
+
|
|
99
|
+
return { modified: true, content: updatedContent };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Validação rigorosa da Regra 15 com suporte a Fail-Fast.
|
|
104
|
+
*/
|
|
105
|
+
function validateRule15({ failFast = true, requireGitHub = true, requireAtlassian = true } = {}) {
|
|
106
|
+
const missing = [];
|
|
107
|
+
const remediation = [];
|
|
108
|
+
|
|
109
|
+
const atlassian = getAtlassianCredentials();
|
|
110
|
+
if (requireAtlassian && !atlassian.isConfigured) {
|
|
111
|
+
if (!atlassian.token) missing.push('ATLASSIAN_API_TOKEN');
|
|
112
|
+
if (!atlassian.email) missing.push('ATLASSIAN_EMAIL');
|
|
113
|
+
if (!atlassian.domain) missing.push('ATLASSIAN_DOMAIN');
|
|
114
|
+
|
|
115
|
+
remediation.push(
|
|
116
|
+
'Configurar variáveis de ambiente ou secrets no GitHub Actions: gh secret set ATLASSIAN_API_TOKEN / ATLASSIAN_EMAIL / ATLASSIAN_DOMAIN'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const github = checkGithubAuth();
|
|
121
|
+
if (requireGitHub && !github.isAuthenticated) {
|
|
122
|
+
missing.push('GITHUB_AUTH (gh CLI ou GITHUB_TOKEN)');
|
|
123
|
+
remediation.push('Autenticar GitHub CLI via "gh auth login" ou exportar GITHUB_TOKEN');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const isValid = missing.length === 0;
|
|
127
|
+
|
|
128
|
+
if (!isValid && failFast) {
|
|
129
|
+
const msg = `[REGRA 15 INVIOLÁVEL: PRÉ-REQUISITOS NÃO ATENDIDOS]\n` +
|
|
130
|
+
`Faltam credenciais/ferramentas obrigatórias: ${missing.join(', ')}\n` +
|
|
131
|
+
`Remediação: ${remediation.join(' | ')}`;
|
|
132
|
+
throw new InviolableRuleError(msg, missing, remediation);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
valid: isValid,
|
|
137
|
+
missing,
|
|
138
|
+
remediation,
|
|
139
|
+
atlassian,
|
|
140
|
+
github
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Verifica credenciais Atlassian a partir do diretório alvo.
|
|
146
|
+
*/
|
|
147
|
+
function checkAtlassianAuth(targetDir = process.cwd()) {
|
|
148
|
+
const creds = getAtlassianCredentials(targetDir);
|
|
149
|
+
const missingFields = [];
|
|
150
|
+
if (!creds.token) missingFields.push('token');
|
|
151
|
+
if (!creds.email) missingFields.push('email');
|
|
152
|
+
if (!creds.domain) missingFields.push('domain');
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
configured: creds.isConfigured,
|
|
156
|
+
email: creds.email,
|
|
157
|
+
domain: creds.domain,
|
|
158
|
+
tokenPresent: Boolean(creds.token),
|
|
159
|
+
maskedToken: maskSecret(creds.token),
|
|
160
|
+
source: creds.source,
|
|
161
|
+
missingFields
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Configura credenciais no arquivo .env e garante proteção no .gitignore.
|
|
167
|
+
*/
|
|
168
|
+
function configureCredentials(targetDir = process.cwd(), credentials = {}) {
|
|
169
|
+
const dir = typeof targetDir === 'string' && targetDir ? targetDir : process.cwd();
|
|
170
|
+
const safeCreds = credentials && typeof credentials === 'object' ? credentials : {};
|
|
171
|
+
|
|
172
|
+
const updates = {
|
|
173
|
+
ATLASSIAN_DOMAIN: safeCreds.domain,
|
|
174
|
+
ATLASSIAN_EMAIL: safeCreds.email,
|
|
175
|
+
ATLASSIAN_API_TOKEN: safeCreds.token,
|
|
176
|
+
JIRA_PROJECT_KEY: safeCreds.jiraKey,
|
|
177
|
+
CONFLUENCE_SPACE_KEY: safeCreds.confluenceSpace,
|
|
178
|
+
GITHUB_TOKEN: safeCreds.githubToken
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
if (!fs.existsSync(dir)) {
|
|
183
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const envPath = path.join(dir, '.env');
|
|
187
|
+
const existingEnv = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
|
|
188
|
+
const updatedEnv = mergeEnvUpdates(existingEnv, updates);
|
|
189
|
+
fs.writeFileSync(envPath, updatedEnv, 'utf8');
|
|
190
|
+
|
|
191
|
+
const gitignorePath = path.join(dir, '.gitignore');
|
|
192
|
+
const existingGitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
193
|
+
const { modified, content: updatedGitignore } = ensureGitignoreProtected(existingGitignore);
|
|
194
|
+
if (modified || !fs.existsSync(gitignorePath)) {
|
|
195
|
+
fs.writeFileSync(gitignorePath, updatedGitignore, 'utf8');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
status: 'SUCCESS',
|
|
200
|
+
success: true,
|
|
201
|
+
envPath,
|
|
202
|
+
configuredKeys: Object.keys(updates).filter(k => updates[k])
|
|
203
|
+
};
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return {
|
|
206
|
+
status: 'ERROR',
|
|
207
|
+
success: false,
|
|
208
|
+
error: err.message,
|
|
209
|
+
configuredKeys: []
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Executa o diagnóstico completo de saúde das credenciais Atlassian e GitHub.
|
|
216
|
+
*/
|
|
217
|
+
function runCredentialHealthcheck(targetDir = process.cwd()) {
|
|
218
|
+
const safeDir = typeof targetDir === 'string' && targetDir ? targetDir : process.cwd();
|
|
219
|
+
const gh = checkGithubAuth();
|
|
220
|
+
const atlassian = checkAtlassianAuth(safeDir);
|
|
221
|
+
|
|
222
|
+
const gitignorePath = path.join(safeDir, '.gitignore');
|
|
223
|
+
let envIgnored = false;
|
|
224
|
+
if (fs.existsSync(gitignorePath)) {
|
|
225
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
226
|
+
envIgnored = gitignoreContent.includes('.env');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const missing = [];
|
|
230
|
+
const remediation = [];
|
|
231
|
+
|
|
232
|
+
if (!gh.authenticated) {
|
|
233
|
+
missing.push('GitHub CLI');
|
|
234
|
+
remediation.push({
|
|
235
|
+
tool: 'GitHub CLI',
|
|
236
|
+
command: 'gh auth login',
|
|
237
|
+
instruction: 'Execute "gh auth login" no terminal para autenticar sua conta GitHub no CLI.'
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!atlassian.configured) {
|
|
242
|
+
const missingItems = (atlassian.missingFields || []).join(', ');
|
|
243
|
+
missing.push(`Credenciais Atlassian (${missingItems})`);
|
|
244
|
+
remediation.push({
|
|
245
|
+
tool: 'Atlassian Cloud (Jira & Confluence)',
|
|
246
|
+
instruction: 'Cadastre suas credenciais no arquivo .env ou execute "npx agent-master-init":',
|
|
247
|
+
requiredVariables: {
|
|
248
|
+
ATLASSIAN_DOMAIN: 'seu-subdominio.atlassian.net',
|
|
249
|
+
ATLASSIAN_EMAIL: '<seu_email_corporativo>',
|
|
250
|
+
ATLASSIAN_API_TOKEN: 'https://id.atlassian.com/manage-profile/security/api-tokens'
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!envIgnored && fs.existsSync(path.join(safeDir, '.env'))) {
|
|
256
|
+
remediation.push({
|
|
257
|
+
tool: 'Git Security Guard',
|
|
258
|
+
instruction: 'Adicione ".env" ao seu arquivo .gitignore para evitar o commit acidental de credenciais.'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const allReady = gh.authenticated && atlassian.configured;
|
|
263
|
+
|
|
264
|
+
let promptGuidance = '';
|
|
265
|
+
if (!allReady) {
|
|
266
|
+
promptGuidance = `⚠️ [ALERTA DE CONFIGURAÇÃO DE AMBIENTE - REGRA 15]\n` +
|
|
267
|
+
`Faltam credenciais essenciais para o funcionamento autônomo da suíte:\n` +
|
|
268
|
+
remediation.map(r => `- ${r.tool}: ${r.instruction || r.command}`).join('\n') +
|
|
269
|
+
`\nSolicite as credenciais ao usuário ou instrua a execução de "gh auth login" antes de prosseguir com tarefas que exijam Jira/Confluence/Git.`;
|
|
270
|
+
} else {
|
|
271
|
+
promptGuidance = '✅ Todas as integrações (GitHub, Jira e Confluence) estão configuradas e prontas para operação autônoma.';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
allReady,
|
|
276
|
+
github: gh,
|
|
277
|
+
atlassian,
|
|
278
|
+
envProtected: envIgnored,
|
|
279
|
+
missing,
|
|
280
|
+
remediation,
|
|
281
|
+
promptGuidance
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Formata relatório executivo em Markdown sobre o status das credenciais e conformidade com a Regra 15.
|
|
287
|
+
* @param {object} [health={}]
|
|
288
|
+
* @returns {string}
|
|
289
|
+
*/
|
|
290
|
+
function formatReport(health = {}) {
|
|
291
|
+
const safeHealth = health && typeof health === 'object' ? health : {};
|
|
292
|
+
let md = '# 🛡️ Credentials Guard — Status Report de Credenciais & Regra 15\n\n';
|
|
293
|
+
|
|
294
|
+
const allReady = Boolean(safeHealth.allReady);
|
|
295
|
+
const statusBadge = allReady ? '🟢 **HOMOLOGADO**' : '🔴 **PENDENTE DE CONFIGURAÇÃO**';
|
|
296
|
+
|
|
297
|
+
md += `**Status Global:** ${statusBadge}\n\n`;
|
|
298
|
+
md += '| Componente / Integração | Status | Detalhes | Ação Recomendada |\n';
|
|
299
|
+
md += '| :--- | :---: | :--- | :--- |\n';
|
|
300
|
+
|
|
301
|
+
const gh = safeHealth.github || {};
|
|
302
|
+
const ghStatus = gh.authenticated ? '✅ Conectado' : '❌ Não autenticado';
|
|
303
|
+
const ghDetails = gh.user ? `Usuário: \`${gh.user}\`` : 'Token/CLI ausente';
|
|
304
|
+
const ghAction = gh.authenticated ? 'Nenhuma' : 'Executar `gh auth login`';
|
|
305
|
+
md += `| **GitHub CLI / Token** | ${ghStatus} | ${ghDetails} | ${ghAction} |\n`;
|
|
306
|
+
|
|
307
|
+
const atl = safeHealth.atlassian || {};
|
|
308
|
+
const atlStatus = atl.configured ? '✅ Configurado' : '❌ Incompleto';
|
|
309
|
+
const atlDetails = atl.configured
|
|
310
|
+
? `Domínio: \`${atl.domain || 'N/A'}\` | Email: \`${atl.email || 'N/A'}\``
|
|
311
|
+
: `Campos faltantes: \`${(atl.missingFields || []).join(', ') || 'todos'}\``;
|
|
312
|
+
const atlAction = atl.configured ? 'Nenhuma' : 'Configurar variáveis no `.env`';
|
|
313
|
+
md += `| **Atlassian Cloud (Jira & Confluence)** | ${atlStatus} | ${atlDetails} | ${atlAction} |\n`;
|
|
314
|
+
|
|
315
|
+
if (safeHealth.envProtected !== undefined) {
|
|
316
|
+
const envStatus = safeHealth.envProtected ? '✅ Protegido' : '⚠️ Desprotegido';
|
|
317
|
+
const envDetails = safeHealth.envProtected ? '`.env` consta no `.gitignore`' : '`.env` ausente do `.gitignore`';
|
|
318
|
+
const envAction = safeHealth.envProtected ? 'Nenhuma' : 'Adicionar `.env` ao `.gitignore`';
|
|
319
|
+
md += `| **Proteção de Segredos (.gitignore)** | ${envStatus} | ${envDetails} | ${envAction} |\n`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
md += '\n';
|
|
323
|
+
|
|
324
|
+
if (!allReady && Array.isArray(safeHealth.remediation) && safeHealth.remediation.length > 0) {
|
|
325
|
+
md += '### 🛠️ Ações de Remediação Imediata\n\n';
|
|
326
|
+
for (const r of safeHealth.remediation) {
|
|
327
|
+
md += `- **${r.tool}:** ${r.instruction || r.command}\n`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return md;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Audita a prontidão de credenciais e integridade emitindo relatório oficial no formato OASIS SARIF v2.1.0.
|
|
336
|
+
* @param {object} [result={}]
|
|
337
|
+
* @returns {object} SARIF v2.1.0 JSON object
|
|
338
|
+
*/
|
|
339
|
+
function auditToSarif(result = {}) {
|
|
340
|
+
const safeResult = result && typeof result === 'object' ? result : {};
|
|
341
|
+
const rules = [
|
|
342
|
+
{
|
|
343
|
+
id: 'RULE-SEC-015',
|
|
344
|
+
name: 'MissingRequiredCredentials',
|
|
345
|
+
severity: 'HIGH',
|
|
346
|
+
message: 'Credenciais essenciais da Regra 15 ausentes (Jira, Confluence ou GitHub).'
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: 'RULE-SEC-016',
|
|
350
|
+
name: 'GitignoreEnvUnprotected',
|
|
351
|
+
severity: 'CRITICAL',
|
|
352
|
+
message: 'Arquivo de ambiente .env desprotegido no .gitignore (Risco de vazamento de segredos).'
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'RULE-SEC-017',
|
|
356
|
+
name: 'GitHubCliUnauthenticated',
|
|
357
|
+
severity: 'MEDIUM',
|
|
358
|
+
message: 'CLI do GitHub (gh) não está autenticado.'
|
|
359
|
+
}
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
const findings = [];
|
|
363
|
+
|
|
364
|
+
// Avaliação baseada em validateRule15
|
|
365
|
+
if (safeResult.valid === false && Array.isArray(safeResult.missing)) {
|
|
366
|
+
findings.push({
|
|
367
|
+
ruleId: 'RULE-SEC-015',
|
|
368
|
+
message: `Regra 15 violada: Faltam pré-requisitos obrigatórios (${safeResult.missing.join(', ')}).`,
|
|
369
|
+
filePath: '.env',
|
|
370
|
+
line: 1,
|
|
371
|
+
column: 1
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Avaliação baseada em runCredentialHealthcheck
|
|
376
|
+
if (safeResult.allReady === false) {
|
|
377
|
+
if (safeResult.atlassian && !safeResult.atlassian.configured) {
|
|
378
|
+
findings.push({
|
|
379
|
+
ruleId: 'RULE-SEC-015',
|
|
380
|
+
message: `Credenciais Atlassian não configuradas (${(safeResult.atlassian.missingFields || []).join(', ')}).`,
|
|
381
|
+
filePath: '.env',
|
|
382
|
+
line: 1,
|
|
383
|
+
column: 1
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (safeResult.github && !safeResult.github.authenticated) {
|
|
388
|
+
findings.push({
|
|
389
|
+
ruleId: 'RULE-SEC-017',
|
|
390
|
+
message: 'GitHub CLI não autenticado no ambiente local.',
|
|
391
|
+
filePath: '.git/config',
|
|
392
|
+
line: 1,
|
|
393
|
+
column: 1
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (safeResult.envProtected === false) {
|
|
399
|
+
findings.push({
|
|
400
|
+
ruleId: 'RULE-SEC-016',
|
|
401
|
+
message: 'Arquivo .env detectado sem proteção de entrada correspondente no .gitignore (CWE-798).',
|
|
402
|
+
filePath: '.gitignore',
|
|
403
|
+
line: 1,
|
|
404
|
+
column: 1
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return createSarifReport({
|
|
409
|
+
toolName: 'agent-credentials-guard',
|
|
410
|
+
toolVersion: '5.1.0',
|
|
411
|
+
rules,
|
|
412
|
+
findings,
|
|
413
|
+
properties: {
|
|
414
|
+
category: 'Security & Secret Management',
|
|
415
|
+
rule: 'Regra 15 - Imutabilidade de Requisitos & Validação Compulsória de Segredos'
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Classe Canônica CredentialsGuard
|
|
422
|
+
*/
|
|
423
|
+
class CredentialsGuard {
|
|
424
|
+
constructor(options = {}) {
|
|
425
|
+
this.targetDir = typeof options.targetDir === 'string' && options.targetDir ? options.targetDir : process.cwd();
|
|
426
|
+
this.logger = options.logger || console;
|
|
427
|
+
this.dryRun = Boolean(options.dryRun);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
validateRule15(opts = {}) {
|
|
431
|
+
return CredentialsGuard.validateRule15(opts);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
checkAtlassianAuth(targetDir = this.targetDir) {
|
|
435
|
+
return CredentialsGuard.checkAtlassianAuth(targetDir);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
checkGithubAuth() {
|
|
439
|
+
return CredentialsGuard.checkGithubAuth();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
configureCredentials(targetDir = this.targetDir, credentials = {}) {
|
|
443
|
+
return CredentialsGuard.configureCredentials(targetDir, credentials);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
runCredentialHealthcheck(targetDir = this.targetDir) {
|
|
447
|
+
return CredentialsGuard.runCredentialHealthcheck(targetDir);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
formatReport(health) {
|
|
451
|
+
return CredentialsGuard.formatReport(health);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
auditToSarif(result) {
|
|
455
|
+
return CredentialsGuard.auditToSarif(result);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Métodos estáticos canônicos
|
|
459
|
+
static get InviolableRuleError() { return InviolableRuleError; }
|
|
460
|
+
static validateRule15(opts) { return validateRule15(opts); }
|
|
461
|
+
static checkAtlassianAuth(dir) { return checkAtlassianAuth(dir); }
|
|
462
|
+
static checkGithubAuth() { return checkGithubAuth(); }
|
|
463
|
+
static configureCredentials(dir, creds) { return configureCredentials(dir, creds); }
|
|
464
|
+
static runCredentialHealthcheck(dir) { return runCredentialHealthcheck(dir); }
|
|
465
|
+
static formatReport(health) { return formatReport(health); }
|
|
466
|
+
static auditToSarif(result) { return auditToSarif(result); }
|
|
467
|
+
static maskSecret(str, chars) { return maskSecret(str, chars); }
|
|
468
|
+
static parseEnvLines(str) { return parseEnvLines(str); }
|
|
469
|
+
static mergeEnvUpdates(existing, updates) { return mergeEnvUpdates(existing, updates); }
|
|
470
|
+
static ensureGitignoreProtected(existing, entries) { return ensureGitignoreProtected(existing, entries); }
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (require.main === module) {
|
|
474
|
+
const outcome = runCredentialHealthcheck();
|
|
475
|
+
console.log(formatReport(outcome));
|
|
476
|
+
if (!outcome.allReady) {
|
|
477
|
+
process.exitCode = 1;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
module.exports = {
|
|
482
|
+
CredentialsGuard,
|
|
483
|
+
InviolableRuleError,
|
|
484
|
+
validateRule15,
|
|
485
|
+
getAtlassianConfig: getAtlassianCredentials,
|
|
486
|
+
getGitHubConfig: checkGithubAuth,
|
|
487
|
+
checkGitHubAuth: checkGithubAuth,
|
|
488
|
+
checkGithubAuth,
|
|
489
|
+
getGitRepositoryUrl,
|
|
490
|
+
getGitMetadata,
|
|
491
|
+
checkAtlassianAuth,
|
|
492
|
+
configureCredentials,
|
|
493
|
+
runCredentialHealthcheck,
|
|
494
|
+
formatReport,
|
|
495
|
+
auditToSarif,
|
|
496
|
+
auditCredentialsToSarif: auditToSarif,
|
|
497
|
+
auditAtlassianToSarif: auditAtlassianToSarifBase,
|
|
498
|
+
maskSecret,
|
|
499
|
+
AtlassianClient
|
|
500
|
+
};
|