Правила составления PURL

Package URL (PURL) — идентификатор пакета, который содержит его экосистему, имя, версию и дополнительные признаки. CodeScoring использует PURL при сопоставлении компонентов с данными о пакетах и уязвимостях.

Эти рекомендации пригодятся при подготовке собственного SBOM, проверке отчёта стороннего инструмента и разборе неизвестных компонентов. Начните с порядка проверки, затем перейдите к правилам нужной экосистемы.

Структура PURL

Общая структура определена в стандарте ECMA-427:

pkg:type/namespace/name@version?qualifiers#subpath

Не все части обязательны. Например, PURL артефакта Maven выглядит так:

pkg:maven/org.apache.logging.log4j/log4j-core@2.17.2
ЧастьОбязательностьЗначение
pkg:Обязательно.Схема идентификатора.
typeОбязательно.Тип пакета: в примере maven. Определяется экосистемой и происхождением пакета.
namespaceЗависит от типа.Пространство имён: в примере org.apache.logging.log4j. Формат зависит от типа.
nameОбязательно.Имя пакета: в примере log4j-core.
versionНеобязательно по общему синтаксису; для анализа укажите фактическую версию.Версия пакета после @: в примере 2.17.2.
qualifiersЗависит от типа и параметра.Дополнительные параметры после ?, например arch=amd64&distro=debian-12.
subpathНеобязательно.Относительный путь внутри пакета после #, если он нужен для идентификации компонента.

Параметры (qualifiers) уточняют характеристики и происхождение пакета. Например, arch обозначает архитектуру, distro — дистрибутив и его выпуск, upstream — исходный пакет, repository_url — репозиторий пакетов. Допустимые параметры и формат их значений зависят от типа PURL. Для некоторых типов отдельные параметры обязательны: например, tag_id у SWID.

Выбирайте тип по происхождению конкретного пакета. Библиотека на одном языке может распространяться через разные пакетные экосистемы: например, Python-библиотека из PyPI имеет тип pypi, из Conda — conda, а пакет дистрибутива Debian — deb.

Источник пакетаТип PURL
PyPIpypi
Condaconda
Репозиторий пакетов Debian или Ubuntudeb
Репозиторий пакетов RPM-дистрибутиваrpm
Репозиторий пакетов Alpineapk
Maven-репозиторий, в том числе для зависимостей Gradle и sbtmaven

Полный перечень приведён в разделе «Типы PURL и компонентов».

Версия для анализа

Общий синтаксис PURL допускает отсутствие версии. Для анализа конкретной зависимости укажите её фактическую версию: диапазон >=1.0, метка latest или версия среды выполнения не заменяют версию установленного пакета.

Порядок проверки

  1. Найдите исходный PURL компонента в SBOM или запросе интеграции. Сверьте его с полями имени и версии этого же компонента.
  2. Установите происхождение пакета: реестр, системный репозиторий или репозиторий исходного кода. Выберите соответствующий тип PURL.
  3. Подтвердите имя и точную версию по lock-файлу, метаданным установленного пакета или записи в исходном реестре. В манифесте может быть указан только диапазон допустимых версий.
  4. Проверьте пространство имён, кодирование и параметры по правилам выбранного типа. Для системных пакетов дополнительно проверьте дистрибутив и сведения об исходном пакете.
  5. Исправьте метаданные или настройки генератора и заново сформируйте SBOM. Если правите SBOM вручную, сохраните согласованность сведений о компоненте и ссылок на него в графе зависимостей.
  6. Повторите сканирование SBOM или импорт в платформу. Проверьте, какой пакет и какая версия определены в результате.

Успешная проверка схемы SBOM не подтверждает правильность координат пакета. Отсутствие найденных уязвимостей также не служит проверкой корректности PURL.

