Porque é que o seu SBOM falha na validação PURL para pacotes npm com scope
Se está a gerar SBOMs CycloneDX para projetos Node.js, pode ter visto o seu SBOM passar na validação de esquema mas falhar nas verificações de elementos mínimos NTIA com este erro:
O componente está lá. A versão está lá. O PURL parece correto: pkg:npm/@types/node@20.11.5. Então qual é o problema?
O problema: dois sinais @
Um Package URL (PURL) usa o carácter @ como separador de versão. O formato é:
pkg:<type>/<namespace>/<n>@<version>
Quando escreve pkg:npm/@types/node@20.11.5, existem dois caracteres @ na string. Um parser PURL precisa de saber qual deles separa a versão. É @types/node na versão 20.11.5, ou é @types/node@20.11.5 sem versão alguma?
Esta ambiguidade faz com que os parsers PURL rejeitem o identificador completamente. O seu componente efetivamente não tem nenhum PURL válido, o que significa que falha no requisito NTIA de "identificador único".
A especificação é explícita
A especificação PURL para pacotes npm aborda este caso diretamente:
"O prefixo @ do scope npm é sempre codificado em percentagem, como era nos primeiros tempos dos scopes npm."
A codificação correta para pacotes npm com scope é:
| Pacote | ❌ PURL incorreto | ✅ PURL correto |
|---|---|---|
@types/node@20.11.5 |
pkg:npm/@types/node@20.11.5 |
pkg:npm/%40types/node@20.11.5 |
@angular/core@17.1.0 |
pkg:npm/@angular/core@17.1.0 |
pkg:npm/%40angular/core@17.1.0 |
@vue/reactivity@3.4.1 |
pkg:npm/@vue/reactivity@3.4.1 |
pkg:npm/%40vue/reactivity@3.4.1 |
O @ do scope torna-se %40, deixando exatamente um único @ literal em todo o PURL — inequivocamente o separador de versão.
Pacotes sem scope como express@4.18.2 não são afetados: pkg:npm/express@4.18.2 já está correto.
Porque é que este erro é tão comum
É um erro natural. Quando olha para @types/node, o prefixo @ faz parte da forma como o npm apresenta e referencia o pacote. Todos os programadores escrevem npm install @types/node — com o @ literal. Codificá-lo parece errado.
Aumentando a confusão, alguns exemplos de PURL que circulam na Internet (e até em alguns ficheiros de exemplo CycloneDX) mostram o sinal @ não codificado. Os exemplos canónicos da especificação no GitHub mostram pkg:npm/@angular/animation@12.3.1 — mas o texto normativo desse mesmo documento diz que o @ deve ser codificado. O texto da especificação prevalece.
A correção
Se está a desenvolver ferramentas SBOM, a correção é simples. Ao construir um PURL para um pacote npm com scope, codifique o @ inicial como %40:
function buildPurl(name: string, version: string): string {
if (name.startsWith('@')) {
return `pkg:npm/%40${name.slice(1)}@${version}`;
}
return `pkg:npm/${name}@${version}`;
}
Um erro comum é usar um name.replace('/', '%2F') genérico ou encodeURIComponent no nome completo. Não faça isso também — o / entre namespace e nome é um separador estrutural PURL e deve permanecer não codificado.
Uma forma rápida de verificar
Após corrigir a sua geração de PURL, pode validar o seu SBOM com:
- FOSSA NTIA SBOM Validator
- O parser de referência
purl-specno GitHub
Ambos detetarão PURLs inválidos antes de se tornarem um problema de conformidade.
Após a correção
Com o prefixo de scope @ corretamente codificado em percentagem, os seus pacotes npm com scope terão PURLs válidos e a verificação de identificador NTIA passa sem problemas:
Como o Verimu lida com isto
O gerador de SBOM do Verimu codifica corretamente os PURLs npm com scope segundo a especificação PURL — %40 para o prefixo de scope, / não codificado para o separador de namespace. Cada SBOM que geramos passa na validação de elementos mínimos NTIA desde o primeiro momento.
Se está a trabalhar na conformidade CRA para acesso ao mercado europeu, ou simplesmente quer SBOMs que não falhem na validação em produção, já resolvemos estes casos especiais para que não tenha de o fazer.
Comece grátis → ou marque uma demo para ver o Verimu em ação.