Лицензии как инженерное ограничение: копилефт и разрешительные
Инженер добавляет в проект библиотеку для конвертации документов. Она делает ровно то, что нужно, у неё две тысячи звёзд, тесты зелёные, CI прошёл, ревью занял четыре минуты. Через год компания продаёт коробочную версию продукта крупному заказчику, тот присылает опросник поставщика, в опроснике пункт «перечислите открытые компоненты и их лицензии». Кто-то впервые запускает сканер. Библиотека — под GPL-3.0. Она вкомпилирована в единственный бинарь, который вы отдаёте заказчику на флешке.
Дальше есть три выхода, и все три стоят денег. Открыть исходники продукта. Выкинуть библиотеку и переписать её функциональность — примерно два человеко-месяца, потому что за год к ней приросло сорок мест в коде. Купить коммерческое исключение у автора, если автор один и если он согласен. Ни один из выходов не стоил бы ничего, если бы вопрос задали в тот момент, когда строка dependencies попала в pull request. Вот почему лицензия — это инженерное ограничение, а не юридическая формальность. Она ведёт себя ровно как ограничение архитектуры: дёшево учесть на входе, дорого исправить потом, и чем позже обнаружено, тем шире радиус переделки. В этой главе мы разбираем лицензии так же, как разбирали бы требования к латентности: какое ограничение накладывает каждое семейство, в какой момент оно срабатывает, где проходит граница, как проверить соблюдение автоматически и во что обходится сама проверка. Про способы довезти продукт до пользователя целиком — карта трека и модели поставки; здесь один срез: какие двери каждая модель поставки закрывает или открывает по лицензионным причинам.
Сразу и однозначно: это не юридическая консультация. Всё ниже — инженерная модель, которая помогает задать правильный вопрос и подготовить к разговору с юристом факты. Раздел «Что спросить у юриста» существует именно потому, что часть вопросов инженерным способом не решается в принципе.
Лицензия — это условное разрешение, а не описание характера проекта
Начнём с первого принципа, который переворачивает интуицию большинства.
По умолчанию — нельзя. Авторское право возникает автоматически в момент, когда код написан. Без отдельного разрешения посторонний человек не имеет права ни копировать код, ни распространять его, ни делать производные произведения. Репозиторий на GitHub без файла LICENSE — это не «код в свободном доступе», это код, который вам разрешили посмотреть и больше ничего. GitHub прямо пишет это в своей документации: без лицензии действуют правила по умолчанию, то есть все права у автора (choosealicense.com/no-permission).
Лицензия — это разрешение, выданное под условиями. Структура любой лицензии одинакова:
- Что разрешено. Использовать, копировать, изменять, распространять, сублицензировать, использовать в патентных целях.
- При каких условиях. Сохранить текст лицензии; указать авторов; отметить изменённые файлы; открыть исходники; не накладывать дополнительных ограничений.
- Чего не даётся. Гарантий, ответственности, прав на товарный знак, иногда — патентных прав.
Отсюда практическое правило, которое стоит выучить дословно: вопрос «а можно ли использовать эту библиотеку» бессмысленный. Осмысленный вопрос — «что мне придётся сделать, если я её использую и потом вот так поставлю продукт». Одна и та же GPL-библиотека безопасна во внутреннем сервисе, требует открыть код в коробочной поставке и попадает в серую зону в контейнере, который вы кладёте в публичный реестр. И сразу стоит отделить три правовых механизма, которые в инженерных разговорах постоянно смешивают:
| Авторское право | Патент | Товарный знак | |
|---|---|---|---|
| Что защищает | конкретный текст кода | техническое решение независимо от реализации | имя, логотип, оформление |
| Даёт ли лицензия на код | да, это её основной предмет | только если сказано явно: Apache-2.0 даёт, MIT молчит | почти никогда, обычно прямо исключено |
| Инженерное следствие | нельзя копировать без соблюдения условий | переписать «своими словами» не спасает | форк надо переименовать и перебрендировать |
| Пример | взяли файл из чужого репозитория | реализовали запатентованный кодек | назвали форк тем же именем |
Именно из третьего столбца растут все истории вида «Debian пересобирает браузер и меняет ему имя»: код открыт, а имя и логотип — нет.
Три с половиной момента, когда лицензия просыпается
Ограничение срабатывает не тогда, когда вы скачали зависимость, а в конкретных событиях. Их немного. Использование. Просто запустить программу. Открытые лицензии здесь почти никогда ничего не требуют: GPL-3.0 прямо говорит, что запуск программы не ограничивается. А вот проприетарные и source-available лицензии ограничивают именно использование — числом ядер, числом пользователей, назначением. Модификация. Изменить код у себя. Само по себе тоже почти всегда свободно. Обязательства почти всех открытых лицензий привязаны не к модификации, а к следующему шагу.
Передача (distribution, в терминах GPL-3.0 — conveying). Вот главный триггер. Вы даёте другому лицу копию — в любой форме: инсталлятор, образ контейнера, прошивка в устройстве, JS-бандл в браузер, артефакт в реестр, архив подрядчику. Здесь просыпается всё: обязанность приложить текст лицензии, предложить исходники, не накладывать дополнительных ограничений. И половина момента — доступ по сети. Классическая GPL написана в мире, где софт передавали копиями, и SaaS в неё не попадает: вы крутите изменённый GPL-код на своём сервере, никому копию не передаёте, значит и обязательств нет. Это осознанное свойство, за которым закрепилось название «лазейка ASP». AGPL-3.0 закрывает её отдельным пунктом 13: если вы изменили программу и даёте пользователям взаимодействовать с ней удалённо по сети, вы обязаны предложить им исходники.
Но учесть в реестре: поставка
может измениться завтра"] B -- "да" --> D{"Какое семейство лицензии?"} D -- "разрешительная
MIT, BSD, ISC, Apache" --> E["Положить текст лицензии
и копирайты в поставку.
Apache: отметить изменения, NOTICE"] D -- "файловый копилефт
MPL-2.0, EPL-2.0" --> F["Открыть исходники изменённых файлов.
Остальной продукт — как хотите"] D -- "библиотечный копилефт
LGPL" --> G["Открыть исходники библиотеки
и дать пользователю подменить её
своей сборкой"] D -- "сильный копилефт
GPL" --> H{"Компонент внутри вашей
единицы поставки?"} D -- "сетевой копилефт
AGPL" --> I{"Вы его изменяли?"} D -- "source-available
BUSL, SSPL, EULA" --> J["Читать сценарные запреты:
конкурирующий сервис, число ядер,
отрасль. Это ограничение бизнеса,
а не сборки"] H -- "да" --> K["Вся работа под GPL.
Проприетарная поставка невозможна"] H -- "нет, отдельный процесс" --> L["Ваш код свободен, но сам компонент
надо отдать с исходниками"] I -- "да" --> M["Предложить исходники
пользователям сервиса.
SaaS не спасает"] I -- "нет, ванильная сборка" --> N["Пункт 13 не сработал.
Но любая передача копии — сработает"]
Обратите внимание на левую нижнюю ветку: «крутится внутри — обязательств почти нет». Это правда и одновременно самая частая ловушка. Модель поставки меняется: внутренний сервис становится продуктом, продукт начинают ставить на площадку заказчика, SaaS обрастает on-premise-версией для банков. Ограничение, которого «не было», внезапно оказывается на критическом пути релиза. Поэтому реестр компонентов ведут всегда, а решение принимают по самой жёсткой из планируемых моделей поставки, а не по текущей.
Что копилефт запрещает делать — конкретным списком
«Копилефт» звучит как идеология. Инженерно это набор запретов, и полезно держать их в голове списком, а не лозунгом. Нельзя отдать бинарь и не отдать исходники. GPL-2.0 в пункте 3 и GPL-3.0 в пункте 6 описывают варианты: приложить исходники к поставке; приложить письменное предложение выдать их в течение трёх лет; для сетевой раздачи — держать исходники на сервере рядом с бинарём. Причём отдать надо не «примерно тот код», а Corresponding Source: ровно ту версию плюс скрипты сборки, конфигурацию, определения интерфейсов — всё, что нужно, чтобы получатель пересобрал ровно этот артефакт. Это прямое требование к вашей системе сборки: если вы не умеете по номеру версии восстановить точное дерево исходников со всеми патчами, вы не сможете выполнить обязательство физически. Здесь лицензия смыкается с воспроизводимостью сборки (цепочка поставки).
Нельзя накладывать дополнительные ограничения. Формулировка GPL — «no further restrictions». Это ломает привычные проприетарные конструкции: NDA на код, запрет реверс-инжиниринга в EULA, ограничение числа установок, запрет передавать копию дальше. Если ваш стандартный договор содержит такие пункты, а в поставке есть GPL-компонент, договор и лицензия конфликтуют — вопрос «как развести наш EULA и GPL в одной поставке» типовой и именно юридический. Нельзя запретить пользователю поставить свою сборку на купленное устройство. GPL-3.0 в пункте 6 требует для «потребительских продуктов» приложить Installation Information — ключи, инструкции, всё, что нужно, чтобы поставить изменённую версию на само устройство. Пункт появился как ответ на практику, когда исходники формально отдавали, а прошивка проверяла подпись и чужую сборку не запускала. Инженерное следствие жёсткое: безопасная загрузка с закрытым набором ключей и GPL-3.0 в прошивке потребительского устройства плохо совместимы. Для встраиваемых продуктов это одно из главных ограничений выбора компонентов (трек про встраиваемые системы — про сами устройства).
Нельзя тихо перелицензировать. Права получены под условиями; сменить условия на выпущенную версию нельзя — это относится и к вашему собственному проекту, если у него больше одного автора (подробнее в разделе про CLA). Нельзя подать патентный иск и сохранить лицензию. GPL-3.0 в пункте 11 и Apache-2.0 в пункте 3 устроены так, что патентная атака на пользователей продукта прекращает вашу собственную лицензию. Для компании с патентным портфелем это не абстракция, а вход в список вопросов к юристу.
И симметрично — что копилефт не запрещает, вопреки распространённому мифу:
- Не запрещает продавать. GPL прямо разрешает брать деньги за копии и за поддержку: она ограничивает не цену, а условия передачи.
- Не заражает всё, к чему прикоснулась. Файл в вашем репозитории не становится GPL оттого, что рядом лежит GPL-библиотека: условие — вхождение в одну работу и передача.
- Не требует публиковать код всему миру. Обязательство существует перед теми, кому вы передали бинарь: если заказчик один, исходники нужно предложить одному заказчику. То, что он потом вправе выложить их публично, — отдельный факт, который стоит осознавать заранее.
- Не действует на неизменённое использование внутри. Пока копия не ушла наружу, триггера нет.
Где проходит граница «одной работы»
Это центральный технический вопрос копилефта, и честный ответ — граница определена не полностью. Есть позиция FSF, изложенная в GPL FAQ, есть практика индустрии, есть немногочисленные судебные решения, и они не покрывают все случаи.
Разберём случаи, в которых чаще всего ошибаются. Статическая линковка. Вы получаете один исполняемый файл, внутри которого куски чужого кода. Здесь спор практически отсутствует: индустрия работает по модели «это одна работа». Языки с преимущественно статической сборкой — Go, Rust, C++ с -static — делают этот случай основным, а не экзотическим. Инженер на Go, который тянет GPL-библиотеку в бинарь для коробочной поставки, получает ровно ту историю из начала главы.
Динамическая линковка. Позиция FSF: комбинированная работа, копилефт распространяется. Позиция части юристов и части компаний: связывание через стабильный публичный интерфейс — не производное произведение. Судебной практики, которая закрыла бы вопрос, мало. Практический вывод: не стройте бизнес-риск на том, что динамическая линковка «спасает» от GPL; она спасает от LGPL, потому что LGPL это явно разрешает.
LGPL и подмена библиотеки. Библиотечный копилефт формулирует требование инженерно: пользователь должен иметь возможность заменить библиотеку своей сборкой и получить работающий продукт. Динамическая линковка это условие выполняет естественно. При статической линковке LGPL требует дать объектные файлы вашего продукта, чтобы пользователь мог перелинковать. Для мобильных приложений это отдельная боль: iOS-сборка целиком статическая и подписанная, возможность перелинковки конфликтует с моделью подписи (релизы в магазины, а правила самих магазинов — в главе про магазины приложений).
Отдельный процесс. Ваша программа вызывает утилиту через exec, обменивается данными через pipe или локальный сокет — FSF называет это скорее двумя программами. Но граница размыта: если обмен идёт «сложными внутренними структурами данных», FSF считает это одной работой. Ориентир: чем ближе взаимодействие к «командная строка и поток байтов», тем безопаснее; чем ближе к «передаём указатели на общие объекты», тем опаснее. Простое объединение (mere aggregation). Диск дистрибутива с тысячей независимых программ не превращает их в одну работу — так живут Debian и любой репозиторий пакетов. При этом каждый пакет по-прежнему едет со своей лицензией и своими исходниками: «объединение» снимает только вопрос перетекания копилефта на соседей.
Образы контейнеров. Слой с debian:bookworm-slim или alpine — это десятки бинарей, часть под GPL. Публикуя образ в реестр, вы передаёте копии этих бинарей. Хорошая новость: обычно это простое объединение и на ваш код ничего не перетекает; ваш слой лежит поверх системных пакетов, а не сливается с ними. Плохая: обязанность отдать исходники системных компонентов формально ваша, а не Debian. Практика индустрии — ссылаться на исходники дистрибутива и хранить точный список пакетов с версиями в SBOM; здесь как раз спасает то, что дистрибутивы держат архивы исходников доступными. Об устройстве образов и реестров — контейнеры и реестры, об упаковке в целом — упаковка и установка.
Фронтенд-бандл. Ключевой и почти всегда пропускаемый случай. Когда браузер скачивает ваш app.min.js, вы передаёте пользователю объектный код — минифицированный результат сборки. Если в бандле есть копилефт-компонент, обязательства срабатывают ровно так же, как при выдаче инсталлятора. Отсутствие «установки» ничего не меняет: копия ушла на чужую машину.
Живой случай портала: инстанс it-tools под GPL-3.0
Этот портал даёт удобный разбор на реальном материале, а не на выдуманном. Мы поднимаем для читателей инстанс it-tools — набор утилит вроде конвертеров и генераторов. Проект под GPL-3.0. Мы не просто запустили ванильную сборку: к ней подключена собственная тема оформления Digitable Focus, то есть код изменён. Инстанс доступен читателям по адресу tools.digitable.life. Отдельно на портале работает Digitable Chat под GPL-2.0-or-later.
Разложим это по нашей же схеме. Момент срабатывания. Пользователь открывает страницу — браузер получает собранный JS/CSS-бандл, то есть объектный код. Триггер сработал независимо от того, что «мы ничего не устанавливаем на компьютер пользователя» и что «это же просто сайт». Объём обязательства. Не «исходники it-tools вообще» и не «ссылка на апстрим», а Corresponding Source той версии, которая запущена, вместе с нашей темой и конфигурацией сборки. Ссылка на репозиторий CorentinTh/it-tools обязательство не закрывает: там нет нашей темы, а значит это не тот код, который получил пользователь.
Как оформлено. В подвале каждой страницы стоят ссылки на форки с явным указанием лицензии. В шаблоне layouts/partials/site-footer.html рядом с ними лежат комментарии, объясняющие, почему ссылка именно тут:
{{/* tools.digitable.life — сборка it-tools под GPL-3.0. Лицензия обязывает
дать получателям доступ к исходному коду той версии, которую мы
запускаем, поэтому ссылка на форк стоит на сайте, а не подразумевается. */}}
<a href="https://github.com/digitable-lol/it-tools" rel="noopener">Исходники IT Tools · GPL-3.0</a>
Комментарий в шаблоне — не украшение: через полгода придёт человек, увидит «лишние» ссылки в подвале и захочет убрать их ради чистоты дизайна, и комментарий ответит ему до того, как он спросит.
Что здесь ещё не закрыто — и это честная часть примера. Ссылка ведёт на форк, а форк в момент написания совпадает с апстримом: gh api repos/digitable-lol/it-tools/compare/CorentinTh:main...digitable-lol:main возвращает "status": "identical". То есть ссылка есть, а кода запущенной версии за ней нет. Обязательство считается выполненным не тогда, когда ссылка появилась в подвале, а тогда, когда за ссылкой лежит именно тот код. В задачнике портала это два отдельных пункта: залить свою сборку в форк и поставить ссылку, причём второй зависит от первого. Пример показателен тем, что типичная ошибка здесь не «мы не знали про GPL», а «мы сделали видимую половину работы».
Случай Digitable Chat отличается одной деталью — лицензия GPL-2.0-or-later. Суффикс «or later» означает, что получатель вправе выбрать условия любой более поздней версии GPL. Для нас это практично: код под GPL-2.0-or-later можно комбинировать с кодом под GPL-3.0, а вот с чистой GPL-2.0-only — нельзя. Разница между GPL-2.0-only и GPL-2.0-or-later в идентификаторе SPDX — не бюрократия, а важный факт о совместимости.
Что из этого следует для сборки. Три технических требования, невидимых, пока не начнёшь выполнять обязательство всерьёз: артефакт должен знать свою версию и коммит, иначе не ответить, какие именно исходники соответствуют тому, что в проде; форк должен обновляться при каждом деплое изменённой сборки, а не «когда вспомним»; проверка «ссылка есть на каждой странице» должна жить в тестах, а не в памяти — тот же класс проверок, что релизные гейты в безопасных релизах.
А что тут спросить у юриста. Достаточно ли ссылки в подвале как способа «предложить» исходники в смысле GPL-3.0 для веб-приложения, или требуется более явное уведомление; обязаны ли мы хранить исторические версии исходников для сборок, снятых с эксплуатации. Вопросы с конкретным ответом, но не инженерным.
AGPL: копилефт, который срабатывает без передачи копии
AGPL-3.0 — это GPL-3.0 плюс пункт 13. Его смысл в одной фразе: если вы изменили программу и дали пользователям работать с ней удалённо по сети, вы обязаны заметно предложить этим пользователям Corresponding Source изменённой версии.
Три инженерных вывода. Триггер — модификация, а не запуск. Ванильная AGPL-программа, поднятая как есть, пункт 13 не активирует. Но «ванильная» надо понимать буквально: патч, чтобы починить баг, — уже модификация. А правку конфигурации обычно модификацией не считают. Граница между «конфигурация» и «модификация» в реальных установках размыта, и она же — главный источник неопределённости.
Граница «программы» не совпадает с границей вашей системы. Ваш сервис ходит по HTTP в отдельно стоящий AGPL-сервис — это две программы, и код вашего сервиса не затрагивается. А вот AGPL-библиотека, вкомпилированная в ваш серверный бинарь, делает весь бинарь единой работой, и пункт 13 относится к нему целиком. Отсюда корпоративные запреты. Во многих компаниях AGPL просто в списке deny — не потому, что лицензия плоха, а потому, что цена ошибки высока, а граница нечёткая. Это управление риском, а не правовая позиция. Если вы строите SaaS, отношение к AGPL — одно из первых архитектурных решений; оно смыкается с выбором облачных уровней и с тем, где кончается ваш периметр. Полезная деталь: GPL-3.0 в своём пункте 13 специально разрешает комбинировать код с AGPL-3.0 — совместимость предусмотрена авторами обеих лицензий.
Разрешительные лицензии: одна обязанность, которую всё равно забывают
MIT, BSD-2-Clause, BSD-3-Clause, ISC устроены почти одинаково: делайте что хотите, включая закрытие исходников и продажу, но сохраните текст лицензии и указание авторства в копиях и в существенных частях. Всё. Гарантий нет, ответственности нет. Одна обязанность — и она нарушается постоянно, потому что незаметна. Мобильное приложение с сорока зависимостями под MIT обязано донести до пользователя сорок копирайт-нотисов. Отсюда экран «Правовая информация» в приложениях, файл THIRD-PARTY-NOTICES.txt в дистрибутиве, слой с лицензиями в образе контейнера. Это генерируется автоматически — вручную такое не поддерживают.
Apache-2.0 добавляет к этому три вещи, и они делают её другим инструментом, а не «MIT с длинным текстом»:
- Явный патентный грант (пункт 3). Контрибьюторы дают вам патентную лицензию на своё вкладываемое. MIT об этом молчит, и молчание — источник неопределённости для компаний с юридической службой. Именно поэтому Apache-2.0 часто предпочитают в корпоративной среде.
- Патентная ответная мера. Судитесь по патентам из-за этого софта — теряете патентную лицензию.
- Требование отмечать изменения и сохранять NOTICE (пункт 4). Если в проекте есть файл
NOTICE, его содержимое надо передавать дальше — конкретная задача сборки, а не благое пожелание.
А BSD-3-Clause отдельно запрещает использовать имя правообладателя для продвижения вашего продукта — тот самый переход в зону товарных знаков. А исторический BSD-4-Clause с «рекламным пунктом» (обязательная фраза в рекламных материалах) несовместим с GPL, и это самый известный пример того, как безобидное на вид требование ломает совместимость. Университет Беркли отозвал этот пункт в 1999 году, но лицензия ещё встречается в старом коде.
Правый верхний угол не означает «плохие лицензии» — он означает «несовместимые с одной конкретной моделью поставки, закрытым продуктом». Если ваша модель другая, копилефт может быть ровно тем, что нужно: он не даёт крупному игроку взять ваш код, закрыть и продавать. Именно так копилефт используют как инструмент конкуренции, и об этом — следующая глава. Отдельно про SSPL и BUSL в этом квадранте: у них мало обязательств по оформлению поставки, но они запрещают целые сценарии использования — принципиально другая ось, и на плоскость она ложится плохо, что само по себе хороший индикатор.
Совместимость: стрелки односторонние
Комбинируя код под разными лицензиями, вы получаете работу, к которой применимы все условия сразу. Если условия противоречат друг другу, комбинация невозможна вообще — не «рискованна», а невозможна.
читаются как доп. ограничение" .-> G2 MPL --> G3 LG --> G3 G3 --> AG G2 --> G2L["GPL-2.0-or-later"] --> G3 G3 -. "ОБРАТНО НЕЛЬЗЯ НИКОГДА" .-> MIT
Читается так: сплошная стрелка A --> B означает «код A можно включить в работу под лицензией B»; пунктир — запрещённое направление. Обратного пути нет: это односторонний граф, и в этом вся его суть. Результирующая лицензия комбинированной работы — самая ограничительная в цепочке, и добраться до неё можно, а вернуться — нет.
Практические следствия, которые стоит помнить наизусть:
- Apache-2.0 и GPL-2.0-only несовместимы. Позиция FSF: патентные условия Apache — дополнительное ограничение, которого GPL-2.0 не допускает. С GPL-3.0 совместимо, потому что третья версия такие условия предусмотрела. Отсюда, кстати, огромная практическая разница между ядром Linux (GPL-2.0-only) и большинством пользовательского софта.
- Суффикс
-or-later— это опцион.GPL-2.0-or-laterсовместим с GPL-3.0-миром,GPL-2.0-only— нет. В SPDX это разные идентификаторы, и сканеры их различают; человек, читающий заголовок файла, часто нет. - MPL-2.0 намеренно сделана мостом. В ней есть механизм «вторичной лицензии», позволяющий комбинировать с GPL, — поэтому MPL часто выбирают для библиотек, которые должны жить и в открытом, и в закрытом мире.
- Проприетарная лицензия несовместима с любым сильным копилефтом в рамках одной работы. Не переговорная позиция, а арифметика условий.
Как посчитать результирующее обязательство: маленький алгоритм
Дерево зависимостей современного проекта — сотни, иногда тысячи узлов, и лицензия транзитивна: она приезжает к вам не только из прямой зависимости, но и из зависимости зависимости на четвёртом уровне. Считать это глазами нельзя. Считать это сканером — можно, и полезно понимать, что сканер делает. Модель простая: лицензии образуют решётку обязательств, где каждый уровень включает предыдущий. Результат для артефакта — максимум по всем достижимым узлам, но с поправкой: копилефт не перетекает через «слабые» рёбра — вызов отдельного процесса или сетевой вызов.
Псевдокод:
функция посчитать(граф, корень):
итог ← НЕТ; отдать_исходники ← ∅
обход в глубину от корня с флагом «связь сильная» = истина:
если связь сильная и лицензия(узел) — копилефт:
итог ← максимум(итог, уровень(лицензия(узел)))
если узел физически попадает в поставку и это копилефт:
отдать_исходники ← отдать_исходники ∪ {узел}
для каждого ребра (узел → потомок):
новый_флаг ← связь_сильная И тип_ребра ∈ {компиляция, линковка, вендоринг}
вернуть итог, отдать_исходники
Реализация:
from enum import IntEnum
from dataclasses import dataclass, field
class Obligation(IntEnum):
"""Решётка обязательств: каждый уровень строго включает предыдущий."""
NONE = 0 # CC0, Unlicense, 0BSD
NOTICE = 1 # MIT, BSD, ISC, Apache-2.0 — сохранить текст и авторство
FILE_COPYLEFT = 2 # MPL-2.0, EPL-2.0 — открыть изменённые файлы
LIB_COPYLEFT = 3 # LGPL — открыть библиотеку и дать её подменить
WORK_COPYLEFT = 4 # GPL — открыть всю работу при передаче
NET_COPYLEFT = 5 # AGPL — то же плюс при доступе по сети
RESTRICTED = 6 # BUSL, SSPL, чужие проприетарные — запреты на сценарии
O = Obligation
LEVELS = {
"MIT": O.NOTICE, "ISC": O.NOTICE, "BSD-3-Clause": O.NOTICE, "Apache-2.0": O.NOTICE,
"MPL-2.0": O.FILE_COPYLEFT, "EPL-2.0": O.FILE_COPYLEFT,
"LGPL-3.0-or-later": O.LIB_COPYLEFT,
"GPL-2.0-only": O.WORK_COPYLEFT, "GPL-3.0-or-later": O.WORK_COPYLEFT,
"AGPL-3.0-or-later": O.NET_COPYLEFT,
"BUSL-1.1": O.RESTRICTED, "SSPL-1.0": O.RESTRICTED,
"Proprietary": O.NONE, # ваш собственный закрытый код
}
# Рёбра, через которые копилефт перетекает на вашу работу.
STRONG_EDGES = {"compile", "static-link", "dynamic-link", "vendor"}
@dataclass
class Node:
name: str
license: str
shipped: bool = True # едет ли компонент в поставку
deps: list[tuple[str, str]] = field(default_factory=list) # (имя, тип ребра)
def compute(graph: dict[str, Node], root: str):
"""Уровень обязательства на вашу работу + компоненты, для которых
надо быть готовым отдать исходники."""
effective, sources = Obligation.NONE, set()
seen, stack = set(), [(root, True)]
while stack:
name, strong = stack.pop()
if (name, strong) in seen:
continue
seen.add((name, strong))
node = graph.get(name)
if node is None:
sources.add(f"{name} (нет в графе)")
continue
level = LEVELS.get(node.license)
if level is None:
# Неоценимая зависимость опаснее известной плохой: в ручную очередь.
sources.add(f"{name} ({node.license}: нет в справочнике)")
elif level >= Obligation.FILE_COPYLEFT:
if strong:
effective = max(effective, level)
if node.shipped:
sources.add(name)
for dep, edge in node.deps:
stack.append((dep, strong and edge in STRONG_EDGES))
return effective, sorted(sources)
# Наш бинарь статически тянет MIT-библиотеку, внутри которой LGPL,
# и отдельно запускает GPL-утилиту как процесс.
graph = {
"our-app": Node("our-app", "Proprietary",
deps=[("fastjson", "static-link"), ("imagetool", "exec")]),
"fastjson": Node("fastjson", "MIT", deps=[("codecs", "static-link")]),
"codecs": Node("codecs", "LGPL-3.0-or-later"),
"imagetool": Node("imagetool", "GPL-3.0-or-later"),
}
level, sources = compute(graph, "our-app")
print(level.name) # LIB_COPYLEFT — GPL через exec на нас не перетёк
print(sources) # ['codecs', 'imagetool'] — исходники нужны для обоих
Сложность — O(V + E) по времени и O(V) по памяти; удвоение из-за флага силы связи константу не меняет. На дереве в десять тысяч узлов это доли секунды, то есть проверку спокойно ставят в каждый pull request.
Ограничение модели важнее самой модели. Результат — это очередь на ревью, а не вывод. Сканер не знает, статически вы линкуете или динамически, не отличит конфигурацию от модификации, не увидит, что «отдельный процесс» обменивается сложными структурами. Автоматика ловит очевидное и снимает 90 % шума; оставшиеся 10 % — там, где нужны человек и юрист.
Как это живёт в репозитории и в конвейере
Разница между «мы соблюдаем лицензии» и «мы можем это доказать» — примерно как между «у нас есть бэкапы» и «мы восстанавливались из них в прошлом квартале». В исходниках. Файл LICENSE в корне; NOTICE, если он требуется Apache-компонентами; заголовок SPDX-License-Identifier в файлах. Последнее — не бюрократия: машиночитаемая метка избавляет от угадывания по тексту. Спецификация REUSE описывает, как разметить репозиторий так, чтобы лицензия каждого файла определялась однозначно, включая картинки и конфиги, у которых нет места под комментарий.
# Проверка полноты разметки: у каждого ли файла определена лицензия
reuse lint
# Инвентаризация зависимостей — по одному инструменту на экосистему
cargo deny check licenses # Rust
go-licenses report ./... # Go
pip-licenses --format=csv # Python
license-checker --production # npm
# Сборка SBOM из готового артефакта, а не из манифеста: видит то, что реально внутри
syft packages docker:registry.example.com/app:4.2.1 -o cyclonedx-json > sbom.json
Разница между «из манифеста» и «из артефакта» существеннее, чем кажется: манифест описывает намерение, артефакт — факт. Внутрь образа попадают системные пакеты, скачанные бинарники, вкомпилированные статические библиотеки, ничего этого в package.json нет. SBOM из артефакта — тот же принцип, что и в цепочке поставки: доверяем тому, что измерили. В конвейере. Гейт устроен как политика со списками, а не как «сканер что-то сказал»:
# Фрагмент политики лицензий. Живёт в репозитории, ревьюится как код.
allow: # можно без вопросов
[MIT, ISC, BSD-2-Clause, BSD-3-Clause, Apache-2.0,
MPL-2.0] # файловый копилефт: наш код не задет
review: # можно, но с записанным решением
[LGPL-2.1-or-later, LGPL-3.0-or-later, EPL-2.0,
CC-BY-4.0] # для ассетов: нужен экран атрибуции
deny: # блокирует сборку
[GPL-2.0-only, GPL-3.0-or-later, AGPL-3.0-or-later, SSPL-1.0, BUSL-1.1,
CC-BY-NC-4.0, # некоммерческое — несовместимо с продажей
NOASSERTION] # лицензия не определена: самый частый вердикт
exceptions:
- package: "imagetool"
license: "GPL-3.0-or-later"
reason: "запускается как отдельный процесс, в поставку не входит"
approved_by: "legal-2026-03-14"
expires: "2027-03-14" # исключения протухают, иначе список растёт вечно
Три свойства этого файла делают его рабочим, а не декоративным.
denyжёстко ломает сборку. Предупреждение, которое никого не будит, равно отсутствию проверки.reviewсуществует отдельно отdeny. Иначе инженеры начинают обходить гейт, а вы теряете видимость — та же динамика, что с любым слишком строгим контролем в конвейере.- Исключения именованы и датированы. Без срока годности список исключений через два года становится главным источником риска.
NOASSERTIONвdeny. Неопределённая лицензия — самая частая находка сканера и самая недооценённая: пакет безLICENSEпо умолчанию не разрешён к использованию вообще.
в новом pull request Обнаружен --> Определён: лицензия распознана
по SPDX или тексту Обнаружен --> Неизвестен: файла лицензии нет
или текст нестандартный Неизвестен --> Ручной_разбор: смотрим репозиторий,
пишем автору Ручной_разбор --> Определён: ответ получен Ручной_разбор --> Отклонён: ответа нет — считаем
«все права защищены» Определён --> Разрешён: лицензия в allow Определён --> На_ревью: лицензия в review Определён --> Отклонён: лицензия в deny На_ревью --> Исключение: решение записано,
указан срок На_ревью --> Отклонён: модель поставки не позволяет Исключение --> Пересмотр: срок истёк
или изменилась поставка Пересмотр --> Исключение: продлено Пересмотр --> Отклонён: условия изменились Разрешён --> Пересмотр: апстрим сменил лицензию
в новой версии Отклонён --> [*]: компонент удалён,
заменён или выкуплен
Переход Разрешён --> Пересмотр — тот, который забывают закладывать. Проект может сменить лицензию в новой версии, и обновление зависимости на минорную версию иногда меняет лицензионный статус. Именно так BUSL приходил в проекты, годами живших на Apache-2.0. Поэтому сканирование в CI повторяется на каждой сборке, а не «один раз при добавлении».
Когда приходит запрос на исходники
Обязательство редко проверяют, но когда проверяют — быстро. Полезно один раз прогнать сценарий целиком и убедиться, что он выполним.
для версии 4.2.1 S->>R: какой коммит и какие патчи у 4.2.1? alt Артефакт помнит свою версию R-->>S: commit abc123, SBOM, список патчей S->>B: собери source-bundle для abc123 B->>A: положить архив исходников,
скриптов сборки и конфигурации A-->>S: ссылка на архив S-->>U: ссылка и инструкция по сборке Note over S,A: срок ответа — рабочие дни,
не месяцы else Артефакт не знает своей версии R-->>S: такой версии в реестре нет Note over S,U: обязательство физически невыполнимо.
Чинится не юристом, а системой сборки end U->>B: пересобирает и сравнивает с полученным бинарём
Ветка «else» — не выдумка, это самый частый способ провалиться. Компания искренне готова отдать исходники и не может: артефакт собран год назад с плавающими версиями зависимостей, которые с тех пор обновились, тег потерян, скрипт сборки жил на ноутбуке уволившегося. Поэтому воспроизводимость сборки и лицензионное соответствие — одна и та же инженерная задача, решаемая одними средствами: пиннинг версий, фиксация тулчейна, хранение SBOM вместе с артефактом, неизменяемые теги релизов. И отдельно про сроки: GPL-2.0 упоминает три года для письменного предложения исходников — значит, архивы релизов надо хранить дольше, чем живёт версия в проде, а политика удаления артефактов из реестра должна знать про этот срок.
Двойное лицензирование, CLA и почему нельзя передумать
Правообладатель не связан собственной лицензией: он может выдавать код на разных условиях разным людям. Отсюда классическая схема двойного лицензирования: открытая версия под сильным копилефтом плюс платная коммерческая лицензия для тех, кому копилефт мешает. Копилефт здесь работает не как идеология, а как продающий механизм — он делает бесплатный вариант непригодным ровно для тех, у кого есть деньги. По этой схеме десятилетиями живут Qt и целый класс инструментов; экономика такой модели — в главе про ценообразование и в материалах о собственном продукте. Схема работает, только если все права у вас одних. Как только появился внешний контрибьютор, его код принадлежит ему, и продать комбинацию под коммерческой лицензией вы уже не вправе. Отсюда два разных инструмента, которые постоянно путают:
| DCO (Developer Certificate of Origin) | CLA (Contributor License Agreement) | |
|---|---|---|
| Что делает контрибьютор | подтверждает Signed-off-by, что вправе отдать код |
подписывает соглашение с правообладателем |
| Передаются ли права | нет, автор остаётся автором | да: передача прав или широкая лицензия проекту |
| Позволяет ли двойное лицензирование | нет | да |
| Цена для проекта | ноль, одна строка в коммите | юридический процесс, отпугивает часть контрибьюторов |
| Типично для | Linux, большинство сообществных проектов | проекты за одной компанией |
Механику подписи коммитов и работу с внешними участниками — в совместной работе в Git. И самое неинтуитивное: перелицензировать выпущенное нельзя. Версия 3.1, отданная под MIT, останется под MIT навсегда, у всех, кто её получил. Смена лицензии касается только будущих версий и требует согласия всех, чей код в проекте. Именно поэтому смены лицензии крупными проектами регулярно рождают форки: сообщество берёт последний коммит под старой лицензией и продолжает — так появились форки инструментов инфраструктуры и хранилищ данных после перехода их владельцев на source-available. Инженерное следствие для вас как потребителя: зафиксированная версия зависимости не может «стать» BUSL задним числом; вы просто окажетесь на ветке без обновлений и без исправлений безопасности, и это уже вопрос обновлений и поддержки старых версий.
Source-available: похоже на открытое, но им не является
Отдельное семейство, выросшее из конфликта «мы пишем, облака зарабатывают». Общая идея: исходники видны, но использование ограничено сценариями.
- BUSL-1.1 (Business Source License). Использование ограничено, кроме разрешённого владельцем сценария (Additional Use Grant); в дату Change Date каждая версия автоматически переходит на открытую лицензию. Обычно запрещено именно «предлагать этот продукт как сервис». Механизм отложенного открытия ограничен сроком, прописанным в самой лицензии; в том же жанре работают «функциональные» лицензии с более коротким сроком и заранее названной будущей открытой лицензией.
- SSPL. Разрешает предлагать сервис, но требует открыть весь стек его обслуживания — оркестрацию, мониторинг, инструменты управления. Требование заведомо неисполнимо для большинства, что и есть его цель.
Ключевой факт: OSI не признаёт эти лицензии открытыми, потому что определение Open Source запрещает дискриминацию по областям применения. Это не оценка, а классификация: слова «open source» в маркетинге такого продукта неточны. Инженерное следствие тоже специфическое. Обязательств по оформлению поставки почти нет — нет требования открыть свой код, нет обязанности отдавать исходники. Есть запрет на бизнес-сценарий, и проверяется он не сканером, а вопросом «а что именно мы продаём». Если ваша компания перепродаёт хостинг чужого движка, вопрос критический; если использует его внутри — обычно нет. Тем не менее держите такие лицензии в deny с обязательным ревью: сценарий использования меняется вместе с продуктом, а лицензия про сценарии.
Волна переходов на source-available пришлась на 2023–2025 годы и затронула популярные инфраструктурные инструменты и хранилища; часть из них позже частично вернулась к открытым лицензиям, часть осталась, почти каждый переход породил форк под управлением фонда. Проверяйте текущий статус конкретного проекта на момент чтения — за последние три года многие из них менялись дважды. Ставить в статью таблицу «кто под чем» бессмысленно: она устареет быстрее, чем вы дочитаете трек.
Не только код: ассеты, шрифты, контент и модели
Самый грязный участок реестра — обычно не библиотеки, а всё остальное: инженеры аккуратны с package.json и беспечны с папкой assets.
Шрифты. SIL OFL 1.1 разрешает свободное использование и встраивание, но запрещает продавать сам шрифт отдельно и требует не использовать «зарезервированное имя шрифта» в модификациях. Коммерческие шрифты лицензируются по числу просмотров или приложений — для мобильного продукта или игры это реальная статья расходов. Иконки и UI-киты. Типичный набор: код под MIT, а сами иконки под CC BY 4.0, то есть с обязательной атрибуцией — для игры это отдельный экран титров, для сайта строка в подвале.
Контент, музыка, звук. Creative Commons — семейство, а не лицензия. CC BY требует атрибуции; CC BY-SA добавляет копилефт-подобное требование к производным; CC BY-NC запрещает коммерческое использование и потому несовместим с продаваемым продуктом — это самая частая и самая дорогая ошибка в игровых проектах, где ассеты набирают из разных источников. Разбор игровой специфики целиком — в главе про дистрибуцию игр.
Код с форумов и из ответов. Пользовательский контент на Stack Overflow лицензирован под CC BY-SA (с мая 2018 года — версия 4.0). Копилефт-подобное условие «производные под той же лицензией» плохо сочетается с проприетарным продуктом, а требование атрибуции почти никогда не выполняется. Скопированные пятьдесят строк — реальный, а не теоретический риск.
Датасеты, веса моделей и код, сгенерированный ассистентом. Зона, где привычные категории не работают. Лицензии на веса часто содержат ограничения по сценариям и порогам использования, то есть ведут себя как source-available, а не как открытые, и эти ограничения обязаны доехать до вашего пользователя в вашем же соглашении. К длинным сгенерированным фрагментам разумно относиться как к коду неизвестного происхождения: прогонять через тот же сканер, а не считать «своим» по умолчанию.
Во что обходится соблюдение
Разговор о цене владения полезно вести в человеко-часах. Порядки величин по опыту небольших команд, на момент лета 2026 года:
| Работа | Разовая настройка | Постоянные расходы |
|---|---|---|
| Реестр компонентов и файл политики | 1–2 дня | ревью новых зависимостей, минуты на pull request |
| Гейт в CI и генерация нотисов | 1–3 дня | близко к нулю, пока не появляются исключения |
| Экран атрибуции в приложении | полдня | автогенерация при сборке |
| Воспроизводимая source-сборка для копилефт-компонентов | 3–5 дней | хранение архивов, порядок десятков гигабайт |
| Хранение релизов под трёхлетний срок | — | плата за объектное хранилище, обычно единицы USD в месяц |
| Разбор одного конфликтного компонента с юристом | — | от нескольких часов работы юриста |
Сравните с ценой промаха: переписывание функциональности вместо GPL-библиотеки — недели; сорванная сделка из-за опросника поставщика — вся сделка; вынужденное открытие кода — стратегия компании. Асимметрия такая, что гейт окупается первым же предотвращённым случаем — тот же тип расчёта, что в юнит-экономике: дешёвая постоянная работа против редкого дорогого события.
Что спросить у юриста — и что принести с собой
Инженер не решает правовые вопросы, но он единственный, кто может корректно их сформулировать. Плохой вопрос: «у нас тут GPL, это нормально?» Хороший содержит модель поставки, способ связывания и границу артефакта. Формулировки, которые работают:
- «Мы отдаём заказчику один исполняемый файл, в который статически слинкована библиотека под LGPL-2.1. Достаточно ли для соблюдения условия о замене библиотеки отдать объектные файлы нашего продукта, или требуется динамическая линковка?»
- «Наш SaaS использует библиотеку под AGPL-3.0, вкомпилированную в серверный бинарь. Мы поправили в ней два бага. Считается ли это модификацией, при которой срабатывает пункт 13, и кому именно мы обязаны предложить исходники — всем посетителям или только авторизованным пользователям?»
- «Мы публикуем образ контейнера на базе Debian в публичный реестр. Считается ли это распространением бинарей системных пакетов с нашей стороны, и достаточно ли ссылки на архив исходников дистрибутива?»
- «Наш стандартный договор запрещает заказчику передавать поставку третьим лицам. В поставке есть GPL-компонент. Как развести эти условия, чтобы не получить нарушения лицензии?»
- «Мы хотим сменить лицензию проекта с MIT на BUSL. Что нужно от 14 внешних контрибьюторов и что делать с версиями, уже выпущенными под MIT?»
Что принести на встречу, чтобы она заняла час, а не месяц: SBOM артефакта, а не список из манифеста; схему сборки с указанием типов связывания; описание модели поставки и того, кто именно получает копию; текст вашего договора или EULA; список уже выданных исключений. Юрист без этих данных начнёт с их запроса, и это будет самый дорогой способ их собрать. Про сами договоры с заказчиком — материал о контрактах; юридические страницы этого портала — оферта, политика ПДн, возвраты — тоже живой пример того, как обязательства формулируются письменно.
Где инженерное решение принципиально невозможно. Является ли конкретная связь производным произведением; трактовка «модификации» для AGPL в вашей конфигурации; сочетание лицензии с вашим договором; юрисдикция и применимое право; риск патентов. Всё это не решается ни сканером, ни голосованием в чате.
Типичные ошибки
- Спросили о лицензии на релизе, а не в pull request; проверили прямые зависимости, забыли транзитивные. Радиус переделки растёт нелинейно, а копилефт приезжает с четвёртого уровня дерева чаще, чем с первого.
- Считают, что SaaS отменяет копилефт, и забывают про фронтенд-бандл. Отменяет для GPL, не отменяет для AGPL и совсем не отменяет, если завтра появится on-premise-версия; а браузер, получивший объектный код, — это состоявшаяся передача.
- Ссылка на апстрим вместо своих исходников — или ссылка есть, а кода за ней нет. Обязательство про запущенную версию с вашими правками; половина работы выглядит как целая (см. случай it-tools выше).
NOASSERTIONигнорируют, нотисы разрешительных лицензий не сохраняют, ассеты и шрифты держат вне реестра. Пакет без лицензии не «свободный по умолчанию», а «запрещённый по умолчанию»; самое лёгкое обязательство нарушают чаще всех;CC BY-NCв коммерческом продукте — классика игровых проектов.- Список исключений без сроков годности. За два года превращается в главный источник неучтённого риска.
- Смена лицензии апстримом не отслеживается, прошлую версию не умеют пересобрать. Минорное обновление меняет правовой статус компонента, а обязательство выполнимо только при воспроизводимой сборке.
GPL-2.0-onlyиGPL-2.0-or-laterсчитают одним и тем же. Разница определяет совместимость со всей третьей веткой.
Мини-итог
Лицензия — это условие на графе сборки и на способе поставки: она отвечает на вопрос, что вы обязаны сделать в момент, когда копия уходит другому лицу. Практический минимум для любого проекта, где софт выходит наружу:
- Реестр компонентов ведётся из артефакта, а не из манифеста. SBOM собирается на каждой сборке и хранится вместе с релизом.
- Политика лицензий лежит в репозитории и ревьюится как код. Три списка —
allow,review,deny— плюс исключения со сроком годности. - Гейт ломает сборку, а не пишет предупреждение.
NOASSERTION— вdeny. - Решение принимается по самой жёсткой из планируемых моделей поставки, а не по текущей.
- Сборка воспроизводима — без этого обязательство отдать исходники физически невыполнимо.
- Нотисы и экраны атрибуции генерируются автоматически, а ассеты, шрифты и веса моделей учитываются наравне с кодом.
- Правовые вопросы задаются юристу в формулировках, содержащих модель поставки и способ связывания.
Проверьте себя одним вопросом: если завтра получатель вашей сборки попросит Corresponding Source версии, выпущенной девять месяцев назад, — соберёте? Если ответ «наверное», то ответ «нет».
Источники
- Тексты лицензий и SPDX-идентификаторы — канонический справочник, который используют все сканеры.
- GPL FAQ от FSF — позиция авторов лицензии по линковке, границе работы и простому объединению; тексты GPL-3.0 и AGPL-3.0 — пункты 6 и 13 стоит прочитать целиком, они короткие.
- Apache License 2.0 — пункты 3 и 4 про патенты и NOTICE; The Open Source Definition, OSI — критерий отнесения лицензии к открытым.
- REUSE Specification — практическая разметка репозитория, включая файлы без комментариев.
- Copyleft and the GNU General Public License: A Comprehensive Tutorial and Guide — подробное руководство от Software Freedom Conservancy; Open Source Licensing: Software Freedom and Intellectual Property Law Lawrence Rosen — классическая книга, объясняющая механику, а не лозунги.
- CycloneDX и SPDX — форматы SBOM, где лицензия является полем первого класса; choosealicense.com — короткий справочник GitHub для выбора лицензии своего проекта.
Что дальше
Мы разобрали лицензию как ограничение: что она запрещает, где проходит граница, как проверять соблюдение автоматически. Но у открытого кода есть и обратная сторона — это полноценный способ поставки со своей экономикой и своей ценой владения, которую платит уже не потребитель компонента, а тот, кто его публикует.