Files
brain/docs/superpowers/plans/2026-06-15-commit-msg-anchor-fix-ceremony.md
T

117 lines
5.9 KiB
Markdown

# 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` — +спуф-тест в describe `buildCommitMessageUrlPattern`.
- 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:
```javascript
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:
```javascript
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:
```javascript
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:
```javascript
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'ы.
```skills-json
["test-driven-development"]
```
```steps-json
[
{"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"}
]
```
```verified-context-json
[
{"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"}
]
```