117 lines
5.9 KiB
Markdown
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"}
|
|
]
|
|
```
|