Кодирование и параметры

  • Сохраняйте порядок частей: версия, затем параметры, затем путь внутри пакета. Не добавляйте пустые @, ? или #.
  • Кодируйте специальные символы внутри значений по правилам PURL. Например, scope npm @angular записывается как %40angular, а знак + в версии — как %2B. Не кодируйте всю строку целиком и не кодируйте %40 повторно в %2540.
  • Записывайте параметры как key=value, разделяя их символом &. Используйте ключи в нижнем регистре, без повторов и пустых значений; в канонической записи ключи отсортированы.
  • Меняйте регистр имени и версии только по правилам экосистемы. Универсальное приведение всех PURL к нижнему регистру может изменить идентификатор.
  • Добавляйте только подтверждённые параметры. repository_url обозначает репозиторий пакетов, а не локальный путь или произвольную ссылку на проект.
  • Используйте subpath для пути внутри пакета. Абсолютный путь рабочей директории, сегменты . и .. в нём недопустимы.

При разработке генератора используйте библиотеку Package URL, которая собирает идентификатор из отдельных полей и учитывает их кодирование.

Правила по экосистемам

Ниже приведены правила для пакетных экосистем и других типов, принимаемых при анализе SBOM. Поддержка типа PURL и наличие сведений о конкретном пакете в базе CodeScoring — разные условия.

Maven: Java, Kotlin и Scala

pkg:maven/org.apache.logging.log4j/log4j-core@2.17.2

Используйте groupId как обязательный namespace, artifactId как name, а разрешённую версию артефакта как version. Координаты можно проверить в POM и дереве зависимостей Maven, Gradle или sbt.

Регистр координат

Сохраняйте исходный регистр groupId, artifactId и версии. Например, не заменяйте суффикс Final на final и не удаляйте его. Копируйте координаты из метаданных артефакта.

Расширение файла .jar и classifier не входят в версию. Для вариантов артефакта спецификация Maven предусматривает параметры type и classifier:

pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources

npm: JavaScript и TypeScript

pkg:npm/lodash@4.17.21
pkg:npm/%40angular/core@12.0.0

Имя и разрешённую версию берите из lock-файла или метаданных установленного пакета. Для scoped-пакета namespace содержит scope с закодированным @; для остальных пакетов пространство имён отсутствует. Не включайте node_modules в PURL и не используйте alias зависимости вместо имени исходного пакета.

Определение npm сохраняет чувствительность имён к регистру для исторических пакетов. Не меняйте регистр произвольно.

PyPI: Python

pkg:pypi/django@4.2.0
pkg:pypi/django-allauth@0.54.0

Используйте имя дистрибутива Python из метаданных пакета, а не имя импортируемого модуля или wheel-файла. namespace отсутствует. При нормализации имени по правилам Python Packaging регистр становится нижним, а последовательности -, _ и . заменяются одним дефисом.

Версия должна относиться к пакету, а не к интерпретатору Python. Не отбрасывайте признаки предварительного, исправленного или локального выпуска. Формат типа описан в определении PyPI.

Go Modules

pkg:golang/github.com/gorilla/mux@v1.8.0

Для зависимости Go Modules используйте полный путь модуля и его разрешённую версию. В примере namespace — github.com/gorilla, name — mux. Если путь модуля оканчивается на /v2 или другой суффикс основной версии, сохраните его. Псевдоверсию берите целиком из метаданных модуля.

Сверяйте координаты с go.mod, списком разрешённых модулей или сведениями о сборке. При replace учитывайте фактически использованный источник. Наличие записи в go.sum само по себе не доказывает, что эта версия вошла в сборку.

Путь vendor/, путь вложенного Go-пакета и версия toolchain вида go1.25.9 не заменяют координаты модуля. Нельзя восстановить правильный PURL простым удалением части пути: сначала определите границу модуля и его версию по исходным данным. Спецификация Go отдельно отмечает сложность различения модуля и пакета по одному PURL.

NuGet: .NET

pkg:nuget/Newtonsoft.Json@13.0.3

В name указывайте полный идентификатор пакета из .nuspec или lock-файла. Точки в имени не отделяют namespace: у NuGet PURL его нет. Используйте разрешённую версию пакета, сохраняя prerelease-часть; версия .NET и имя сборки DLL для этого не подходят.

Composer, RubyGems и Cargo

Composer (PHP) — обязательный vendor находится в namespace, имя пакета — в name. Оба значения записываются в нижнем регистре. Проверяйте версию по composer.lock.

