
Todos os validadores OSCAL afirmam «validar OSCAL». Quase nenhum consegue dizer-lhe o que essa frase significa.
Os modelos OSCAL do NIST são escritos em Metaschema: oito módulos raiz mais importações partilhadas, milhares de linhas de XML que definem não só a forma dos documentos, mas também a sua semântica: vocabulários de valores permitidos, regras de unicidade, índices de referências cruzadas, requisitos de cardinalidade. Um JSON Schema capta a forma. É na semântica que os documentos de conformidade realmente falham, e é aí que «validamos OSCAL» se transforma discretamente em «validamos parte do OSCAL, não sabemos bem qual».
Nós queríamos saber exatamente qual. Por isso, construímos o nosso validador como se audita um sistema: partir das fontes, enumerar tudo e contabilizar cada item, um a um.
A primeira decisão foi a mais importante: as fontes do NIST são a autoridade, não a implementação de referência. Fixámos o usnistgov/OSCAL no commit 21403b4a… (v1.2.2), guardámos em cache cada byte do Metaschema com hashes SHA-256 e tratámos a veterana OSCAL CLI em Java como aquilo que realmente é: um termo de comparação a contrainterrogar, não uma verdade a copiar. (Para começar, incorpora bindings do OSCAL 1.2.1: consegue processar documentos 1.2.2, mas não fala 1.2.2.)
Depois, fizemos um inventário léxico de todas as ocorrências de restrições nas fontes fixadas. Não de tipos de restrições, mas de ocorrências, identificadas por ficheiro e linha, para que duas restrições que por acaso partilhem um ID nunca se fundam numa só. A contagem chegou a 348.
Para cada ocorrência, a nossa build tem de manter exatamente um de três veredictos, imposto por um gerador que recalcula o registo a cada execução:
Por contabilizar: zero.
Foi nesse último grupo, o das exclusões, que as coisas se tornaram interessantes.
Quando se verificam 348 restrições uma a uma, encontram-se coisas. Três das nossas exclusões são defeitos nas próprias fontes do NIST:
oscal_mapping-common_metaschema.xml restringe uma flag definida cinco linhas antes e que depois nunca é referenciada por nada. Nenhum documento que possa existir contém esta flag. A restrição é impossível de satisfazer.inventory-item aplica-se apenas quando @type é software, hardware ou service, mas inventory-item não declara nenhuma flag type. (O seu vizinho, asset-id, está ele próprio comentado na fonte.) O predicado nunca pode corresponder.with-child-controls em perfis. Uma pessoa que lê a fonte vê-as; o esquema compilado nunca as aplica. As três constatações foram reportadas a montante: usnistgov/OSCAL#2254, #2255, #2256.E a auditoria funcionou nos dois sentidos: o nosso próprio avaliador tinha um bug em que as restrições dirigidas a flags (como os valores permitidos de action/@type) eram compiladas no inventário, mas silenciosamente nunca disparavam. Não o encontrámos por sorte: a barreira de fecho recusou-se a aceitar evidências para essas duas ocorrências, que é precisamente o modo de falha que todo o sistema foi concebido para expor.
Afirmar que «a implementação de referência deixa escapar coisas» apenas com base na inspeção das fontes é exatamente o tipo de afirmação não provada que este projeto existe para eliminar. Por isso, descarregámos a OSCAL CLI 3.2.0 em Java do Maven Central, verificámos o hash e executámos ambos os validadores sobre uma família de fixtures cujos documentos de base passam sem erros em ambos os lados, para que cada divergência seja atribuível a uma única alteração injetada.
O OSCAL 1.2.2 alterou exatamente duas restrições para além das atualizações de versão (ambas no modelo SSP). A CLI Java, que incorpora bindings 1.2.1, está do lado errado de todas as consequências observáveis:
rel="validation" pendente viola o 1.2.2. Java: válido, exit 0.rel="validated-by" pendente está correto segundo o 1.2.2 (a restrição foi eliminada). Java: inválido.responsible-role sem o party-uuid opcional está correto segundo o 1.2.2; o NIST acrescentou o predicado precisamente para corrigir isto. Java dá um erro com, literalmente, Key reference [null] not found.Depois, um caso que nada tem a ver com diferenças de versão: um catálogo cujo action nos metadados declara "type": "invented-type", proibido na linha 877 do Metaschema de metadados tanto no 1.2.2 como nas fontes 1.2.1 que a CLI Java incorpora. A CLI Java declara-o válido, em JSON e em XML. A restrição tem como alvo uma flag; restrições dirigidas a flags que silenciosamente não disparam são precisamente a classe de bug que a nossa barreira de completude detetou no nosso próprio avaliador. A implementação de referência tem a mesma classe de bug, e nenhuma barreira de fecho para a detetar. Reportámo-lo a montante como metaschema-framework/oscal-cli#279. (Mapeámos a classe de forma mecânica: exatamente 2 das 200 ocorrências de allowed-values têm flags como alvo, e o único membro de nível erro é o que testámos em execução real. Não fica nenhum resto por testar.)
E o momento de honestidade, porque a execução funcionou nos dois sentidos: o nosso validador rejeitou o próprio catálogo SP 800-53 rev5 do NIST. Vinte erros, todos de um só defeito: o nosso avaliador de índices tratava um campo de chave opcional em falta como uma violação, quando a especificação Metaschema diz que se trata de uma chave nula. A CLI Java aceitou o ficheiro; nós estávamos errados; corrigimos no mesmo dia e o 800-53 valida sem erros. A execução apanhou mais tarde um segundo caso: continuávamos a aplicar o vocabulário de tipos de ação do OSCAL mesmo quando action/@system declarava um URI personalizado, que é exatamente a segmentação por organização que as próprias observações do NIST preveem. Também corrigido. Um harness diferencial que só encontra os bugs do outro lado não é um harness, é marketing.
(Também na categoria «ambos os lados»: nenhum dos resolvedores validava semanticamente o seu próprio resultado. O resolve-profile da CLI Java escreve sem hesitar um catálogo que o próprio validate da CLI Java depois rejeita, e o nosso fazia o mesmo, com barreira de esquema, mas sem barreira semântica. O nosso agora recusa-se a escrever resoluções semanticamente inválidas; a CLI Java continua sem o conseguir, porque fechar essa lacuna exige uma camada semântica em que se confie.)
A segunda coisa que falta nas ferramentas existentes: os documentos OSCAL não vivem sozinhos. Um resultado de avaliação importa um plano de avaliação, que importa um SSP, que importa um perfil, que é resolvido face a catálogos. A documentação do modelo está cheia de relações que nenhum validador de ficheiro único consegue verificar: «o alvo desta constatação tem de resolver para uma declaração na baseline», «este POA&M tem de identificar o seu sistema».
Por isso, construímos uma validação entre documentos que resolve efetivamente essas ligações. Peça-lhe para verificar um SSP e ela resolve a baseline importada através de todo o pipeline de resolução de perfis (segundo a especificação em rascunho) até aos catálogos, calcula o conjunto de controlos selecionado e diz-lhe:
{
"ruleId": "SSP-003.b",
"severity": "warning",
"path": "/…/implemented-requirements/1/control-id",
"message": "implemented control 'ac-99' is not supplied by the resolved import-profile 'file:///…/baseline.json'"
}
Com uma regra de conceção crucial: estas verificações de relações são interpretações de texto documentado, e tratamo-las como tal: desativadas por omissão, avisos e não erros, o âmbito de resolução de cada campo fixado à respetiva linha de origem e tudo o que seja meramente inferível excluído por nome. Quando a norma é ambígua, um validador não deve fazer bluff. (O nosso exemplo preferido: a especificação do POA&M diz que é obrigatória uma importação de SSP ou um system-id, «Both may be present.» Por isso, só avisamos quando nenhum existe. Nada de um ou-exclusivo inventado.)
Porque os documentos vivem em navegadores, funções serverless, CLIs e bases de dados, e queríamos um único validador em todos eles, e não um sidecar JVM ao lado de cada um. Os validadores são módulos Ajv standalone pré-compilados: JavaScript gerado simples, zero compilação de esquemas em tempo de execução, resultado estável ao nível dos bytes. O mesmo código que alimenta as nossas superfícies de validação corre sem alterações no separador do seu navegador e na nossa CLI. E o determinismo não é apenas uma comodidade operacional: é o que torna possíveis as evidências fixadas por hash.
import { createDiagnosticsOscalProcessor } from "@secani/oscal/diagnostics";
const result = createDiagnosticsOscalProcessor({ maxIssues: 50 }).validate(doc);
validate --resolve-imports --interpretive all)O relatório técnico completo (modelo de autoridade, fecho de completude, a tabela de comprovativos diferenciais e o que deliberadamente não afirmamos) está na documentação do validador. O validador está em staging privado antes de um lançamento open source; a API de validação alojada expõe hoje o nível de esquema, e a camada semântica virá a seguir.
A Secani liga âmbitos, evidências, tarefas e agentes de IA num espaço de trabalho partilhado.
O OLIR fornece conteúdo de mapeamento e governação. O OSCAL fornece a estrutura legível por máquina para utilizar esses mapeamentos na análise de lacunas, na reutilização de evidências e nos fluxos de impacto de alterações.
O OSCAL transforma documentos de conformidade em dados estruturados: oito modelos de documentos, três formatos e um ecossistema que se está a tornar a norma da regulação.
Com o RFC-0024 e as Consolidated Rules 2026, o FedRAMP torna obrigatórios os dados de autorização estruturados. Os prazos são escalonados, mas a direção é inequívoca.