@semacode/cli 1.2.9 → 1.2.12
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 +2 -2
- package/dist/drift.d.ts +1 -1
- package/dist/drift.js +213 -2
- package/dist/drift.js.map +1 -1
- package/dist/index.js +66 -8
- package/dist/index.js.map +1 -1
- package/docs/sintaxe.md +95 -1
- package/node_modules/@sema/gerador-dart/package.json +1 -1
- package/node_modules/@sema/gerador-lua/package.json +1 -1
- package/node_modules/@sema/gerador-python/package.json +1 -1
- package/node_modules/@sema/gerador-typescript/package.json +1 -1
- package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +7 -1
- package/node_modules/@sema/nucleo/dist/formatador/index.js +65 -21
- package/node_modules/@sema/nucleo/dist/formatador/index.js.map +1 -1
- package/node_modules/@sema/nucleo/dist/ir/conversor.js +91 -1
- package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +1 -1
- package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +71 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.js +6 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +1 -1
- package/node_modules/@sema/nucleo/dist/parser/parser.js +12 -0
- package/node_modules/@sema/nucleo/dist/parser/parser.js.map +1 -1
- package/node_modules/@sema/nucleo/dist/semantico/analisador.js +409 -8
- package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +1 -1
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.d.ts +6 -1
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.js +63 -13
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.js.map +1 -1
- package/node_modules/@sema/nucleo/dist/semantico/seguranca.d.ts +91 -0
- package/node_modules/@sema/nucleo/dist/semantico/seguranca.js +258 -0
- package/node_modules/@sema/nucleo/dist/semantico/seguranca.js.map +1 -0
- package/node_modules/@sema/nucleo/package.json +1 -1
- package/node_modules/@sema/padroes/package.json +1 -1
- package/package.json +7 -7
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { criarDiagnostico } from "../diagnosticos/index.js";
|
|
2
2
|
import { ehCategoriaEfeitoSemantico, ehCriticidadeEfeitoSemantico, extrairReferenciasDaExpressao, parsearEfeitoSemantico, parsearEtapaFlow, parsearExpressaoSemantica, parsearTransicaoEstado, } from "./estruturas.js";
|
|
3
|
+
import { CLASSIFICACOES_DADO_SUPORTADAS, MODOS_AUTH_SUPORTADOS, MOTIVOS_AUDIT_SUPORTADOS, ORIGENS_AUTH_SUPORTADAS, PRINCIPAIS_AUTH_SUPORTADOS, REDACOES_LOG_SUPORTADAS, TENANTS_AUTHZ_SUPORTADOS, contratoDadosTemSegredoOuCredencial, contratoDadosTemSensivel, extrairContratoAudit, extrairContratoAuth, extrairContratoAuthz, extrairContratoDados, extrairContratoForbidden, extrairContratoSegredos, efeitoEhPrivilegiado, efeitoRequerSegredo, forbiddenContemRegra, } from "./seguranca.js";
|
|
3
4
|
function ehUseInterop(use) {
|
|
4
5
|
return use.origem !== "sema";
|
|
5
6
|
}
|
|
@@ -44,6 +45,41 @@ const CAMPOS_EXECUCAO_SUPORTADOS = new Set([
|
|
|
44
45
|
"compensacao",
|
|
45
46
|
"criticidade_operacional",
|
|
46
47
|
]);
|
|
48
|
+
const CAMPOS_AUTH_SUPORTADOS = new Set([
|
|
49
|
+
"modo",
|
|
50
|
+
"estrategia",
|
|
51
|
+
"principal",
|
|
52
|
+
"origem",
|
|
53
|
+
]);
|
|
54
|
+
const CAMPOS_AUTHZ_SUPORTADOS = new Set([
|
|
55
|
+
"papel",
|
|
56
|
+
"papeis",
|
|
57
|
+
"escopo",
|
|
58
|
+
"escopos",
|
|
59
|
+
"politica",
|
|
60
|
+
"tenant",
|
|
61
|
+
]);
|
|
62
|
+
const CAMPOS_DADOS_SUPORTADOS = new Set([
|
|
63
|
+
"classificacao_padrao",
|
|
64
|
+
"redacao_log",
|
|
65
|
+
"retencao",
|
|
66
|
+
]);
|
|
67
|
+
const CAMPOS_AUDIT_SUPORTADOS = new Set([
|
|
68
|
+
"evento",
|
|
69
|
+
"ator",
|
|
70
|
+
"correlacao",
|
|
71
|
+
"retencao",
|
|
72
|
+
"motivo",
|
|
73
|
+
]);
|
|
74
|
+
const CAMPOS_SEGREDO_SUPORTADOS = new Set([
|
|
75
|
+
"origem",
|
|
76
|
+
"escopo",
|
|
77
|
+
"acesso",
|
|
78
|
+
"rotacao",
|
|
79
|
+
"nao_logar",
|
|
80
|
+
"nao_retornar",
|
|
81
|
+
"mascarar",
|
|
82
|
+
]);
|
|
47
83
|
const CAMPOS_ERRO_OPERACIONAL = new Set([
|
|
48
84
|
"mensagem",
|
|
49
85
|
"categoria",
|
|
@@ -109,9 +145,15 @@ function validarCamposDeTipos(campos, tiposConhecidos, diagnosticos, contexto) {
|
|
|
109
145
|
}
|
|
110
146
|
}
|
|
111
147
|
function localizarBloco(corpo, nome) {
|
|
112
|
-
|
|
148
|
+
if (!corpo) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
return corpo.blocos.find((bloco) => bloco.tipo === "bloco_generico" && (bloco.palavraChave === nome || bloco.nome === nome));
|
|
113
152
|
}
|
|
114
153
|
function localizarCampo(bloco, ...nomes) {
|
|
154
|
+
if (!bloco) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
115
157
|
return bloco.campos.find((campo) => nomes.includes(campo.nome));
|
|
116
158
|
}
|
|
117
159
|
function valorCampoCompleto(campo) {
|
|
@@ -230,23 +272,280 @@ function validarVinculos(bloco, diagnosticos, contexto) {
|
|
|
230
272
|
}
|
|
231
273
|
}
|
|
232
274
|
}
|
|
233
|
-
function
|
|
234
|
-
|
|
275
|
+
function extrairPerfilCompatibilidade(bloco, padrao = "interno") {
|
|
276
|
+
const perfil = bloco
|
|
277
|
+
? valorCampoCompleto(localizarCampo(bloco, "perfil", "compatibilidade"))?.toLowerCase()
|
|
278
|
+
: undefined;
|
|
279
|
+
if (perfil === "publico"
|
|
280
|
+
|| perfil === "interno"
|
|
281
|
+
|| perfil === "experimental"
|
|
282
|
+
|| perfil === "legado"
|
|
283
|
+
|| perfil === "deprecado") {
|
|
284
|
+
return perfil;
|
|
285
|
+
}
|
|
286
|
+
return padrao;
|
|
287
|
+
}
|
|
288
|
+
function coletarSuperficiesModulo(modulo) {
|
|
289
|
+
return [
|
|
290
|
+
...modulo.workers.map((superficie) => ({ tipo: "worker", superficie })),
|
|
291
|
+
...modulo.eventos.map((superficie) => ({ tipo: "evento", superficie })),
|
|
292
|
+
...modulo.filas.map((superficie) => ({ tipo: "fila", superficie })),
|
|
293
|
+
...modulo.crons.map((superficie) => ({ tipo: "cron", superficie })),
|
|
294
|
+
...modulo.webhooks.map((superficie) => ({ tipo: "webhook", superficie })),
|
|
295
|
+
...modulo.caches.map((superficie) => ({ tipo: "cache", superficie })),
|
|
296
|
+
...modulo.storages.map((superficie) => ({ tipo: "storage", superficie })),
|
|
297
|
+
...modulo.policies.map((superficie) => ({ tipo: "policy", superficie })),
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
function superficieEhPublica(superficie, tipoSuperficie) {
|
|
301
|
+
return extrairPerfilCompatibilidade(superficie, tipoSuperficie === "webhook" ? "publico" : "interno") === "publico";
|
|
302
|
+
}
|
|
303
|
+
function taskEhSensivel(task) {
|
|
304
|
+
const criticidadeOperacional = task.execucao
|
|
305
|
+
? valorCampoCompleto(localizarCampo(task.execucao, "criticidade_operacional"))
|
|
306
|
+
: undefined;
|
|
307
|
+
if (criticidadeOperacional === "alta" || criticidadeOperacional === "critica") {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
return (task.effects?.linhas ?? []).some((linha) => {
|
|
311
|
+
const efeito = parsearEfeitoSemantico(linha.conteudo);
|
|
312
|
+
if (!efeito) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
return efeito.categoria === "persistencia" || efeito.criticidade === "alta" || efeito.criticidade === "critica" || efeitoEhPrivilegiado(efeito);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function taskTemRastreabilidade(task) {
|
|
319
|
+
return Boolean(task.impl || task.vinculos);
|
|
320
|
+
}
|
|
321
|
+
function routeEhMutante(route) {
|
|
322
|
+
const metodo = (localizarCampo(route.corpo, "metodo")?.valor ?? "").toUpperCase();
|
|
323
|
+
return ["POST", "PUT", "PATCH", "DELETE"].includes(metodo);
|
|
324
|
+
}
|
|
325
|
+
function validarExecucaoBloco(execucao, diagnosticos, contexto) {
|
|
326
|
+
if (!execucao) {
|
|
235
327
|
return;
|
|
236
328
|
}
|
|
237
|
-
for (const campo of
|
|
329
|
+
for (const campo of execucao.campos) {
|
|
238
330
|
if (!CAMPOS_EXECUCAO_SUPORTADOS.has(campo.nome)) {
|
|
239
|
-
diagnosticos.push(criarDiagnostico("SEM065", `Campo de execucao "${campo.nome}" nao e suportado
|
|
331
|
+
diagnosticos.push(criarDiagnostico("SEM065", `Campo de execucao "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use apenas idempotencia, timeout, retry, compensacao ou criticidade_operacional."));
|
|
240
332
|
continue;
|
|
241
333
|
}
|
|
242
334
|
if (campo.nome === "criticidade_operacional") {
|
|
243
335
|
const criticidade = valorCampoCompleto(campo);
|
|
244
336
|
if (criticidade && !CRITICIDADES_OPERACIONAIS.has(criticidade)) {
|
|
245
|
-
diagnosticos.push(criarDiagnostico("SEM066", `
|
|
337
|
+
diagnosticos.push(criarDiagnostico("SEM066", `Execucao de ${contexto} declarou criticidade_operacional invalida: "${criticidade}".`, "erro", campo.intervalo, "Use apenas baixa, media, alta ou critica em execucao."));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function validarExecucao(task, diagnosticos) {
|
|
343
|
+
validarExecucaoBloco(task.execucao, diagnosticos, `task "${task.nome}"`);
|
|
344
|
+
}
|
|
345
|
+
function validarAuthBloco(bloco, diagnosticos, contexto) {
|
|
346
|
+
if (!bloco) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
for (const campo of bloco.campos) {
|
|
350
|
+
if (!CAMPOS_AUTH_SUPORTADOS.has(campo.nome)) {
|
|
351
|
+
diagnosticos.push(criarDiagnostico("SEM074", `Campo de auth "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use apenas modo, estrategia, principal ou origem em auth."));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const auth = extrairContratoAuth(bloco);
|
|
355
|
+
if (auth.modo && !MODOS_AUTH_SUPORTADOS.has(auth.modo)) {
|
|
356
|
+
diagnosticos.push(criarDiagnostico("SEM075", `Auth em ${contexto} declarou modo invalido: "${auth.modo}".`, "erro", bloco.intervalo, "Use obrigatorio, opcional, anonimo, interno ou m2m."));
|
|
357
|
+
}
|
|
358
|
+
if (auth.principal && !PRINCIPAIS_AUTH_SUPORTADOS.has(auth.principal)) {
|
|
359
|
+
diagnosticos.push(criarDiagnostico("SEM076", `Auth em ${contexto} declarou principal invalido: "${auth.principal}".`, "erro", bloco.intervalo, "Use usuario, servico, sistema ou anonimo."));
|
|
360
|
+
}
|
|
361
|
+
if (auth.origem && !ORIGENS_AUTH_SUPORTADAS.has(auth.origem)) {
|
|
362
|
+
diagnosticos.push(criarDiagnostico("SEM077", `Auth em ${contexto} declarou origem invalida: "${auth.origem}".`, "erro", bloco.intervalo, "Use publica, interna, worker, webhook, fila ou cron."));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function validarAuthzBloco(bloco, diagnosticos, contexto) {
|
|
366
|
+
if (!bloco) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
for (const campo of bloco.campos) {
|
|
370
|
+
if (!CAMPOS_AUTHZ_SUPORTADOS.has(campo.nome)) {
|
|
371
|
+
diagnosticos.push(criarDiagnostico("SEM078", `Campo de authz "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use papel, papeis, escopo, escopos, politica ou tenant em authz."));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const authz = extrairContratoAuthz(bloco);
|
|
375
|
+
if (authz.tenant && !TENANTS_AUTHZ_SUPORTADOS.has(authz.tenant)) {
|
|
376
|
+
diagnosticos.push(criarDiagnostico("SEM079", `Authz em ${contexto} declarou tenant invalido: "${authz.tenant}".`, "erro", bloco.intervalo, "Use obrigatorio, opcional ou isolado."));
|
|
377
|
+
}
|
|
378
|
+
if (authz.papeis.length === 0 && authz.escopos.length === 0 && !authz.politica) {
|
|
379
|
+
diagnosticos.push(criarDiagnostico("SEM080", `Authz em ${contexto} precisa declarar papeis, escopos ou politica.`, "erro", bloco.intervalo, "Explicite ao menos um papel, escopo ou politica para a autorizacao nao virar enfeite."));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function validarDadosBloco(bloco, diagnosticos, contexto) {
|
|
383
|
+
if (!bloco) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
for (const campo of bloco.campos) {
|
|
387
|
+
const valor = valorCampoCompleto(campo);
|
|
388
|
+
if (CAMPOS_DADOS_SUPORTADOS.has(campo.nome)) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (valor && !CLASSIFICACOES_DADO_SUPORTADAS.has(valor)) {
|
|
392
|
+
diagnosticos.push(criarDiagnostico("SEM081", `Dados em ${contexto} declarou classificacao invalida para "${campo.nome}": "${valor}".`, "erro", campo.intervalo, "Use publico, interno, pii, financeiro, credencial ou segredo."));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const dados = extrairContratoDados(bloco);
|
|
396
|
+
if (dados.classificacaoPadrao && !CLASSIFICACOES_DADO_SUPORTADAS.has(dados.classificacaoPadrao)) {
|
|
397
|
+
diagnosticos.push(criarDiagnostico("SEM081", `Dados em ${contexto} declarou classificacao_padrao invalida: "${dados.classificacaoPadrao}".`, "erro", bloco.intervalo, "Use publico, interno, pii, financeiro, credencial ou segredo."));
|
|
398
|
+
}
|
|
399
|
+
if (dados.redacaoLog && !REDACOES_LOG_SUPORTADAS.has(dados.redacaoLog)) {
|
|
400
|
+
diagnosticos.push(criarDiagnostico("SEM082", `Dados em ${contexto} declarou redacao_log invalida: "${dados.redacaoLog}".`, "erro", bloco.intervalo, "Use livre, parcial, obrigatoria ou proibida."));
|
|
401
|
+
}
|
|
402
|
+
for (const subbloco of bloco.blocos) {
|
|
403
|
+
if (subbloco.tipo !== "bloco_generico") {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const nomeSubbloco = subbloco.nome ?? subbloco.palavraChave;
|
|
407
|
+
if (nomeSubbloco !== "input" && nomeSubbloco !== "output") {
|
|
408
|
+
diagnosticos.push(criarDiagnostico("SEM083", `Dados em ${contexto} nao suporta o subbloco "${nomeSubbloco}".`, "erro", subbloco.intervalo, "Use apenas campos diretos ou subblocos input/output para classificar dados."));
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
for (const campo of subbloco.campos) {
|
|
412
|
+
const classificacao = valorCampoCompleto(campo);
|
|
413
|
+
if (classificacao && !CLASSIFICACOES_DADO_SUPORTADAS.has(classificacao)) {
|
|
414
|
+
diagnosticos.push(criarDiagnostico("SEM081", `Dados em ${contexto} declarou classificacao invalida para "${nomeSubbloco}.${campo.nome}": "${classificacao}".`, "erro", campo.intervalo, "Use publico, interno, pii, financeiro, credencial ou segredo."));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function validarAuditBloco(bloco, diagnosticos, contexto) {
|
|
420
|
+
if (!bloco) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
for (const campo of bloco.campos) {
|
|
424
|
+
if (!CAMPOS_AUDIT_SUPORTADOS.has(campo.nome)) {
|
|
425
|
+
diagnosticos.push(criarDiagnostico("SEM084", `Campo de audit "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use evento, ator, correlacao, retencao ou motivo em audit."));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const audit = extrairContratoAudit(bloco);
|
|
429
|
+
if (!audit.evento) {
|
|
430
|
+
diagnosticos.push(criarDiagnostico("SEM085", `Audit em ${contexto} precisa declarar evento.`, "erro", bloco.intervalo, "Explique qual evento auditavel sera registrado para a operacao."));
|
|
431
|
+
}
|
|
432
|
+
if (audit.motivo && !MOTIVOS_AUDIT_SUPORTADOS.has(audit.motivo)) {
|
|
433
|
+
diagnosticos.push(criarDiagnostico("SEM086", `Audit em ${contexto} declarou motivo invalido: "${audit.motivo}".`, "erro", bloco.intervalo, "Use obrigatorio, opcional ou dispensado."));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function validarSegredosBloco(bloco, diagnosticos, contexto) {
|
|
437
|
+
if (!bloco) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const segredos = extrairContratoSegredos(bloco);
|
|
441
|
+
if (segredos.itens.length === 0) {
|
|
442
|
+
diagnosticos.push(criarDiagnostico("SEM087", `Segredos em ${contexto} precisa declarar ao menos um segredo nomeado.`, "erro", bloco.intervalo, "Use segredos { nome_do_segredo { origem: vault escopo: runtime ... } }."));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
for (const item of bloco.blocos) {
|
|
446
|
+
if (item.tipo !== "bloco_generico") {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
for (const campo of item.campos) {
|
|
450
|
+
if (!CAMPOS_SEGREDO_SUPORTADOS.has(campo.nome)) {
|
|
451
|
+
diagnosticos.push(criarDiagnostico("SEM087", `Segredo "${item.nome ?? item.palavraChave}" em ${contexto} usa o campo "${campo.nome}", que nao e suportado.`, "erro", campo.intervalo, "Use origem, escopo, acesso, rotacao, nao_logar, nao_retornar ou mascarar."));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const nomeSegredo = item.nome ?? item.palavraChave;
|
|
455
|
+
const origem = valorCampoCompleto(localizarCampo(item, "origem"));
|
|
456
|
+
if (!origem) {
|
|
457
|
+
diagnosticos.push(criarDiagnostico("SEM088", `Segredo "${nomeSegredo}" em ${contexto} precisa declarar origem.`, "erro", item.intervalo, "Explicite a origem do segredo, como vault, env, secret_manager ou runtime."));
|
|
458
|
+
}
|
|
459
|
+
for (const nomeBooleano of ["nao_logar", "nao_retornar", "mascarar"]) {
|
|
460
|
+
const campo = localizarCampo(item, nomeBooleano);
|
|
461
|
+
const valor = valorCampoCompleto(campo);
|
|
462
|
+
if (campo && valor !== "verdadeiro" && valor !== "true" && valor !== "falso" && valor !== "false") {
|
|
463
|
+
diagnosticos.push(criarDiagnostico("SEM089", `Segredo "${nomeSegredo}" em ${contexto} declarou "${nomeBooleano}" com valor invalido: "${valor}".`, "erro", campo.intervalo, "Use verdadeiro/falso para campos booleanos de segredos."));
|
|
246
464
|
}
|
|
247
465
|
}
|
|
248
466
|
}
|
|
249
467
|
}
|
|
468
|
+
function validarForbiddenBloco(bloco, efeitos, diagnosticos, contexto) {
|
|
469
|
+
const forbidden = extrairContratoForbidden(bloco);
|
|
470
|
+
if (!bloco) {
|
|
471
|
+
return forbidden;
|
|
472
|
+
}
|
|
473
|
+
for (const regra of forbidden.regras) {
|
|
474
|
+
if (!/^[A-Za-z_][A-Za-z0-9_.-]*$/u.test(regra)) {
|
|
475
|
+
diagnosticos.push(criarDiagnostico("SEM090", `Forbidden em ${contexto} declarou regra invalida: "${regra}".`, "erro", bloco.intervalo, "Use regras simples como network.egress, shell.exec, retorno.credencial ou log.segredo."));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
for (const linha of efeitos) {
|
|
479
|
+
const efeito = parsearEfeitoSemantico(linha.conteudo);
|
|
480
|
+
if (efeito && forbiddenContemRegra(forbidden, efeito.categoria)) {
|
|
481
|
+
diagnosticos.push(criarDiagnostico("SEM091", `Forbidden em ${contexto} proibe "${efeito.categoria}", mas effects ainda declara esse efeito.`, "erro", linha.intervalo, "Remova o efeito proibido ou ajuste o bloco forbidden para refletir a operacao permitida de verdade."));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return forbidden;
|
|
485
|
+
}
|
|
486
|
+
function coletarPerfilSegurancaDeclarado(corpo, effects, diagnosticos, contexto) {
|
|
487
|
+
const authBloco = localizarBloco(corpo, "auth");
|
|
488
|
+
const authzBloco = localizarBloco(corpo, "authz");
|
|
489
|
+
const dadosBloco = localizarBloco(corpo, "dados");
|
|
490
|
+
const auditBloco = localizarBloco(corpo, "audit");
|
|
491
|
+
const segredosBloco = localizarBloco(corpo, "segredos");
|
|
492
|
+
const forbiddenBloco = localizarBloco(corpo, "forbidden");
|
|
493
|
+
if (diagnosticos) {
|
|
494
|
+
validarAuthBloco(authBloco, diagnosticos, contexto);
|
|
495
|
+
validarAuthzBloco(authzBloco, diagnosticos, contexto);
|
|
496
|
+
validarDadosBloco(dadosBloco, diagnosticos, contexto);
|
|
497
|
+
validarAuditBloco(auditBloco, diagnosticos, contexto);
|
|
498
|
+
validarSegredosBloco(segredosBloco, diagnosticos, contexto);
|
|
499
|
+
}
|
|
500
|
+
const forbidden = diagnosticos
|
|
501
|
+
? validarForbiddenBloco(forbiddenBloco, effects?.linhas ?? [], diagnosticos, contexto)
|
|
502
|
+
: extrairContratoForbidden(forbiddenBloco);
|
|
503
|
+
const auth = extrairContratoAuth(authBloco);
|
|
504
|
+
const authz = extrairContratoAuthz(authzBloco);
|
|
505
|
+
const dados = extrairContratoDados(dadosBloco);
|
|
506
|
+
const audit = extrairContratoAudit(auditBloco);
|
|
507
|
+
const segredos = extrairContratoSegredos(segredosBloco);
|
|
508
|
+
const efeitosEstruturados = (effects?.linhas ?? [])
|
|
509
|
+
.map((linha) => parsearEfeitoSemantico(linha.conteudo))
|
|
510
|
+
.filter((efeito) => Boolean(efeito));
|
|
511
|
+
return {
|
|
512
|
+
auth,
|
|
513
|
+
authz,
|
|
514
|
+
dados,
|
|
515
|
+
audit,
|
|
516
|
+
segredos,
|
|
517
|
+
forbidden,
|
|
518
|
+
efeitoPrivilegiado: efeitosEstruturados.some((efeito) => efeitoEhPrivilegiado(efeito)),
|
|
519
|
+
dadosSensiveis: contratoDadosTemSensivel(dados),
|
|
520
|
+
exigeSegredos: efeitosEstruturados.some((efeito) => efeitoRequerSegredo(efeito)) || contratoDadosTemSegredoOuCredencial(dados),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function emitirGuardrailsSeguranca(contexto, intervalo, perfil, diagnosticos, opcoes) {
|
|
524
|
+
const exigeAuth = opcoes.publico;
|
|
525
|
+
const exigeAuthz = opcoes.publico || opcoes.sensivel || perfil.efeitoPrivilegiado || perfil.dadosSensiveis;
|
|
526
|
+
const exigeDados = opcoes.publico || opcoes.sensivel || perfil.efeitoPrivilegiado;
|
|
527
|
+
const exigeAudit = opcoes.publico || opcoes.sensivel || perfil.efeitoPrivilegiado || perfil.dadosSensiveis;
|
|
528
|
+
const exigeSegredos = perfil.exigeSegredos;
|
|
529
|
+
const exigeForbidden = perfil.efeitoPrivilegiado || perfil.dadosSensiveis;
|
|
530
|
+
if (exigeAuth && !perfil.auth.explicita) {
|
|
531
|
+
diagnosticos.push(criarDiagnostico("SEM094", `${contexto} deveria declarar auth explicita para reduzir ambiguidade de seguranca na borda publica.`, "aviso", intervalo, "Declare auth { modo: obrigatorio|anonimo ... } para deixar a intencao da exposicao publica cristalina."));
|
|
532
|
+
}
|
|
533
|
+
if (exigeAuthz && !perfil.authz.explicita) {
|
|
534
|
+
diagnosticos.push(criarDiagnostico("SEM095", `${contexto} deveria declarar authz explicita porque opera com risco, privilegio ou exposicao publica.`, "aviso", intervalo, "Declare papeis, escopos ou politica em authz para nao empurrar autorizacao para o limbo do codigo vivo."));
|
|
535
|
+
}
|
|
536
|
+
if (exigeDados && !perfil.dados.explicita) {
|
|
537
|
+
diagnosticos.push(criarDiagnostico("SEM096", `${contexto} deveria classificar dados de forma explicita em dados { ... }.`, "aviso", intervalo, "Classifique input/output com publico, interno, pii, financeiro, credencial ou segredo."));
|
|
538
|
+
}
|
|
539
|
+
if (exigeAudit && !perfil.audit.explicita) {
|
|
540
|
+
diagnosticos.push(criarDiagnostico("SEM097", `${contexto} deveria declarar audit explicita para operar com trilha semantica de seguranca.`, "aviso", intervalo, "Declare audit { evento: ... correlacao: ... motivo: ... } para nao depender de adivinhacao operacional."));
|
|
541
|
+
}
|
|
542
|
+
if (exigeSegredos && !perfil.segredos.explicita) {
|
|
543
|
+
diagnosticos.push(criarDiagnostico("SEM098", `${contexto} deveria declarar segredos explicitos porque toca credencial, segredo ou secret.read.`, "aviso", intervalo, "Use segredos { nome { origem: vault escopo: runtime ... } } para governar acesso sensivel."));
|
|
544
|
+
}
|
|
545
|
+
if (exigeForbidden && !perfil.forbidden.explicita) {
|
|
546
|
+
diagnosticos.push(criarDiagnostico("SEM099", `${contexto} deveria declarar forbidden explicito para proibir operacoes perigosas ou vazamento semantico.`, "aviso", intervalo, "Use forbidden { network.egress shell.exec log.segredo retorno.credencial } conforme o risco da operacao."));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
250
549
|
function validarErroOperacional(task, diagnosticos) {
|
|
251
550
|
if (!task.error) {
|
|
252
551
|
return;
|
|
@@ -289,6 +588,7 @@ function validarSuperficie(superficie, tipoSuperficie, tasksConhecidas, tiposCon
|
|
|
289
588
|
const effects = localizarBloco(superficie, "effects");
|
|
290
589
|
const impl = localizarBloco(superficie, "impl");
|
|
291
590
|
const vinculos = localizarBloco(superficie, "vinculos");
|
|
591
|
+
const execucao = localizarBloco(superficie, "execucao");
|
|
292
592
|
if (!task && !impl && !vinculos) {
|
|
293
593
|
diagnosticos.push(criarDiagnostico("SEM069", `Superficie ${tipoSuperficie} "${nomeSuperficie}" precisa declarar task, impl ou vinculos para nao virar bloco decorativo.`, "erro", superficie.intervalo, "Declare ao menos uma task associada, um impl explicito ou vinculos rastreaveis com codigo vivo."));
|
|
294
594
|
}
|
|
@@ -304,7 +604,13 @@ function validarSuperficie(superficie, tipoSuperficie, tasksConhecidas, tiposCon
|
|
|
304
604
|
if (effects) {
|
|
305
605
|
validarEfeitosDeclarados(effects.linhas, diagnosticos, `effects da superficie ${tipoSuperficie} ${nomeSuperficie}`);
|
|
306
606
|
}
|
|
607
|
+
validarExecucaoBloco(execucao, diagnosticos, `superficie ${tipoSuperficie} "${nomeSuperficie}"`);
|
|
307
608
|
validarVinculos(vinculos, diagnosticos, `${tipoSuperficie} ${nomeSuperficie}`);
|
|
609
|
+
const perfilSeguranca = coletarPerfilSegurancaDeclarado(superficie, effects, diagnosticos, `superficie ${tipoSuperficie} "${nomeSuperficie}"`);
|
|
610
|
+
emitirGuardrailsSeguranca(`Superficie ${tipoSuperficie} "${nomeSuperficie}"`, superficie.intervalo, perfilSeguranca, diagnosticos, {
|
|
611
|
+
publico: superficieEhPublica(superficie, tipoSuperficie),
|
|
612
|
+
sensivel: perfilSeguranca.efeitoPrivilegiado || perfilSeguranca.dadosSensiveis,
|
|
613
|
+
});
|
|
308
614
|
}
|
|
309
615
|
function recomporCaminhoRoute(campo) {
|
|
310
616
|
if (!campo) {
|
|
@@ -387,15 +693,23 @@ function validarEfeitosDeclarados(linhas, diagnosticos, contexto) {
|
|
|
387
693
|
for (const linha of linhas) {
|
|
388
694
|
const efeito = parsearEfeitoSemantico(linha.conteudo);
|
|
389
695
|
if (!efeito) {
|
|
390
|
-
diagnosticos.push(criarDiagnostico("SEM023", `Declaracao invalida de efeito em ${contexto}: "${linha.conteudo}".`, "erro", linha.intervalo, "Use o formato \"categoria alvo\" ou \"categoria alvo detalhe\",
|
|
696
|
+
diagnosticos.push(criarDiagnostico("SEM023", `Declaracao invalida de efeito em ${contexto}: "${linha.conteudo}".`, "erro", linha.intervalo, "Use o formato \"categoria alvo\" ou \"categoria alvo detalhe\", podendo adicionar criticidade=..., privilegio=... e isolamento=...."));
|
|
391
697
|
continue;
|
|
392
698
|
}
|
|
393
699
|
if (!ehCategoriaEfeitoSemantico(efeito.categoria)) {
|
|
394
|
-
diagnosticos.push(criarDiagnostico("SEM048", `Categoria de efeito "${efeito.categoria}" nao e suportada em ${contexto}.`, "erro", linha.intervalo, "Use
|
|
700
|
+
diagnosticos.push(criarDiagnostico("SEM048", `Categoria de efeito "${efeito.categoria}" nao e suportada em ${contexto}.`, "erro", linha.intervalo, "Use categorias como persistencia, consulta, evento, auditoria, db.write, queue.publish, fs.write, network.egress, secret.read ou shell.exec."));
|
|
395
701
|
}
|
|
396
702
|
if (efeito.criticidadeTexto && !ehCriticidadeEfeitoSemantico(efeito.criticidadeTexto)) {
|
|
397
703
|
diagnosticos.push(criarDiagnostico("SEM052", `Criticidade de efeito "${efeito.criticidadeTexto}" nao e suportada em ${contexto}.`, "erro", linha.intervalo, "Use apenas criticidade=baixa, criticidade=media, criticidade=alta ou criticidade=critica."));
|
|
398
704
|
}
|
|
705
|
+
if (efeito.privilegioTexto
|
|
706
|
+
&& !["leitura", "escrita", "publicacao", "execucao", "admin", "egress"].includes(efeito.privilegioTexto)) {
|
|
707
|
+
diagnosticos.push(criarDiagnostico("SEM092", `Privilegio de efeito "${efeito.privilegioTexto}" nao e suportado em ${contexto}.`, "erro", linha.intervalo, "Use privilegio=leitura, privilegio=escrita, privilegio=publicacao, privilegio=execucao, privilegio=admin ou privilegio=egress."));
|
|
708
|
+
}
|
|
709
|
+
if (efeito.isolamentoTexto
|
|
710
|
+
&& !["tenant", "processo", "host", "vps", "global"].includes(efeito.isolamentoTexto)) {
|
|
711
|
+
diagnosticos.push(criarDiagnostico("SEM093", `Isolamento de efeito "${efeito.isolamentoTexto}" nao e suportado em ${contexto}.`, "erro", linha.intervalo, "Use isolamento=tenant, isolamento=processo, isolamento=host, isolamento=vps ou isolamento=global."));
|
|
712
|
+
}
|
|
399
713
|
}
|
|
400
714
|
}
|
|
401
715
|
function validarState(state, tiposConhecidos, enumsConhecidos, diagnosticos) {
|
|
@@ -683,6 +997,11 @@ function validarRoute(route, tasksConhecidas, tarefasDetalhadas, diagnosticos) {
|
|
|
683
997
|
validarEfeitosDeclarados(effects.linhas, diagnosticos, `effects da route ${route.nome}`);
|
|
684
998
|
}
|
|
685
999
|
validarVinculos(route.vinculos, diagnosticos, `route ${route.nome}`);
|
|
1000
|
+
const perfilSeguranca = coletarPerfilSegurancaDeclarado(route.corpo, effects, diagnosticos, `route "${route.nome}"`);
|
|
1001
|
+
emitirGuardrailsSeguranca(`Route "${route.nome}"`, route.intervalo, perfilSeguranca, diagnosticos, {
|
|
1002
|
+
publico: extrairPerfilCompatibilidade(route.corpo, "publico") === "publico",
|
|
1003
|
+
sensivel: routeEhMutante(route) || perfilSeguranca.efeitoPrivilegiado || perfilSeguranca.dadosSensiveis,
|
|
1004
|
+
});
|
|
686
1005
|
if (task && !tasksConhecidas.has(task.valor)) {
|
|
687
1006
|
diagnosticos.push(criarDiagnostico("SEM018", `Route "${route.nome}" referencia task "${task.valor}" que nao existe.`, "erro", task.intervalo, "Ajuste o campo task da route para apontar para uma task declarada no modulo."));
|
|
688
1007
|
return;
|
|
@@ -691,6 +1010,82 @@ function validarRoute(route, tasksConhecidas, tarefasDetalhadas, diagnosticos) {
|
|
|
691
1010
|
validarContratoRoute(route, task.valor, tarefasDetalhadas, diagnosticos);
|
|
692
1011
|
}
|
|
693
1012
|
}
|
|
1013
|
+
function validarGuardrailsSeguranca(modulo, diagnosticos) {
|
|
1014
|
+
const superficies = coletarSuperficiesModulo(modulo);
|
|
1015
|
+
for (const task of modulo.tasks) {
|
|
1016
|
+
const motivos = new Set();
|
|
1017
|
+
const rotasPublicasAssociadas = modulo.routes.filter((route) => localizarCampo(route.corpo, "task", "tarefa")?.valor === task.nome
|
|
1018
|
+
&& extrairPerfilCompatibilidade(route.corpo, "publico") === "publico");
|
|
1019
|
+
const superficiesPublicasAssociadas = superficies.filter((item) => localizarCampo(item.superficie, "task", "tarefa")?.valor === task.nome
|
|
1020
|
+
&& superficieEhPublica(item.superficie, item.tipo));
|
|
1021
|
+
const perfilTask = coletarPerfilSegurancaDeclarado(task.corpo, task.effects, undefined, `task "${task.nome}"`);
|
|
1022
|
+
if (taskEhSensivel(task)) {
|
|
1023
|
+
motivos.add("criticidade operacional alta/critica ou efeito sensivel");
|
|
1024
|
+
}
|
|
1025
|
+
for (const route of rotasPublicasAssociadas) {
|
|
1026
|
+
motivos.add(`route publica "${route.nome}"`);
|
|
1027
|
+
}
|
|
1028
|
+
for (const item of superficiesPublicasAssociadas) {
|
|
1029
|
+
motivos.add(`superficie publica ${item.tipo} "${item.superficie.nome ?? item.tipo}"`);
|
|
1030
|
+
}
|
|
1031
|
+
const motivosOrdenados = [...motivos];
|
|
1032
|
+
if (motivosOrdenados.length === 0) {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
const perfisPublicos = [
|
|
1036
|
+
...rotasPublicasAssociadas.map((route) => coletarPerfilSegurancaDeclarado(route.corpo, localizarBloco(route.corpo, "effects"), undefined, `route "${route.nome}"`)),
|
|
1037
|
+
...superficiesPublicasAssociadas.map((item) => coletarPerfilSegurancaDeclarado(item.superficie, localizarBloco(item.superficie, "effects"), undefined, `superficie ${item.tipo} "${item.superficie.nome ?? item.tipo}"`)),
|
|
1038
|
+
];
|
|
1039
|
+
const perfilPublico = {
|
|
1040
|
+
auth: { ...perfilTask.auth, explicita: perfilTask.auth.explicita || perfisPublicos.some((perfil) => perfil.auth.explicita) },
|
|
1041
|
+
authz: {
|
|
1042
|
+
...perfilTask.authz,
|
|
1043
|
+
explicita: perfilTask.authz.explicita || perfisPublicos.some((perfil) => perfil.authz.explicita),
|
|
1044
|
+
papeis: [...new Set([perfilTask.authz.papeis, ...perfisPublicos.map((perfil) => perfil.authz.papeis)].flat())],
|
|
1045
|
+
escopos: [...new Set([perfilTask.authz.escopos, ...perfisPublicos.map((perfil) => perfil.authz.escopos)].flat())],
|
|
1046
|
+
},
|
|
1047
|
+
dados: {
|
|
1048
|
+
...perfilTask.dados,
|
|
1049
|
+
explicita: perfilTask.dados.explicita || perfisPublicos.some((perfil) => perfil.dados.explicita),
|
|
1050
|
+
campos: [...perfilTask.dados.campos, ...perfisPublicos.flatMap((perfil) => perfil.dados.campos)],
|
|
1051
|
+
},
|
|
1052
|
+
audit: { ...perfilTask.audit, explicita: perfilTask.audit.explicita || perfisPublicos.some((perfil) => perfil.audit.explicita) },
|
|
1053
|
+
segredos: {
|
|
1054
|
+
...perfilTask.segredos,
|
|
1055
|
+
explicita: perfilTask.segredos.explicita || perfisPublicos.some((perfil) => perfil.segredos.explicita),
|
|
1056
|
+
itens: [...perfilTask.segredos.itens, ...perfisPublicos.flatMap((perfil) => perfil.segredos.itens)],
|
|
1057
|
+
},
|
|
1058
|
+
forbidden: {
|
|
1059
|
+
...perfilTask.forbidden,
|
|
1060
|
+
explicita: perfilTask.forbidden.explicita || perfisPublicos.some((perfil) => perfil.forbidden.explicita),
|
|
1061
|
+
regras: [...new Set([perfilTask.forbidden.regras, ...perfisPublicos.map((perfil) => perfil.forbidden.regras)].flat())],
|
|
1062
|
+
},
|
|
1063
|
+
efeitoPrivilegiado: perfilTask.efeitoPrivilegiado || perfisPublicos.some((perfil) => perfil.efeitoPrivilegiado),
|
|
1064
|
+
dadosSensiveis: perfilTask.dadosSensiveis || perfisPublicos.some((perfil) => perfil.dadosSensiveis),
|
|
1065
|
+
exigeSegredos: perfilTask.exigeSegredos || perfisPublicos.some((perfil) => perfil.exigeSegredos),
|
|
1066
|
+
};
|
|
1067
|
+
if (rotasPublicasAssociadas.length > 0 || superficiesPublicasAssociadas.length > 0) {
|
|
1068
|
+
emitirGuardrailsSeguranca(`Task "${task.nome}" exposta publicamente`, task.intervalo, perfilPublico, diagnosticos, { publico: true, sensivel: false });
|
|
1069
|
+
}
|
|
1070
|
+
const resumoMotivos = motivosOrdenados.join(", ");
|
|
1071
|
+
if (!task.execucao) {
|
|
1072
|
+
diagnosticos.push(criarDiagnostico("SEM071", `Task "${task.nome}" exige execucao explicita para producao por causa de ${resumoMotivos}, mas ainda opera com execucao implicita.`, "aviso", task.intervalo, "Declare timeout, retry, compensacao, idempotencia e criticidade_operacional no bloco execucao da task."));
|
|
1073
|
+
}
|
|
1074
|
+
if (!taskTemRastreabilidade(task)) {
|
|
1075
|
+
diagnosticos.push(criarDiagnostico("SEM072", `Task "${task.nome}" exige rastreabilidade forte por causa de ${resumoMotivos}, mas ainda nao declara impl nem vinculos.`, "aviso", task.intervalo, "Adicione impl e/ou vinculos para apontar arquivo, simbolo, recurso ou superficie real do codigo vivo."));
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
for (const item of superficies) {
|
|
1079
|
+
if (!superficieEhPublica(item.superficie, item.tipo)) {
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const execucao = localizarBloco(item.superficie, "execucao");
|
|
1083
|
+
if (!execucao) {
|
|
1084
|
+
const nomeSuperficie = item.superficie.nome ?? item.tipo;
|
|
1085
|
+
diagnosticos.push(criarDiagnostico("SEM073", `Superficie publica ${item.tipo} "${nomeSuperficie}" deveria declarar execucao explicita para producao, mas ainda depende do padrao implicito.`, "aviso", item.superficie.intervalo, `Declare timeout, retry, compensacao e criticidade_operacional no proprio bloco ${item.tipo}.`));
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
694
1089
|
export function criarContextoLocal(modulo) {
|
|
695
1090
|
const simbolos = new Map();
|
|
696
1091
|
const tiposConhecidos = new Set(TIPOS_PRIMITIVOS);
|
|
@@ -829,6 +1224,11 @@ function validarTask(task, tiposConhecidos, statesConhecidos, diagnosticos) {
|
|
|
829
1224
|
}
|
|
830
1225
|
validarVinculos(task.vinculos, diagnosticos, `task ${task.nome}`);
|
|
831
1226
|
validarExecucao(task, diagnosticos);
|
|
1227
|
+
const perfilSeguranca = coletarPerfilSegurancaDeclarado(task.corpo, task.effects, diagnosticos, `task "${task.nome}"`);
|
|
1228
|
+
emitirGuardrailsSeguranca(`Task "${task.nome}"`, task.intervalo, perfilSeguranca, diagnosticos, {
|
|
1229
|
+
publico: false,
|
|
1230
|
+
sensivel: taskEhSensivel(task) || perfilSeguranca.efeitoPrivilegiado || perfilSeguranca.dadosSensiveis || perfilSeguranca.exigeSegredos,
|
|
1231
|
+
});
|
|
832
1232
|
validarImplementacoesTask(task, diagnosticos);
|
|
833
1233
|
if (task.tests) {
|
|
834
1234
|
for (const bloco of task.tests.blocos) {
|
|
@@ -1060,6 +1460,7 @@ export function analisarSemantica(modulo, opcoes = {}) {
|
|
|
1060
1460
|
for (const policy of modulo.policies) {
|
|
1061
1461
|
validarSuperficie(policy, "policy", tasksConhecidas, tiposConhecidos, diagnosticos);
|
|
1062
1462
|
}
|
|
1463
|
+
validarGuardrailsSeguranca(modulo, diagnosticos);
|
|
1063
1464
|
const assinaturasRoute = new Map();
|
|
1064
1465
|
for (const route of modulo.routes) {
|
|
1065
1466
|
const metodo = (localizarCampo(route.corpo, "metodo")?.valor ?? "").toUpperCase();
|