mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Три вещи, которые аудит 12.09 назвал в §10. **Перемещаемые ссылки.** `home-assistant/actions/hassfest@master` и `hacs/action@main` — это произвольный будущий коммит чужой ветки, а ревьюера с Read/Write/Bash запускал перемещаемый major `anthropics/claude-code-action@v1`. Все 116 `uses:` в девяти воркфлоу закреплены полным SHA с комментарием-версией; `scripts/action-pins.mjs` это проверяет, а предполётный вердикт Validate — исполняет. Локальная переиспользуемая workflow пина не требует и исключена явно. **Права.** Один блок `permissions` на весь конвейер выдавал `issues: write` и OIDC каждой стадии, включая единственную недоверенную — работу модели. Теперь права выдаются по job: модели только чтение и OIDC для самой `claude-code-action`, писать в issue умеют детерминированные стадии. **Граница.** Разбор запечатанного результата переехал из inline-shell в `scripts/review-result-gate.mjs` — не ради красоты, а потому что в YAML его нельзя прогнать ни одним отрицательным случаем. Проверяются те же вещи, что и раньше, и в том же объёме: точный набор файлов, контрольные суммы, схема паспорта и совпадение КАЖДОГО из семнадцати полей с тем, что посчитала детерминированная стадия. Сверху — пятнадцать враждебных фикстур: неполный набор, лишний файл, подменённое содержимое, чужой run и попытка, устаревший material_sha и tree, чужие задача, этап, раунд и ветка, вердикт вне словаря, пустой документ, manifest не о тех файлах, неразбираемый JSON. Настоящих секретов и привилегированных операций фикстуры не трогают. Issue: #556 User-Visible: no
68 lines
3.7 KiB
JavaScript
68 lines
3.7 KiB
JavaScript
// #556: перемещаемая ссылка в `uses:` — это доверие чужому владельцу тега здесь
|
||
// и сейчас, а не коду, который читали. До правки конвейер брал
|
||
// `home-assistant/actions/hassfest@master` и `hacs/action@main`, то есть
|
||
// произвольный будущий коммит чужой ветки, а ревьюера с Read/Write/Bash
|
||
// запускал перемещаемый major `anthropics/claude-code-action@v1`.
|
||
import assert from 'node:assert/strict';
|
||
import test from 'node:test';
|
||
import { readFileSync } from 'node:fs';
|
||
|
||
import {
|
||
auditRepository, auditWorkflowSource, isLocal, isPinned, hasVersionNote, listWorkflows,
|
||
} from '../scripts/action-pins.mjs';
|
||
|
||
test('#556: в репозитории не осталось незакреплённых Actions', () => {
|
||
assert.deepEqual(auditRepository(), []);
|
||
});
|
||
|
||
test('#556: проверка смотрит на все воркфлоу, а не на один', () => {
|
||
const files = listWorkflows();
|
||
assert.ok(files.length >= 9, `воркфлоу найдено ${files.length}`);
|
||
assert.ok(files.includes('process.yml') && files.includes('validate.yml'));
|
||
});
|
||
|
||
test('#556: локальная переиспользуемая workflow пина не требует', () => {
|
||
assert.equal(isLocal('./.github/workflows/announce.yml'), true);
|
||
assert.deepEqual(auditWorkflowSource('x.yml', ' uses: ./.github/workflows/announce.yml\n'), []);
|
||
});
|
||
|
||
test('#556: перемещаемая ссылка — находка, в какой бы форме ни пришла', () => {
|
||
for (const spec of [
|
||
'actions/checkout@v7', 'hacs/action@main', 'home-assistant/actions/hassfest@master',
|
||
'anthropics/claude-code-action@v1', 'owner/repo@abc1234',
|
||
]) {
|
||
assert.equal(isPinned(spec), false, spec);
|
||
const found = auditWorkflowSource('x.yml', ` - uses: ${spec}\n`);
|
||
assert.equal(found.length, 1, spec);
|
||
assert.match(found[0], /не закреплён полным SHA/);
|
||
}
|
||
});
|
||
|
||
test('#556: SHA без комментария с версией — тоже находка', () => {
|
||
const sha = 'a'.repeat(40);
|
||
assert.equal(isPinned(`owner/repo@${sha}`), true);
|
||
assert.equal(hasVersionNote(' # v7'), true);
|
||
assert.equal(hasVersionNote(''), false);
|
||
const bare = auditWorkflowSource('x.yml', ` - uses: owner/repo@${sha}\n`);
|
||
assert.equal(bare.length, 1);
|
||
assert.match(bare[0], /без комментария с версией/);
|
||
assert.deepEqual(auditWorkflowSource('x.yml', ` - uses: owner/repo@${sha} # v7\n`), []);
|
||
});
|
||
|
||
test('#556: находка называет файл и строку', () => {
|
||
const source = ['jobs:', ' a:', ' steps:', ' - uses: hacs/action@main'].join('\n');
|
||
assert.match(auditWorkflowSource('.github/workflows/x.yml', source)[0],
|
||
/^\.github\/workflows\/x\.yml:4: /);
|
||
});
|
||
|
||
// Проверка обязана стоять в предполётном вердикте Validate, иначе она есть, но
|
||
// не сработает ни на одном пуше.
|
||
test('#556: preflight Validate считает пины частью вердикта', () => {
|
||
const workflow = readFileSync(new URL('../.github/workflows/validate.yml', import.meta.url), 'utf8');
|
||
const preflight = workflow.slice(0, workflow.indexOf('\n changes:'));
|
||
assert.match(preflight, /^\s+run: node scripts\/action-pins\.mjs$/m, 'шаг запускает проверку');
|
||
assert.match(preflight, /ACTION_PINS: \$\{\{ steps\.action_pins\.outcome \}\}/);
|
||
assert.match(preflight, /^\s+check "пины сторонних Actions" "\$ACTION_PINS"$/m,
|
||
'исход попадает в вердикт, а не теряется в continue-on-error');
|
||
});
|