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

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 — +спуф-тест в 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:
  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"}
]