pkg:composer/laravel/laravel@5.5.0

RubyGems (Ruby) — namespace отсутствует; используйте имя gem и версию из Gemfile.lock или метаданных gem. Вариант платформы можно указать в platform, не добавляя его к версии.

pkg:gem/jruby-launcher@1.1.2?platform=java

Cargo (Rust) — namespace отсутствует. Используйте имя пакета и версию из Cargo.lock, учитывая источник зависимости. Не подменяйте имя пакета alias из манифеста или именем модуля в исходном коде.

pkg:cargo/structopt@0.3.11

Определения типов: Composer, RubyGems, Cargo.

CocoaPods и Swift Package Manager

CocoaPods — используйте имя pod с исходным регистром и версию из Podfile.lock. namespace отсутствует, а subspec при наличии записывается после #.

pkg:cocoapods/AFNetworking@4.0.1
pkg:cocoapods/ShareKit@2.0#Twitter

Swift Package Manager — обязательный namespace содержит хост и владельца репозитория, name — имя репозитория. Сверяйте версию или ревизию с Package.resolved; не используйте отображаемое имя продукта вместо координат зависимости.

pkg:swift/github.com/Alamofire/Alamofire@5.4.3

Определения типов: CocoaPods, Swift.

CRAN, Hex и Pub

CRAN (R) — используйте имя пакета с исходным регистром и полную версию, включая суффикс после дефиса. namespace отсутствует. Источники координат — DESCRIPTION установленного пакета и renv.lock.

pkg:cran/rJava@1.0-4

Hex (Erlang, Elixir, Gleam) — имя пакета записывается в нижнем регистре. Необязательный namespace обозначает организацию для приватных пакетов. Используйте имя Hex-пакета и разрешённую версию из lock-файла, учитывая возможное отличие имени приложения от имени пакета.

pkg:hex/jason@1.1.2

Pub (Dart, Flutter) — используйте имя пакета и версию из pubspec.lock. namespace отсутствует; имя содержит строчные латинские буквы, цифры и подчёркивания.

pkg:pub/characters@1.2.0

Определения типов: CRAN, Hex, Pub.

Conan и Conda

Conan (C/C++) — разделяйте имя, версию и признаки варианта пакета. В ссылке Conan name/version@user/channel значения user и channel соответствуют одноимённым параметрам PURL. Если известны ревизии рецепта и пакета, укажите rrev и prev; сведения о сборке берите из метаданных Conan.

pkg:conan/openssl@3.0.3

Conda — namespace отсутствует. Имя и версия относятся к Conda-пакету, даже если он содержит библиотеку Python. Для конкретной сборки сохраните подтверждённые build, channel, subdir и type.

pkg:conda/absl-py@0.4.1?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2

Определения типов: Conan, Conda.

Системные пакеты: DEB, RPM и APK

Для системного пакета важна версия, выпущенная дистрибутивом, со всеми его суффиксами и исправлениями. Версия исходного проекта, например OpenSSL, не заменяет версию пакета Debian, RHEL или Alpine.

В namespace указывайте поставщика пакета, например debian, ubuntu, fedora или alpine, в нижнем регистре. arch берите из метаданных пакета: значения amd64 и x86_64 нельзя произвольно заменять друг другом между экосистемами.

DEB — сохраняйте полную версию, включая эпоху перед двоеточием и ревизию пакета. В примере эпоха 1, а upstream=shadow указывает имя исходного пакета:

pkg:deb/debian/login@1%3A4.13%2Bdfsg1-1?arch=amd64&distro=debian-12&upstream=shadow

RPM — используйте версию вместе с release: version-release. Если в метаданных есть эпоха, передайте её параметром epoch. Не включайте расширение .rpm в версию и архитектуру — в имя пакета.

pkg:rpm/fedora/curl@7.50.3-1.fc25?arch=i386&distro=fedora-25

APK — сохраняйте ревизию вида -r0 в версии. Тип apk относится к пакетам Alpine и других APK-based дистрибутивов, а не к Android-приложениям.

