Por qué tu SBOM falla en la validación PURL para paquetes npm con scope
Si estás generando SBOMs CycloneDX para proyectos Node.js, es posible que hayas visto tu SBOM superar la validación del esquema pero fallar en las verificaciones de elementos mínimos NTIA con este error:
El componente está ahí. La versión está ahí. El PURL parece correcto: pkg:npm/@types/node@20.11.5. Entonces, ¿cuál es el problema?
El problema: dos signos @
Un Package URL (PURL) usa el carácter @ como separador de versión. El formato es:
pkg:<type>/<namespace>/<n>@<version>
Cuando escribes pkg:npm/@types/node@20.11.5, hay dos caracteres @ en la cadena. Un parser PURL necesita saber cuál separa la versión. ¿Es @types/node en la versión 20.11.5, o es @types/node@20.11.5 sin versión alguna?
Esta ambigüedad hace que los parsers PURL rechacen el identificador por completo. Tu componente efectivamente no tiene ningún PURL válido, lo que significa que no cumple el requisito NTIA de "identificador único".
La especificación es explícita
La especificación PURL para paquetes npm aborda este caso directamente:
"El prefijo @ del scope npm siempre se codifica en porcentaje, como se hacía en los primeros días de los scopes npm."
La codificación correcta para paquetes npm con scope es:
| Paquete | ❌ PURL incorrecto | ✅ PURL correcto |
|---|---|---|
@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 |
El @ del scope se convierte en %40, dejando exactamente un solo @ literal en todo el PURL — inequívocamente el separador de versión.
Los paquetes sin scope como express@4.18.2 no se ven afectados: pkg:npm/express@4.18.2 ya es correcto.
Por qué este error es tan común
Es un error natural. Cuando miras @types/node, el prefijo @ forma parte de cómo npm muestra y referencia el paquete. Todos los desarrolladores escriben npm install @types/node — con el @ literal. Codificarlo parece incorrecto.
Añadiendo más confusión, algunos ejemplos de PURL que circulan por Internet (e incluso en algunos archivos de ejemplo CycloneDX) muestran el signo @ sin codificar. Los ejemplos canónicos de la especificación en GitHub muestran pkg:npm/@angular/animation@12.3.1 — pero el texto normativo de ese mismo documento dice que el @ debe codificarse. El texto de la especificación prevalece.
La corrección
Si estás desarrollando herramientas SBOM, la corrección es sencilla. Al construir un PURL para un paquete npm con scope, codifica el @ 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}`;
}
Un error común es usar un name.replace('/', '%2F') genérico o encodeURIComponent sobre el nombre completo. No hagas eso tampoco — el / entre namespace y nombre es un separador estructural PURL y debe permanecer sin codificar.
Una forma rápida de verificar
Después de corregir tu generación de PURL, puedes validar tu SBOM con:
- FOSSA NTIA SBOM Validator
- El parser de referencia
purl-specen GitHub
Ambos detectarán PURLs inválidos antes de que se conviertan en un problema de cumplimiento.
Después de la corrección
Con el prefijo de scope @ correctamente codificado en porcentaje, tus paquetes npm con scope tendrán PURLs válidos y la verificación de identificador NTIA se supera sin problemas:
Cómo Verimu gestiona esto
El generador de SBOM de Verimu codifica correctamente los PURLs npm con scope según la especificación PURL — %40 para el prefijo de scope, / sin codificar para el separador de namespace. Cada SBOM que generamos supera la validación de elementos mínimos NTIA desde el primer momento.
Si estás trabajando en el cumplimiento CRA para acceder al mercado europeo, o simplemente quieres SBOMs que no fallen en la validación en producción, ya hemos resuelto estos casos especiales para que tú no tengas que hacerlo.
Empieza gratis → o reserva una demo para ver Verimu en acción.