Правила составления PURL
Package URL (PURL) — идентификатор пакета, который содержит его экосистему, имя, версию и дополнительные признаки. CodeScoring использует PURL при сопоставлении компонентов с данными о пакетах и уязвимостях.
Эти рекомендации пригодятся при подготовке собственного SBOM, проверке отчёта стороннего инструмента и разборе неизвестных компонентов. Начните с порядка проверки, затем перейдите к правилам нужной экосистемы.
Структура PURL
Общая структура определена в стандарте ECMA-427:
Не все части обязательны. Например, PURL артефакта Maven выглядит так:
Параметры (qualifiers) уточняют характеристики и происхождение пакета. Например, arch обозначает архитектуру, distro — дистрибутив и его выпуск, upstream — исходный пакет, repository_url — репозиторий пакетов. Допустимые параметры и формат их значений зависят от типа PURL. Для некоторых типов отдельные параметры обязательны: например, tag_id у SWID.
Выбирайте тип по происхождению конкретного пакета. Библиотека на одном языке может распространяться через разные пакетные экосистемы: например, Python-библиотека из PyPI имеет тип pypi, из Conda — conda, а пакет дистрибутива Debian — deb.
Полный перечень приведён в разделе «Типы PURL и компонентов».
Общий синтаксис PURL допускает отсутствие версии. Для анализа конкретной зависимости укажите её фактическую версию: диапазон >=1.0, метка latest или версия среды выполнения не заменяют версию установленного пакета.
Порядок проверки
- Найдите исходный PURL компонента в SBOM или запросе интеграции. Сверьте его с полями имени и версии этого же компонента.
- Установите происхождение пакета: реестр, системный репозиторий или репозиторий исходного кода. Выберите соответствующий тип PURL.
- Подтвердите имя и точную версию по lock-файлу, метаданным установленного пакета или записи в исходном реестре. В манифесте может быть указан только диапазон допустимых версий.
- Проверьте пространство имён, кодирование и параметры по правилам выбранного типа. Для системных пакетов дополнительно проверьте дистрибутив и сведения об исходном пакете.
- Исправьте метаданные или настройки генератора и заново сформируйте SBOM. Если правите SBOM вручную, сохраните согласованность сведений о компоненте и ссылок на него в графе зависимостей.
- Повторите сканирование 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
Используйте groupId как обязательный namespace, artifactId как name, а разрешённую версию артефакта как version. Координаты можно проверить в POM и дереве зависимостей Maven, Gradle или sbt.
Сохраняйте исходный регистр groupId, artifactId и версии. Например, не заменяйте суффикс Final на final и не удаляйте его. Копируйте координаты из метаданных артефакта.
Расширение файла .jar и classifier не входят в версию. Для вариантов артефакта спецификация Maven предусматривает параметры type и classifier:
npm: JavaScript и TypeScript
Имя и разрешённую версию берите из lock-файла или метаданных установленного пакета. Для scoped-пакета namespace содержит scope с закодированным @; для остальных пакетов пространство имён отсутствует. Не включайте node_modules в PURL и не используйте alias зависимости вместо имени исходного пакета.
Определение npm сохраняет чувствительность имён к регистру для исторических пакетов. Не меняйте регистр произвольно.
PyPI: Python
Используйте имя дистрибутива Python из метаданных пакета, а не имя импортируемого модуля или wheel-файла. namespace отсутствует. При нормализации имени по правилам Python Packaging регистр становится нижним, а последовательности -, _ и . заменяются одним дефисом.
Версия должна относиться к пакету, а не к интерпретатору Python. Не отбрасывайте признаки предварительного, исправленного или локального выпуска. Формат типа описан в определении PyPI.
Go Modules
Для зависимости 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
В name указывайте полный идентификатор пакета из .nuspec или lock-файла. Точки в имени не отделяют namespace: у NuGet PURL его нет. Используйте разрешённую версию пакета, сохраняя prerelease-часть; версия .NET и имя сборки DLL для этого не подходят.
Composer, RubyGems и Cargo
Composer (PHP) — обязательный vendor находится в namespace, имя пакета — в name. Оба значения записываются в нижнем регистре. Проверяйте версию по composer.lock.
RubyGems (Ruby) — namespace отсутствует; используйте имя gem и версию из Gemfile.lock или метаданных gem. Вариант платформы можно указать в platform, не добавляя его к версии.
Cargo (Rust) — namespace отсутствует. Используйте имя пакета и версию из Cargo.lock, учитывая источник зависимости. Не подменяйте имя пакета alias из манифеста или именем модуля в исходном коде.
Определения типов: Composer, RubyGems, Cargo.
CocoaPods и Swift Package Manager
CocoaPods — используйте имя pod с исходным регистром и версию из Podfile.lock. namespace отсутствует, а subspec при наличии записывается после #.
Swift Package Manager — обязательный namespace содержит хост и владельца репозитория, name — имя репозитория. Сверяйте версию или ревизию с Package.resolved; не используйте отображаемое имя продукта вместо координат зависимости.
Определения типов: CocoaPods, Swift.
CRAN, Hex и Pub
CRAN (R) — используйте имя пакета с исходным регистром и полную версию, включая суффикс после дефиса. namespace отсутствует. Источники координат — DESCRIPTION установленного пакета и renv.lock.
Hex (Erlang, Elixir, Gleam) — имя пакета записывается в нижнем регистре. Необязательный namespace обозначает организацию для приватных пакетов. Используйте имя Hex-пакета и разрешённую версию из lock-файла, учитывая возможное отличие имени приложения от имени пакета.
Pub (Dart, Flutter) — используйте имя пакета и версию из pubspec.lock. namespace отсутствует; имя содержит строчные латинские буквы, цифры и подчёркивания.
Определения типов: CRAN, Hex, Pub.
Conan и Conda
Conan (C/C++) — разделяйте имя, версию и признаки варианта пакета. В ссылке Conan name/version@user/channel значения user и channel соответствуют одноимённым параметрам PURL. Если известны ревизии рецепта и пакета, укажите rrev и prev; сведения о сборке берите из метаданных Conan.
Conda — namespace отсутствует. Имя и версия относятся к Conda-пакету, даже если он содержит библиотеку Python. Для конкретной сборки сохраните подтверждённые build, channel, subdir и type.
Определения типов: Conan, Conda.
Системные пакеты: DEB, RPM и APK
Для системного пакета важна версия, выпущенная дистрибутивом, со всеми его суффиксами и исправлениями. Версия исходного проекта, например OpenSSL, не заменяет версию пакета Debian, RHEL или Alpine.
В namespace указывайте поставщика пакета, например debian, ubuntu, fedora или alpine, в нижнем регистре. arch берите из метаданных пакета: значения amd64 и x86_64 нельзя произвольно заменять друг другом между экосистемами.
DEB — сохраняйте полную версию, включая эпоху перед двоеточием и ревизию пакета. В примере эпоха 1, а upstream=shadow указывает имя исходного пакета:
RPM — используйте версию вместе с release: version-release. Если в метаданных есть эпоха, передайте её параметром epoch. Не включайте расширение .rpm в версию и архитектуру — в имя пакета.
APK — сохраняйте ревизию вида -r0 в версии. Тип apk относится к пакетам Alpine и других APK-based дистрибутивов, а не к Android-приложениям.
Дистрибутив и исходный пакет
name содержит имя бинарного пакета, а upstream — сведения об исходном пакете, из которого он собран. Имена этих пакетов могут совпадать.
Для сопоставления системных пакетов CodeScoring учитывает дополнительные параметры:
Проверяйте 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:
У типа Docker пространство имён может содержать реестр или владельца образа, а версией может быть тег или SHA-256 идентификатор. Для воспроизводимой идентификации предпочтителен digest: тег может указывать на другой образ со временем.
PURL образа описывает образ целиком. Для его содержимого нужны отдельные PURL установленных пакетов.
Репозитории, модели и произвольные артефакты
У этих типов отличаются источники координат. Используйте их, когда они соответствуют происхождению компонента.
Если пакет получен из Maven, PyPI или системного репозитория, сохраняйте его пакетную экосистему. Наличие исходного кода на GitHub не делает пакет типом github, а замена неизвестного PURL на generic не восстанавливает недостающие координаты.
Нормализация в CodeScoring
Перед сопоставлением CodeScoring нормализует PURL по правилам экосистем. Поэтому запись в результатах может отличаться от исходной: например, регистром, представлением версии или набором параметров.
Не подгоняйте входной PURL под внутреннюю нормализованную запись удалением сведений о пакете. Допустимые по спецификации classifier у Maven или platform у RubyGems могут не участвовать в сопоставлении, но это не делает их ошибочными. Для Cargo используйте имя из реестра и lock-файла, даже если нормализатор меняет дефисы на подчёркивания.
Нормализация не восстанавливает достоверную версию, отсутствующий groupId или происхождение пакета. Эти сведения нужно получить из источника.
Типичные ошибки
Примеры ошибок
Исправляйте PURL по метаданным своей зависимости. Имена и версии в примерах ниже относятся к конкретным пакетам и не предназначены для замены координат в вашем SBOM.
Для 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 с проблемным компонентом. Эти данные помогут отличить ошибку генерации от недостатка сведений о пакете.
