Перейти до вмісту

Машинний переклад. Авторитетним джерелом є англійська версія; перевірка носієм мови ще не виконана.

Claude Code

Всередині хуків PEP: політика deny-closed всередині Claude Code

Автор Olivares AI 8 хв читання

Команда платформи дозволяє Claude Code у всій своїй інженерній організації. Щоб застосувати політику викликів інструментів, вони підключають сервер хуків — локальний процес, який отримує події хуків на stdin і повертає рішення у форматі JSON на stdout. Перший тиждень пройшов нормально. PreToolUse спрацьовує перед кожним викликом інструменту, хук повертає deny для всього, що стосується виробництва, і команда вірить, що застосування політики працює.

Потім Claude Code випускає нову версію. PermissionRequest починає спрацьовувати разом із PreToolUse. Сервер хуків повертає той самий JSON permissionDecision, який він уже повертав. Claude Code приймає відповідь, розбирає її, не знаходить жодного поля, яке вона розпізнає для цієї події, і продовжує так, ніби рішення не було надано. Відмова ігнорується без повідомлення. Точка застосування політики в команді тепер є стіною із gap у ній, і в журналах нічого не вказує на це.

Ось проблему, яку має вирішити керований PEP: життєвий цикл хуку Claude Code - це не одна подія з одним форматом проводки. Це приблизно 30 подій, кожна з різною схемою вихідних даних, яку дотримується середовище виконання, і помилка в схемі не відрізняється від відсутності сигналу.

Механізм проводки є контрактом безпеки

Причина, чому одне поле permissionDecision не працює скрізь, полягає в тому, що хук-події Claude Code еволюціонували незалежно. Форма вихідних даних, яка забезпечує виклик інструменту, не є формою, яка забезпечує подачу запиту, що не є формою, яка зупиняє завдання.

Існує шість різних механізмів проводки:

МеханізмФорма вихідних данихПодії
permissionDecisionhookSpecificOutput.permissionDecision (дозволити /deny/ask/defer)ПередВикористанняІнструменту
permissionBehaviorhookSpecificOutput.decision.behavior (дозволити /deny)ЗапитДозволу
topLevelDecisionВерхній рівень decision: "block" + reasonВідправкаКористувачемЗапиту, РозширенняЗапитуКористувача, ПередСтисненням, ЗмінаКонфігурації, ПісляПакетуІнструмента
continueFalsecontinue: false + stopReasonЗавданняСтворено, ЗавданняЗавершено, КолегаНеактивний
postToolUsedecision: "block" (блокує подальшу обробку; інструмент вже використаний)ПісляВикористанняІнструменту
neutralНемає блокування, що підлягає виконаннюВсі події context/observe, плюс перевернуті події, такі як Stop

Якщо PEP повертає permissionDecision: "deny" при події PermissionRequest, Claude Code ігнорує це — ця подія очікує decision.behavior, а не permissionDecision. JSON дійсний, HTTP-відповідь 200, і відмови не існує. Це не теоретичний крайній випадок; це задокументований протокол, перевірений щодо code.claude.com/docs/en/hooks.

Трьохетапна класифікація: gating, context, observe

Не кожна подія хука може контролювати дію. Спроба відмовити SessionEnd або Notification безглузда — ці події інформаційні і не мають контролю за рішенням. Спроба відмовити подію Stop гірше ніж безглузда: decision: "block" на Stop підтримує агента в роботі, що є протилежним до безпечної зупинки.

PEP класифікує кожну визнану подію hook у одну з трьох категорій:

Gating події містять рішення, яке може дозволити, заборонити або заблокувати дію. Вони є поверхнею виконання. Але не всі події типу gating однаково реалізовані: Stop та SubagentStop класифікуються як gating у власній таксономії Claude Code, але їх семантика блокування інвертована (block = продовжувати виконання), тому PEP розглядає їх як нейтральні — він ніколи не генерує блок, який би залишав агентів живими всупереч намірам оператора. Подібним чином Elicitation та ElicitationResult мають класифікацію як gating, але в поточній версії не мають механізму зв’язаної дії, тому PEP за замовчуванням ставить їх у нейтральний стан замість того, щоб удавати, що існує виконання там, де його немає.

Контекст події дозволяють PEP вставляти additionalContext або переписувати вихід, але не можуть справді заблокувати дію. PostToolUse є виключенням partial — воно може блокувати подальшу обробку позначеного виходу, але інструмент вже було виконано. Решта (PermissionDenied, MessageDisplay, SessionStart, Setup, SubagentStart, PostCompact, InstructionsLoaded, PostToolUseFailure) спостереження з контекстом: корисні для збагачення огляду моделі, а не для її зупинки.

Спостереження події (Notification, SessionEnd, StopFailure, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove) зовсім не мають контролю за рішенням. PEP записує їх для телеметричного шляху та інвентарю SIEM, відповідає нейтрально і рухається далі.