pkg:apk/alpine/curl@7.83.0-r0?arch=x86

Дистрибутив и исходный пакет

name содержит имя бинарного пакета, а upstream — сведения об исходном пакете, из которого он собран. Имена этих пакетов могут совпадать.

Для сопоставления системных пакетов CodeScoring учитывает дополнительные параметры:

ПараметрИсточник значения
distroДистрибутив и его выпуск в анализируемом окружении: например, debian-12, ubuntu-22.04, alpine-3.18, rhel-8, fedora-40.
upstreamИсходный пакет, из которого собран бинарный: метаданные Source в DEB, Source RPM в RPM или origin в APK.

Проверяйте distro по окружению, из которого получен пакет. При сканировании контейнера это дистрибутив контейнера, а не хоста. Не достраивайте номер выпуска по единому шаблону: у Debian, Ubuntu и RPM-дистрибутивов разные схемы версий.

Если имя исходного пакета отличается от имени бинарного, сохраните его в upstream. Если отличается и версия, передайте подтверждённые имя и версию в форме name@version с кодированием специальных символов. Не выводите имя исходного пакета простым удалением суффикса из имени бинарного. Если генератор уже передал Source RPM в виде имени файла, сверяйте его с метаданными; такой формат сам по себе не доказывает ошибку PURL.

Определения типов: DEB, RPM, APK.

Контейнерные образы: OCI и Docker

Для OCI namespace отсутствует, а name содержит последний сегмент имени репозитория. В версии указывается digest артефакта, полный путь к репозиторию передаётся в repository_url, тег — в tag.

Шаблон ниже требует замены <digest> на полный фактический SHA-256 digest:

pkg:oci/debian@sha256%3A<digest>?repository_url=docker.io%2Flibrary%2Fdebian&tag=bookworm

У типа Docker пространство имён может содержать реестр или владельца образа, а версией может быть тег или SHA-256 идентификатор. Для воспроизводимой идентификации предпочтителен digest: тег может указывать на другой образ со временем.

PURL образа описывает образ целиком. Для его содержимого нужны отдельные PURL установленных пакетов.

Репозитории, модели и произвольные артефакты

У этих типов отличаются источники координат. Используйте их, когда они соответствуют происхождению компонента.

ТипПравила
githubnamespace — владелец, name — репозиторий; оба значения в нижнем регистре. Версия — commit или тег.
huggingfacenamespace — пользователь или организация, name — репозиторий модели с исходным регистром. Версия — хеш commit нужной ревизии.
mlflownamespace отсутствует. Имя и версия берутся из Model Registry, repository_url указывает MLflow tracking URI. Правила регистра имени зависят от сервера.
swidИмя и версия берутся из SoftwareIdentity SWID-тега. Обязателен непустой параметр tag_id; сведения о создателе ПО могут задавать namespace.
genericДля артефактов, которым не подходит специализированный тип. Укажите фактические имя и версию, а происхождение и контрольную сумму сохраните в метаданных компонента или соответствующих параметрах.

Если пакет получен из Maven, PyPI или системного репозитория, сохраняйте его пакетную экосистему. Наличие исходного кода на GitHub не делает пакет типом github, а замена неизвестного PURL на generic не восстанавливает недостающие координаты.

Нормализация в CodeScoring

Перед сопоставлением CodeScoring нормализует PURL по правилам экосистем. Поэтому запись в результатах может отличаться от исходной: например, регистром, представлением версии или набором параметров.

Не подгоняйте входной PURL под внутреннюю нормализованную запись удалением сведений о пакете. Допустимые по спецификации classifier у Maven или platform у RubyGems могут не участвовать в сопоставлении, но это не делает их ошибочными. Для Cargo используйте имя из реестра и lock-файла, даже если нормализатор меняет дефисы на подчёркивания.

Нормализация не восстанавливает достоверную версию, отсутствующий groupId или происхождение пакета. Эти сведения нужно получить из источника.

Типичные ошибки

