PunchKano begins diphtheria immunisation in five Kano LGsRTP DesportoFrancisco Cabral na final de pares em HangzhouESPN DeportesGrecia derrotó a Alemania por primera vez en la historia y golpeó a Klopp en su debut ante su públicoThe Jerusalem PostIsrair awaits final approval for Tokyo, Miami flights, adds new European destinations for 2027Daily MaverickWe worried AI would make things up, we should also worry when it doesn’tInquirerRains to continue in Visayas, Mindanao due to ITCZ until Sept. 30Bollywood HungamaVinod Kapri's Pyre to release in theaters on October 23, 2026BlickNächstes Amt abgelegt: Jens Spahn zieht sich aus Haushaltsausschuss zurückRapplerPhilippines should fix tax gaps and procurement, not raise tax rates – WBSportstarIndia in Athletics LIVE Updates, Asian Games 2026: Vithya Ramraj breaks National Record to win 400m hurdles bronze; Javelin throw final at 4:45 PM ISTIl Fatto QuotidianoMorto Stefano Milani, il tifoso del Milan diventato famoso su X. Da Bertolucci a Valenti: “Ha lottato come nessuno, mai un passo indietro”7sur7“Pourquoi je perds toujours?”: quand André Agassi taquine Alexander Zverev
The Daily Newsstand · Free, Always
Monday, September 28, 2026

Claude Code сливает ключи в коммиты вдвое чаще среднего: почему хук PreToolUse их пропускает

Translate

В публичных коммитах, сделанных с помощью Claude Code, секреты встречаются в 3,2% случаев, а в среднем по GitHub — в 1,5% (GitGuardian). Кто вписал ключ, агент или человек, по этим данным не видно. Запретить это строкой в CLAUDE.md мало: на длинной сессии агент правило теряет, поэтому ключи я проверяю хуком PreToolUse перед каждым вызовом инструмента. Но простой хук ключ пропустит: он срабатывает до git add и видит пустой индекс, а упав с ошибкой, не блокирует ничего.

CLAUDE.md не гарантирует, что Claude выполнит правило

По документации Claude Code, CLAUDE.md — это инструкции человека, которые подгружаются в каждую сессию. Гарантии исполнения там нет: правило лежит в том же контекстном окне, что и вся переписка. А факт из середины длинного контекста языковая модель находит хуже (Liu et al.). Правило из начала сессии к середине работы попадает как раз туда. У меня агент так однажды нарушил прямой запрет из CLAUDE.md и ради одной цифры для статьи запустил платную серию запросов к API.

Агент Replit летом 2025 года нарушил прямой запрет заказчика на изменения и удалил рабочую базу с данными больше тысячи компаний (AIID).

Как написать хук Claude Code, который остановит команду агента, даже если сам упадёт

Claude Code hooks — это скрипты, которые Claude Code запускает на событиях сессии. Перед вызовом инструмента событие PreToolUse приходит на stdin JSON-объектом с полями tool_name и tool_input. Код выхода 2 блокирует вызов, текст из stderr агент получает как причину отказа; второй способ — выйти с 0 и вернуть JSON с permissionDecision: deny. (документация).

Хук подключается в .claude/settings.json на PreToolUse с matcher: "Bash" и таймаутом 120 секунд. Движок один, правила лежат в JSON: какую команду ловить, какой проверкой, с какой причиной отказа.

import json, os, shlex, subprocess, sys

ROOT = os.environ.get("CLAUDE_PROJECT_DIR", ".")

def segments(command):  # && || ; | и перевод строки, кавычки учтены
    lex = shlex.shlex(command, posix=True, punctuation_chars=";&|\n")
    lex.whitespace, lex.whitespace_split = " \t\r", True
    seg = []
    for tok in [*lex, "\n"]:
        if set(tok) <= set(";&|\n"):
            while seg and "=" in seg[0]:  # VAR=x git push
                seg.pop(0)
            yield seg
            seg = []
        else:
            seg.append(tok)

def check(payload):
    rules = json.load(open(os.path.join(ROOT, ".claude/rules.json")))
    for seg in segments(payload["tool_input"]["command"]):
        for rule in rules:
            prefix = rule["command"].split()
            head = [os.path.basename(t) for t in seg[:1]] + seg[1:len(prefix)]
            if head != prefix:
                continue  # команду упомянули, но не запускают
            args = seg[len(prefix):]  # аргументы именно этой части
            done = subprocess.run(shlex.split(rule["require"]) + args,
                                  cwd=ROOT, capture_output=True, timeout=100)
            if done.returncode:
                return f"{rule['reason']}: {shlex.join(seg)}"

def main():
    try:
        reason = check(json.load(sys.stdin))
    except Exception as e:  # сбой, таймаут, кривой вход
        reason = f"hook error: {e}"
    if reason:
        print(reason, file=sys.stderr)
        sys.exit(2)