Ця класифікація не є рекомендаційною. Вона визначає, чи застосовує вирішувач у корені складу правило політики (гейтінг + обов’язкове виконання), вводить контекст (контекст) або просто спостерігає (спостереження). Помилка тут призводить до або неправильного застосування (повернення відмови, яку ігнорують), або пропуску застосування (тлумачення події гейтінгу як спостереження).

Мапа hookSpecs: єдине джерело істини

Класифікація, механізм зв’язку та прапорець обов’язковості живуть в одній мапі. Це фактичний код, який використовує конектор — і рендерер HTTP-відповіді, і вирішувач у корені складу читають з нього:

type hookSpec struct {
    class       string   // "gating" | "context" | "observe"
    mech        hookMech
    enforceable bool
}

var hookSpecs = map[string]hookSpec{
    // GATING — повернення хука може застосувати allow/deny/block до дії.
    "PreToolUse":          {"gating", mechPermissionDecision, true},
    "PermissionRequest":   {"gating", mechPermissionBehavior, true},
    "UserPromptSubmit":    {"gating", mechTopLevelDecision, true},
    "UserPromptExpansion": {"gating", mechTopLevelDecision, true},
    "PreCompact":          {"gating", mechTopLevelDecision, true},
    "ConfigChange":        {"gating", mechTopLevelDecision, true},
    "PostToolBatch":       {"gating", mechTopLevelDecision, true},
    "TaskCreated":         {"gating", mechContinueFalse, true},
    "TaskCompleted":       {"gating", mechContinueFalse, true},
    "TeammateIdle":        {"gating", mechContinueFalse, true},
    "Stop":                {"gating", mechNeutral, false}, // інвертовано
    "SubagentStop":        {"gating", mechNeutral, false}, // інвертовано
    "Elicitation":         {"gating", mechNeutral, false}, // не підключено у v1
    "ElicitationResult":   {"gating", mechNeutral, false},
    // CONTEXT — additionalContext / переписування виводу, без реального блокування.
    "PostToolUse":        {"context", mechPostToolUse, true},
    "PostToolUseFailure": {"context", mechNeutral, false},
    "PermissionDenied":   {"context", mechNeutral, false},
    "MessageDisplay":     {"context", mechNeutral, false},
    "SessionStart":       {"context", mechNeutral, false},
    "Setup":              {"context", mechNeutral, false},
    "SubagentStart":      {"context", mechNeutral, false},
    "PostCompact":        {"context", mechNeutral, false},
    "InstructionsLoaded": {"context", mechNeutral, false},
    // OBSERVE — без контролю рішень.
    "Notification":   {"observe", mechNeutral, false},
    "SessionEnd":     {"observe", mechNeutral, false},
    "StopFailure":    {"observe", mechNeutral, false},
    "CwdChanged":     {"observe", mechNeutral, false},
    "FileChanged":    {"observe", mechNeutral, false},
    "WorktreeCreate": {"observe", mechNeutral, false},
    "WorktreeRemove": {"observe", mechNeutral, false},
}

Тридцять подій, кожна з яких має точно одну класифікацію, один провідний механізм і один вердикт щодо застосовності. Рендерер консультується з hookMechFor(event), щоб вирішити, яку JSON-структуру видавати. Децидер консультується з HookEnforcementFor(event), щоб вирішити, чи застосовувати правило політики, впроваджувати контекст або спостерігати. Обидва читають з однієї карти, тому вони не можуть мати різні думки.

Дозвольно-закритий хук PEP: потік класифікації подій і провідного механізму

Дозвольно-закритий за замовчуванням

Найважливіша функція у файлі має чотири рядки:

func hookSpecFor(event string) hookSpec {
    if s, ok := hookSpecs[event]; ok {
        return s
    }
    return hookSpec{class: "unknown", mech: mechPermissionDecision, enforceable: true}
}

Подія, якої немає на карті — оскільки Claude Code відправив нову подію гачка, яку конектор ще не класифікував — розглядається як unknown, призначається механізм проводки mechPermissionDecision і позначається як примусовий до виконання. Це значення за замовчуванням із закритим доступом: невпізнана подія є бар’єром дозволу, а не мовчазним пропуском. Якщо жодне правило політики не підходить, застосовується налаштована оператором поведінка за замовчуванням, яка у керованому розгортанні є відмовою.

Альтернатива — значення за замовчуванням нейтральне або спостережливе — означала б, що кожна нова подія гачка, яку вводить Claude Code, залишається неконтрольованою, поки хтось не помітить і не додасть її на карту. У моделі з закритим доступом нова подія регулюється з моменту її виникнення, навіть якщо класифікація консервативна. Хибна відмова на нову подію є видимою і підлягає виправленню; мовчазний дозвіл є невидимий і може зберігатися місяцями.