НаблюдениеЧто проверить
В PURL Maven нет groupId или в версию попали .jar и classifier.Координаты артефакта в POM и дереве зависимостей.
В PURL Go присутствуют vendor/ и версия toolchain.Путь и версию фактически использованного модуля.
Версия DEB потеряла эпоху, а версия RPM — release.Полную версию в метаданных установленного пакета.
У системного пакета указан другой дистрибутив.Происхождение пакета и выпуск ОС в анализируемом окружении.
Вместо имени пакета указаны import, alias или имя файла.Имя, под которым пакет опубликован в своей экосистеме.
Вместо версии указаны диапазон, latest или произвольное 0.0.0.Разрешённую версию из lock-файла или метаданных. Неизвестную версию нельзя заменять вымышленной.
После @ указана архитектура вместо версии пакета.Укажите фактическую версию пакета. Для DEB, RPM и APK архитектура передаётся параметром arch.
Префикс v в версии добавлен или удалён без учёта правил экосистемы.Сверьте версию с метаданными пакета и правилами экосистемы. У Go Modules префикс v сохраняется.
Вместо версии пакета указана версия среды выполнения, приложения или сборки.Сверьте версию именно этого пакета по lock-файлу или метаданным установленного компонента.

Примеры ошибок

Исправляйте PURL по метаданным своей зависимости. Имена и версии в примерах ниже относятся к конкретным пакетам и не предназначены для замены координат в вашем SBOM.

ОшибкаПример и исправление
Неполная версия DEBНеверно: pkg:deb/debian/libudev1@247.3-7?arch=amd64&distro=debian-11&upstream=systemd
Верно: pkg:deb/debian/libudev1@247.3-7%2Bdeb11u4?arch=amd64&distro=debian-11&upstream=systemd
Если установлен пакет версии 247.3-7+deb11u4, суффикс +deb11u4 должен сохраняться. Символ + кодируется как %2B.
Несуществующая версия NuGetНеверно: pkg:nuget/system.reflection.primitives@6.0.0
У пакета System.Reflection.Primitives нет выпуска 6.0.0. Укажите фактическую версию из .nuspec или lock-файла и проверьте её наличие в исходном реестре. Версия .NET не заменяет версию NuGet-пакета.
Вместо groupId указан artifactIdНеверно: pkg:maven/sketches-java/sketches-java@0.8.2
Верно: pkg:maven/com.datadoghq/sketches-java@0.8.2
В POM этого артефакта указан groupId со значением com.datadoghq.
Изменён регистр имени Maven-артефактаНеверно: pkg:maven/com.zaxxer/hikaricp@7.0.2
Верно: pkg:maven/com.zaxxer/HikariCP@7.0.2
Сохраняйте регистр artifactId из POM: HikariCP.
Путь Go-пакета и версия toolchain вместо координат модуляНеверно: pkg:golang/vendor/golang.org/x/text/unicode/bidi@go1.25.9
Верно для модуля golang.org/x/text версии v0.9.0: pkg:golang/golang.org/x/text@v0.9.0
unicode/bidi — пакет внутри этого модуля, vendor/ — каталог, а go1.25.9 — версия toolchain. Путь модуля и его версию проверьте по go.mod, списку разрешённых зависимостей или данным сборки.

Для Go отдельно проверяйте, какой модуль использован в проекте. Например, pkg:golang/go.etcd.io/etcd/api/v3@v3.5.23 описывает существующий модуль API etcd. Замена на pkg:golang/github.com/etcd-io/etcd@v3.0.0%2Bincompatible укажет на другой компонент и другую версию. Не меняйте координаты только потому, что компонент не найден в результатах анализа.

Пакет не найден

Неизвестный компонент не обязательно имеет некорректный PURL. Это может быть внутренний пакет, отсутствующая в источнике версия или пакет, для которого в базе пока нет сведений.

После проверки PURL убедитесь, что в исходном реестре существует именно указанная версия и что она соответствует анализируемому артефакту. Если пакет остаётся неизвестным, передайте в поддержку исходный PURL, ссылку на источник или метаданные пакета, название и версию генератора, а также минимальный фрагмент SBOM с проблемным компонентом. Эти данные помогут отличить ошибку генерации от недостатка сведений о пакете.

Страница была полезна?