dimhold.by
← Статьи

codecard отказывается рисовать сниппет, который выдаёт кодовую базу. Все 569 строк пакета Kotlin, которые я ему дал, он пропустил

В августе я открыл codecard. Он рисует сниппет кода или лога как карточку для ленты. Перед тем как рисовать, он читает текст и отказывается, если текст выдаёт кодовую базу. То есть строка пакета, импорт вашего собственного кода, путь сборки с именем пользователя, внутренний хост или что-то похожее на учётные данные. Отказ у него жёсткий: код возврата 2 и никакого файла. Я сделал так потому, что предупреждение в час ночи пролистывают, а картинка остаётся в чужой ленте.

У гарда 11 правил и 171 тест и все они проходят. Но каждый случай в этих тестах написан для этих тестов. Пример в README это файл на Java, который я придумал так, чтобы в нём было ровно 5 утечек. Гард ни разу не видел кода, который писал не я, поэтому я запустил его на чужом коде.

Я взял codecard с GitHub на последнем коммите, f10a09e, на Python 3.12. Рядом положил 9 публичных репозиториев на их последнем коммите до 1 октября. Это 8 языков от Java до Rust, среди них spring-petclinic и vite. Из каждого репозитория я взял до 300 файлов. Из каждого файла взял 2 окна по 15 строк: начало файла и одно случайное окно из остального. Второе окно примерно то, что я копирую, когда хочу показать функцию.

Как часто он отказывает публичному коду

Всё в этом корпусе уже опубликовано, так что каждый отказ здесь это отказ коду, который не секрет. На случайных окнах из середины файла он отказал в 17 случаях из 790, это 2,2%. На первых строках файлов он отказал в 138 из 1200. 132 из них это файлы C# в MediatR, где namespace стоит в первых 15 строках. По его же определению гард тут прав: namespace действительно называет кодовую базу. Эта база публичная и для такого случая есть --guard off.

Часть из 17 это шум. В express он отказал тесту с URL /users/0-2. Правило путей ищет среди прочего /Users/ на macOS и /home/ на Linux. Но codecard компилирует все правила по умолчанию с re.I, поэтому маршрут в нижнем регистре тоже подходит. По всем файлам корпуса правило путей срабатывает на 107 строках и 23 из них только из-за этого флага. В okhttp он отказал импортам org.robolectric и org.bouncycastle. У правила приватных импортов есть белый список из 26 имён групп, а любое другое имя оно считает вашим собственным кодом.

2,2% это цена, которую я принял в августе вслепую. Теперь я знаю число. Отказ стоит мне одного --guard off. С флагом регистра другое дело, его надо чинить.

Строка, которую он обещал ловить

Потом я прогнал гард по строке объявления каждого файла, без выборки и по одной строке. Java дала 106 пойманных из 106, C# дал 133 из 133. Kotlin дал 0 из 569.

правило: ^[ \t]*package\s+[\w.]+\s* ; Java package com.acmebank.ledger.settlement ; поймано в 106 из 106 файлов Kotlin package com.acmebank.ledger.settlement этого ждёт правило поймано в 0 из 569 файлов
Строка пакета каждого файла корпуса, проверенная отдельно. Правило для namespace в C# допускает строку без точки с запятой и поймало 133 из 133.

Правилу нужна точка с запятой в конце строки пакета, а Kotlin её там не ставит. Когда правило срабатывает, оно печатает причину “a Java or Kotlin package name is your company and product, spelled out”, то есть имя пакета Java или Kotlin это название вашей компании и продукта открытым текстом. Эту фразу и регулярку я писал, глядя на файл Java. Тесты к ним тоже на Java.

Это хуже, чем одна пропущенная строка. Я взял пример с утечками из README и переписал его на Kotlin. Гард всё равно отказывает, с 4 находками: импорт, ключ, хост и путь. Пакета в списке нет. Когда я исправляю 4 вещи, о которых он сказал, карточка рисуется с package com.acmebank.ledger.settlement в первой строке и кодом возврата 0. Тот же файл с точкой с запятой даёт код 2. В час ночи список выглядит полным, так что я бы исправил эти 4 и опубликовал.

Рядом со сканером секретов

Потом я захотел узнать, что гард пропускает. Настоящие утечки я посчитать не могу, поэтому составил список из 31 формы утечки, какие бывают при вставке из редактора или в логе. Я прогнал их через codecard и через gitleaks 8.30.1, сканер, который я бы поставил на репозиторий. Секреты в списке это случайные строки нужного формата, они генерируются при запуске.

codecard gitleaks выдаёт кодовую базу (18) 10 0 пакет Java пакет Kotlin namespace C# импорт Java импорт Kotlin импорт Python импорт Go импорт TypeScript C:\Users\ в трассировке .NET путь /home/ в ошибке путь /Users/ в стеке путь в трассе Python путь с ~ URL https://*.internal *.internal:5432 без схемы *.svc.cluster.local хост на домене компании адрес 10.x секреты (13) 6 12 API_KEY = "..." строка .env без кавычек пароль в YAML без кавычек Authorization: Bearer заголовок token в curl ключ AWS AKIA... секретный ключ AWS блок приватного ключа JWT postgres://user:pass@ JDBC ?password= вебхук Slack api_key="sk-proj-..."
31 форма утечки, по строке на каждую. Закрашенная точка значит, что инструмент строку поймал, синяя подпись значит, что не поймал ни один. Секреты это случайные строки нужного формата.

Эти 2 инструмента почти не пересекаются. Из 18 форм, которые выдают кодовую базу, codecard поймал 10, а gitleaks ни одной. Это справедливо: gitleaks сделан для секретов. Из 13 секретов gitleaks поймал 12, а codecard 6. Его правило для учётных данных видит значение только в кавычках после имени вроде api_key или password. Строка из файла .env рисуется. Рисуются и пароль в YAML без кавычек и заголовок Authorization: Bearer в логе и URL вебхука Slack. Вот это немного обидно. Тему ember я сделал для упавших логов, а в логе токен Bearer как раз стоит без кавычек.

Вместе они поймали 23 из 31. Все 8, которых не поймал ни один из них, выдают кодовую базу. Среди них пакет Kotlin и внутренний хост без https:// в начале. Труднее всего хост на собственном домене компании и я не представляю, как его ловить, не зная самой компании. В README уже написано, что гард не знает, как называется ваша компания.

Первая строка README

Установка в README начинается с pip install codecard. Я запустил её в чистом virtualenv и pip не нашёл ни одного дистрибутива. Имя на PyPI вообще не зарегистрировано. Выходит, README инструмента, который существует ради защиты от утечек, предлагает ставить имя, которое может зарегистрировать кто угодно. Так было с середины августа, а я не замечал.

Чего я не проверял

31 форма это мой собственный список, поэтому он говорит, какие формы проходят, а не как часто люди их вставляют. Корпус это 9 репозиториев и 1 случайное зерно. Другой размер окна мог бы сдвинуть 2,2%. trufflehog я не запускал. И я ещё ничего не починил. Точка с запятой, флаг регистра, секреты без кавычек и имя на PyPI так и остаются открытыми в codecard на f10a09e.