Структура HookEnforcement експортує цю класифікацію, щоб вирішувач міг розрізняти три рівні подій розблокування:

  • Класичні ворота (PreToolUse, PermissionRequest, PostToolUse та будь-яка невідома подія): коли жодне кероване правило не збігається, застосовується стандартна політика оператора (deny-closed).
  • Життєвий цикл ворота (інші здійснені події розблокування, такі як UserPromptSubmit, TaskCreated): вирішувач застосовує безпечне стандартне значення для кожної події — нейтральне для подій UX/lifecycle, відмова для подій, що змінюють стан.
  • Необов’язкові ворота (Stop, SubagentStop, Elicitation): вирішувач повертає нейтральний результат у будь-якому випадку. Видача відмови, яку Claude Code інтерпретує як «продовжувати роботу», була б протилежністю безпечного рішення.

Розподіл: від класифікації до флоту

Правильна класифікація подій — це лише половина справи. Інша половина — встановлення PEP на кожен екземпляр Claude Code. Підключення керованих налаштувань перетворює конфігурацію хука у форму, яку очікує Claude Code, і розповсюджує її як файл керованих сервером налаштувань — некеровану конфігурацію, яку контрольна площина поширює на кожен керований хост.

Хук PEP поширюється з порожнім відповідником (підходить всім інструментам — жоден інструмент не уникає точки застосування) і поєднується з allowManagedHooksOnly, щоб запобігти підміщенню локальних хуків розробника керованим PEP. Без цього прапорця хук на рівні користувача міг би затіняти керований, і застосування політики виглядало б присутнім, хоча його можна обійти. Валідація під час створення виявляє це: якщо політика постачає хук PreToolUse PEP без allowManagedHooksOnly, консоль видає попередження про захист від втручання.

Шлях телеметрії є окремим і без плани-заборони: експорт OpenTelemetry для Claude Code увімкнено через керовані змінні середовища (CLAUDE_CODE_ENABLE_TELEMETRY, ключі експортера OTEL_*), щоб план керування міг спостерігати використання підписки без проксування висновків або доступу до облікових даних підписки. Захоплення контенту (OTEL_LOG_USER_PROMPTS, OTEL_LOG_TOOL_CONTENT) за замовчуванням вимкнено; його увімкнення є свідомим вибором, що позначений, оскільки контент підказок і інструментів відправляється з комп’ютера розробника, створюючи вимогу щодо зберігання і редагування, яку має виконувати план керування.

Що це означає для керованого розгортання

Команда, що запускає Claude Code з керованим PEP, отримує кілька властивостей, які мають значення:

  1. Жодного прихованого дозволу. Кожна подія-хук класифікується, і кожна невизнана подія за замовчуванням відхиляється. Нова версія Claude Code не може вводити неконтрольовану подію життєвого циклу.
  2. Правильна форма даних для кожної події. PEP не повертає загальний об’єкт JSON, сподіваючись, що Claude Code його дотримається. Він повертає точну схему вихідних даних, яку очікує конкретна подія, оскільки відмова у неправильній схемі не є відмовою.
  3. Чесне невиконання. Події, які не можна забезпечити (події спостереження, події з інверсним керуванням), не виконуються «для видимості». PEP їх записує, повертає нейтральний результат і не дає оператору хибного відчуття контролю.
  4. Захист від підробки при розподілі. Шар керованих налаштувань гарантує, що хук PEP не може бути перевизначений і позначає конфігурації, де його можна обійти.

PEP є виконавчою складовою більшої керованої операційної моделі. Карта доступу, аудиторський журнал та шар політики як коду навколо нього охоплюються в огляді продукту та документації по хуках. Якщо ви хочете побачити, як формуються телеметричний шлях і контроль доступу, огляд архітектури детально пояснює обидва аспекти.

Пов’язані матеріали

Часто задавані запитання

Чому PEP за замовчуванням встановлює для невідомих подій хуку deny замість allow?

Тихе дозволення для невизнаної події означає, що будь-який новий хук Claude Code, який з’явиться у майбутньому випуску, обходитиме примусове виконання, поки хтось вручну не додасть його до карти класифікації. Це протилежно безпековій позиції, необхідній для керованого розгортання. Значення за замовчуванням deny-closed розглядає невідомі події як контроль доступу: вони фіксуються, ніколи не пропускаються, і якщо жодне правило політики до них не підходить, вони відхиляються. Це гарантує, що нові події життєвого циклу керуються з моменту їх появи, а не з моменту, коли хтось помічає, що їх не було.

Що станеться, якщо PEP видає неправильну форму даних для відповіді хука?

Claude Code мовчки ігнорує це. Кожна подія hook дотримується конкретної схеми вихідних даних: PreToolUse очікує hookSpecificOutput.permissionDecision, PermissionRequest очікує hookSpecificOutput.decision.behavior, а інші події контролю очікують верхньорівневе поле decision або continue. Якщо PEP повертає дійсний об’єкт JSON, але у неправильній схемі для цієї події, Claude Code сприймає це як відсутність рішення і продовжує. Ось чому механізм wire є частиною контракту безпеки, а не косметикою: заборона, яку Claude Code ігнорує, не є забороною.

Подивіться, до чого мають доступ ваші агенти

Olivares AI — це відкрита self-hosted платформа для вашого AI-середовища. Розгорніть її на власній інфраструктурі та отримайте карту доступу, про яку давно просять ваші команди безпеки й платформи.