GitHub AI Scan для PHP: изолированный тест и границы проверки
GitHub AI Scan для PHP: изолированный тест
Проверка источников: 16 октября 2026 года, Екатеринбург (UTC+5). Подготовлен рецепт, не отчёт об исполненном AI Scan. Платная функция, политики и рабочие workflows SEO Recipes не включались. Исходники закрытых проектов не передавались. Локальная проверка PHP описана отдельно в конце.
Что выпущено и где применимо
15 октября GitHub объявил GA AI Scan для pull requests. Он дополняет CodeQL для поддерживаемых изменений, включая PHP, Shell/Bash, HCL и Dockerfile. Проверка запускается для подходящего PR при включённых code scanning и эффективной настройке AI Scan. PR из форков и от Dependabot не обрабатываются. Замечания с меткой AI появляются в обсуждении и diff, но не блокируют объединение через rulesets.
По тому же анонсу, публичные репозитории личных аккаунтов и организаций поддерживаются; для private/internal репозитория организации требуется Code Security или Advanced Security. Лицензия Copilot не обязательна, однако выполнение расходует Code Security AI Credits. Доступность функции не означает бесплатное использование без лимитов.
Документация AI Scan уточняет: приватные личные репозитории не поддерживаются; CodeQL default setup и успешное завершение CodeQL не являются обязательными условиями. AI Scan анализирует PR, не весь репозиторий, и не создаёт такой же backlog в Security, как CodeQL. Нельзя считать отсутствие замечания доказательством безопасности.
Для SEO Recipes пример внутри Markdown не считается проверенным PHP-изменением. Ниже предполагаются настоящие .php-файлы в отдельном учебном репозитории. Тест не требует изменения Laravel production или запуска публичного веб-сервера.
Сначала определить плательщика и область включения
До запуска владелец должен проверить биллинг Advanced Security: включённые кредиты, счёт списания, бюджет и прекращение расхода при достижении лимита. Не следует предполагать универсальную цену одного PR.
Для личного публичного репозитория плательщиком обычно служит личный аккаунт; если лицензию Copilot предоставляет организация/enterprise, документация допускает списание на её счёт. При отсутствии доступных кредитов из-за жёсткого бюджета findings не появятся: это не успешная проверка без уязвимостей.
Важно для организаций: включение общего параметра сразу охватывает подходящие репозитории с code scanning. Для ограниченного пилота сначала согласуйте эту область и бюджет. Не включайте настройку всей организации только ради одного примера. Для личного публичного учебного репозитория администратор управляет переключателем AI Scan в разделе Code scanning. Названия и наследование настроек сверяйте с текущей документацией.
Запишите согласованные репозиторий, владельца расходов, предельный бюджет и ответственного за отключение. Пока разрешение на расход отсутствует, остановитесь на чтении документации и локальной проверке синтаксиса.
Учебный сценарий: запрос к базе по адресу почты
Создайте отдельный репозиторий без реальных ключей, пользовательских данных, Actions secrets, деплоя и публикации зеркала. Используйте только условные адреса домена .invalid. Ветки для PR должны принадлежать этому же репозиторию; fork для данного испытания не подходит.
Следующий фрагмент — заведомо небезопасный учебный вариант lookup.php. Не подключайте его к HTTP, реальной базе или production. Само объявление функции не выполняет SQL; оно даёт анализатору небольшой пример для проверки.
<?php
declare(strict_types=1);
function lookupByEmail(PDO $db, string $email): array
{
$sql = "SELECT id, email FROM users WHERE email = '" . $email . "'";
return $db->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
В отдельном PR добавьте файл и поясните в описании, что $email моделирует значение, полученное от клиента. Ожидаемая проверяемая гипотеза, а не обещанный результат продукта: анализатор может обнаружить небезопасное включение строки в SQL. Зафиксируйте commit SHA, статус запуска, расход, наличие и текст findings. Отсутствие результата отделяйте от подтверждённого исполнения без замечаний.
После изучения finding замените содержимое файла следующим вариантом, сохранив имя функции:
<?php
declare(strict_types=1);
function lookupByEmail(PDO $db, string $email): array
{
$statement = $db->prepare(
'SELECT id, email FROM users WHERE email = :email'
);
$statement->execute(['email' => $email]);
return $statement->fetchAll(PDO::FETCH_ASSOC);
}
Это демонстрация параметризации значения по документации PDO::prepare. Она не решает отдельно авторизацию пользователя, ограничения выборки, обработку ошибок приложения и подстановку имён таблиц/столбцов. Для примера ниже включён режим исключений PDO; поведение драйвера и настройки реальной БД проверяются отдельно.
Локальная приёмка исправленного варианта
Сохраните следующий файл как check_lookup.php рядом с исправленным lookup.php. Требуются PHP 8.2+ и pdo_sqlite. Он работает только в CLI с синтетической SQLite-базой в памяти: сетевых запросов, файлов базы и секретов нет.
<?php
declare(strict_types=1);
if (PHP_SAPI !== 'cli') {
exit(1);
}
if (!extension_loaded('pdo_sqlite')) {
fwrite(STDERR, "SKIP: pdo_sqlite is required\n");
exit(77);
}
require __DIR__ . '/lookup.php';
$db = new PDO('sqlite::memory:', null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$db->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL)');
$insert = $db->prepare('INSERT INTO users (id, email) VALUES (:id, :email)');
$insert->execute(['id' => 1, 'email' => 'reader@example.invalid']);
$insert->execute(['id' => 2, 'email' => "o'connor@example.invalid"]);
$cases = [
['reader@example.invalid', [1]],
["o'connor@example.invalid", [2]],
['missing@example.invalid', []],
["' OR 1=1 -- ", []],
];
foreach ($cases as $number => [$email, $expected]) {
$rows = lookupByEmail($db, $email);
$actual = array_map('intval', array_column($rows, 'id'));
if ($actual !== $expected) {
fwrite(STDERR, 'FAIL: case ' . ($number + 1) . "\n");
exit(1);
}
}
fwrite(STDOUT, "PASS: 4 synthetic cases\n");
Сначала проверьте php -v и php -m, затем выполните php -l lookup.php, php -l check_lookup.php и php check_lookup.php. Вывод PASS ожидается только при фактическом прохождении четырёх случаев. Код 77 — пропуск, не успех; ошибка и исключение требуют расследования. Не запускайте файл через веб-сервер.
Подготовленный набор — минимальная регрессия, не доказательство безопасности всего приложения. Для Laravel добавьте собственные feature-тесты проверки доступа и обработки ошибочного ввода; SQLite не заменяет проверку выбранного MySQL/PostgreSQL-драйвера.
Как оценивать AI Scan отдельно от тестов
| Контрольная точка | Что записать в учебный отчёт |
|---|---|
| Условия запуска | Владелец/видимость репозитория, тип PR, эффективная настройка и доступный бюджет |
| Первая версия | SHA небезопасного примера, подтверждение запуска и finding либо отсутствие результата |
| Исправление | SHA после ручного ревью; передано ли значение отдельно от SQL, не ослаблена ли авторизация |
| Повторный анализ | Фактический исход нового запуска; отсутствие finding не подменяет локальные тесты |
| Расход | Наблюдаемое потребление по выбранному счёту, не оценка по количеству комментариев |
| Завершение | Учебный PR закрыт без публикации уязвимого приложения; настройка и бюджет возвращены в согласованное состояние |
Не объединяйте предложенный autofix автоматически. При ложном срабатывании сохраните минимальный воспроизводимый случай и объяснение. Для дополнительных PHP/Shell/HCL/Dockerfile-примеров используйте ту же схему с отдельными безопасными фикстурами; результат SQL-примера не переносится на все языки и типы дефектов.
Что проверено при подготовке
16 октября локально выполнена проверка синтаксиса трёх PHP-блоков на PHP 8.4.24. В среде есть PDO, но нет pdo_sqlite: запуск check_lookup.php завершился ожидаемым SKIP / код 77, без SQL-операций. Четыре сценария базы поэтому не заявляются пройденными.
AI Scan, настройки GitHub, лицензия и расход реального аккаунта не проверялись; учебный PR с PHP-кодом на GitHub не создавался. Этот документационный PR сам по себе не является испытанием сканера. Полная сборка сайта и результаты проверки документации оцениваются отдельно.