if __name__ == "__main__":
    main()
Шесть шагов вызова через хук: от решения агента до exit 0 или exit 2 с причиной в stderr

Упавший хук для агента выглядит так же, как нарушенное правило.

Команда ./deploy.sh хорошая.md && ./deploy.sh плохая.md даёт две части, и проверка идёт дважды, каждая со своим файлом; поиск по подстроке пропустил бы её целиком. Слова git push в сообщении коммита правило не тронут. Непойманное исключение в Python даёт код 1, а любой код, кроме 2, Claude Code считает неблокирующим, поэтому хук ловит всё, включая таймаут проверки, и выходит с 2. В моём движке отказ идёт JSON-ответом deny, код 2 — для сбоев; в примере для краткости всё идёт через код 2.

Почему хук не замечает ключ API, если агент добавляет файл и коммитит одной командой

Для безопасности ИИ-агентов главный рубеж — коммит. Первая версия поиска ключей перед git commit и git push сканировала файлы на диске. Ревью кода нашло в ней два пропуска: ключ можно добавить в индекс, стереть с диска и закоммитить, а неотправленные коммиты перед push она не смотрела вовсе.

Есть и третья ловушка: хук срабатывает до команды. Агенты часто коммитят одной командой git add -A && git commit или git commit -a, и в момент проверки индекс ещё пуст. Проверка перед коммитом должна смотреть индекс, правки против HEAD и новые файлы, перед push — все коммиты, которых нет на удалённом репозитории. Я пришёл к такой версии:

#!/usr/bin/env bash
# scan_secrets.sh commit|push; код 2 — ключ или сбой git
set -uo pipefail
added() { "$@" | sed -n '/^+++ /d; s/^+//p'; }  # только добавленные строки
changes() {
  if [ "$1" = commit ]; then
    added git diff --cached --no-color --no-ext-diff || return 1
    if git rev-parse -q --verify HEAD >/dev/null; then  # в пустом репозитории HEAD нет
      added git diff HEAD --no-color --no-ext-diff || return 1
    fi
    git ls-files -o --exclude-standard -z | while IFS= read -r -d '' f; do cat -- "$f"; done
  else
    added git log -p --no-color -m --branches --not --remotes
  fi
}
out=$(mktemp) || exit 2
changes "$1" > "$out" || { rm -f "$out"; echo "git не ответил" >&2; exit 2; }
# шаблон упрощён, в работе возьмите gitleaks или trufflehog
grep -qaE '(^|[^A-Za-z0-9_])(sk-|ghp_|AKIA)[A-Za-z0-9_-]{16,}' "$out"; rc=$?; rm -f "$out"
[ "$rc" = 1 ] || { echo "похоже на ключ" >&2; exit 2; }
Где искать ключ перед коммитом: в индексе, в правках против HEAD и новых файлах, в неотправленных коммитах; упавшая проверка блокирует вызов

Ключ, стёртый с диска после git add, всё равно уезжает в коммит.

Тот же отчёт GitGuardian нашёл больше двадцати тысяч секретов в публичных конфигах MCP. Конфиги MCP с ключами держите в .gitignore и отдавайте сканеру отдельно.

Какие действия агента проверять хуком, а какие оставить правилом в CLAUDE.md

Хуки у меня стоят на всех ИИ-агентах для программирования, и правило очень простое: если откат ошибки дороже пяти минут разговора с агентом, правило проверяет код. Стиль, тон и порядок работы остаются правилами в CLAUDE.md → такую ошибку агент исправит одним ответом.

  1. Начните с двух правил: ключи перед commit и push (в паре с родными git-хуками, о них ниже), проверка перед выкладкой.

  2. Режьте составную команду на части и передавайте в проверку аргументы своей части.

  3. Сбой и таймаут хука блокируют: таймаут проверки меньше таймаута хука, любое исключение — код 2.

  4. На каждое правило держите сценарии регресса: команды на пропуск и на блок. У меня их несколько десятков, новый обычно появляется после поломки.

  5. Файл с правилами хуков агент не должен править сам, нужен хук на Edit|Write по пути файла.

Пятый пункт я проверил на себе: регулярка в правилах блокировала лишнее, и Claude Code не дал агенту без моего подтверждения поправить файл с правилами хуков. Правила разрешений ведут себя не так, как ждёшь (issue #27333). На составных командах они не срабатывают, поэтому на dev.to проверку тоже переносят в хук PreToolUse (dev.to).

Что ещё очень важно — хук видит команду, а не намерение, и git -C другая/папка push или вызов через свой скрипт не поймает. Ключи надёжнее ловить родными git-хуками pre-commit и pre-push, они срабатывают уже после git add, а хуком агента запретить --no-verify.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.