5.9 KiB
Commit-Message Lookahead Anchor — Security Fix — Ceremony Plan
For agentic workers: REQUIRED SUB-SKILL: superpowers:executing-plans (инлайн под стеной). Steps — checkbox (
- [ ]).
Goal: Закрыть subdomain-спуф обход в buildCommitMessageUrlPattern — добавить host-терминатор в negative-lookahead, зеркаля buildNavigateWhitelistPatterns.
Architecture: Одна правка в tools/url-whitelist-rules.mjs (возврат билдера) + спуф-тест в tools/url-whitelist-rules.test.mjs. TDD: RED спуф → якорь → GREEN.
Tech Stack: Node.js ESM (.mjs), vitest (npx vitest run --root .).
Спек: docs/superpowers/specs/2026-06-15-commit-msg-anchor-fix-spec.md (D1 дефект, D2 фикс, D3 инвариант, D4 edge, D5 критерий).
File Structure
- Modify:
tools/url-whitelist-rules.test.mjs— +спуф-тест в describebuildCommitMessageUrlPattern. - Modify:
tools/url-whitelist-rules.mjs— возвратbuildCommitMessageUrlPattern(общий host-терминатор).
Task 1: anchor commit-message lookahead
- Step 1 — failing test. Edit
tools/url-whitelist-rules.test.mjs. old_string:
it('empty → liderra blocked (fail-CLOSED), anthropic ok', () => {
const re = buildCommitMessageUrlPattern([]);
expect(re.test('see https://liderra.ru/x')).toBe(true);
expect(re.test('see https://docs.anthropic.com/x')).toBe(false);
});
});
new_string:
it('empty → liderra blocked (fail-CLOSED), anthropic ok', () => {
const re = buildCommitMessageUrlPattern([]);
expect(re.test('see https://liderra.ru/x')).toBe(true);
expect(re.test('see https://docs.anthropic.com/x')).toBe(false);
});
it('flags subdomain-suffix spoof of a whitelisted host (anchor)', () => {
const re = buildCommitMessageUrlPattern(['liderra.ru', 'github.com/liderra']);
expect(re.test('see https://liderra.ru.evil.com/x')).toBe(true);
expect(re.test('see https://github.com/liderra-evil/x')).toBe(true);
expect(re.test('see https://liderra.ru/admin')).toBe(false);
});
});
-
Step 2 — RED. Run:
npx vitest run --root . tools/url-whitelist-rules.test.mjs→ FAIL (спуф ожидает true, но неякорённый паттерн гасится префиксом → false). -
Step 3 — anchor builder. Edit
tools/url-whitelist-rules.mjs. old_string:
export function buildCommitMessageUrlPattern(projectDomains) {
const proj = (projectDomains || []).filter((d) => typeof d === 'string' && d);
const frags = [...BASE_COMMIT_MSG_FRAGS, ...proj.map(escapeDomain)];
return new RegExp('\\bhttps?:\\/\\/(?!' + frags.join('|') + ')\\S+', 'i');
}
new_string:
export function buildCommitMessageUrlPattern(projectDomains) {
const proj = (projectDomains || []).filter((d) => typeof d === 'string' && d);
const frags = [...BASE_COMMIT_MSG_FRAGS, ...proj.map(escapeDomain)];
// Host-terminator (?:[:/?#]|$) after the allowlist alternation closes subdomain-suffix
// spoofs (liderra.ru.evil.com / github.com/liderra-evil); mirrors buildNavigateWhitelistPatterns.
return new RegExp('\\bhttps?:\\/\\/(?!(?:' + frags.join('|') + ')(?:[:/?#]|$))\\S+', 'i');
}
- Step 4 — GREEN. Run:
npx vitest run --root . tools/url-whitelist-rules.test.mjs→ PASS (спуф флагуется, легитимные кейсы сохранены).
Коммит (через escape, как в прошлой церемонии) после GREEN; авторитетный полный свод — терминал владельца.
Переговоры (позиция контроллера)
- Каждый мутирующий шаг проверяем одиночным
npx vitest run(DR-1); цепочек нет. - Нет двух Edit одного файла подряд: Step 1 правит тест-файл, Step 3 — модуль (разные файлы), между ними verify-шаг.
- RED и GREEN с одной командой не дублирующие: между ними мутирующий Step 3 (якорь), меняющий состояние — RED доказывает обнаружение дефекта, GREEN — его закрытие.
- Backward-compat: легитимные allow/block (
liderra.ru/x,docs.anthropic.com/x,evil.example.com/p) не меняются; меняется только спуф (теперь флагуется). - Полный свод и коммит — терминал/escape владельца (Claude-Bash рушит воркеры на vitest-import).
Self-Review
- Покрытие spec: D1 дефект → спуф-тест Step 1 (RED доказывает); D2 фикс → Step 3 якорь; D3 инвариант → Step 1 включает
liderra.ru/admin→ false; D4 edge → спуф-варианты.evil/-evilв тесте; D5 критерий → Step 4 GREEN + owner full-suite. - Заглушек нет: полные old/new diff'ы.
["test-driven-development"]
[
{"op":"Edit","object":"tools/url-whitelist-rules.test.mjs","ref":"D5"},
{"op":"Bash","object":"npx vitest run --root . tools/url-whitelist-rules.test.mjs","ref":"D5"},
{"op":"Edit","object":"tools/url-whitelist-rules.mjs","ref":"D2"},
{"op":"Bash","object":"npx vitest run --root . tools/url-whitelist-rules.test.mjs","ref":"D5"}
]
[
{"id":"pc1","kind":"EXTRACTED","ref":"tools/url-whitelist-rules.mjs","anchor":"buildCommitMessageUrlPattern"},
{"id":"pc2","kind":"EXTRACTED","ref":"tools/url-whitelist-rules.mjs","anchor":"buildNavigateWhitelistPatterns"},
{"id":"pc3","kind":"EXTRACTED","ref":"tools/url-whitelist-rules.mjs","anchor":"BASE_COMMIT_MSG_FRAGS"}
]