Bollywood HungamaGIVA pulls down Kriti Sanon Raksha Bandhan ad after backlash; jewellery brand issues apologyThe Jerusalem PostAnti-AIPAC, anti-Israel platforms take center stage as progressive wins Oklahoma Democratic primaryוואלה8 הרוגים במפולת בוץ בטיבט: חשש לנעדרים נוספיםRTP DesportoMundiais de Canoagem. K4 500 avança para as meias-finais na defesa do títuloPunchBenin-Agbor-Asaba road failure contractor’s fault, say PresidencyCNN TürkTicaret savaşında yeni perde! Kanada'dan ABD'ye yüzde 50'ye varan tarifeInquirer EntertainmentAlex Gonzaga cherishes final days of pregnancy before childbirthDaily MaverickWHAT’S COOKING: Roasted marrow bones in a beef and onion broth, with gremolataInquirerSan Beda Alabang campus declared safe after alleged threatSky TG24Brothers, il trailer della serie con McConaughey e HarrelsoUOLConfira os falecimentos desta quarta-feira (26) em ApucaranaRTL BoulevardCamron Jones speelt Tupac in biopic over Snoop Dogg
The Daily Newsstand · Free, Always
Wednesday, August 26, 2026

Разгоняем GateAI

Translate

Решил сделать свой Gate AI, который не гоняет просто сканеры (semgrep, osv‑scanner, syft), а отдает каждую находку на триаж Claude api (sonnet 5), прежде чем решить, валить билд или нет. Логика проста, semgrep репортит 200 находок — это не fail. Три из них выжили после триажа как реально эксплуатируемые — вот это fail

Делал это с супер крутой идеей, пока не посчитал токены. В этом посте я расскажу агентный цикл триажа, который быстро упирается в токены, и вот что из этого вышло — конкретная оптимизация с цифрами, 3 бага, которые я не заметил бы без телеметрии, и один момент, когда Claude в буквальном смысле впал в панику посреди tool‑call

Почему это вообще дорого?

Триаж одной находки — это не один запрос. Модель вызывает read_file, search_code, find_callers, list_entrypoints, читает результат, зовёт следующий инструмент — и так до вердикта или потолка в 14 итераций

Проблема в том, как устроен Messages API: на каждой итерации в запрос уходит вся история диалога целиком, включая всё, что модель уже прочитала на прошлых шагах. Итерация № 7 повторно отправляет содержимое итераций 1 — 6. Без кэша каждый шаг платит полную цену за один и тот же контекст заново.

Решение очень быстрое: сделать один плавающий breakpoint.

В коде уже был cache_control на системном промпте — это статика, общая для всех находок стадии. А вот растущая история диалога кэш‑точки не имела вовсе.

Anthropic разрешает до 4 breakpoint'ов на запрос, система уже занимает один. Я держу один плавающий breakpoint на последнем блоке последнего сообщения: перед каждым вызовом снимаю метку со старого места и ставлю на новое. Дешево и работает:)

296 982 из 371 337 входных токенов в финальном прогоне пришли из кэша. А обычного input, который не попал ни в cache read, ни в cache write, осталось всего 147 токенов — примерно 1.7–1.9 токена на API‑вызов. Похоже на служебный overhead Messages API, который breakpoint'ом уже не накрыть

Cache read стоит ≈ 10% от обычной цены input, cache write (TTL 5 минут) — ≈125%. Перевожу в токены‑эквиваленты полной цены — экономия по стоимости прогона получается ≈ 61%

Кэш держит один диапазон на трёх языках

Дальше я не остановился на одном прогоне — погонял GateAI на трёх совершенно разных кодовых базах: свой Go‑репозиторий, вырезанный кусок DVWA (PHP) и вырезанный кусок pyGoat (Python/Django)

80% / 80% / 82% — разные языки, разные прогоны, один и тот же диапазон. Свойство одного механизма все роляет

Три бага, которые нашла только телеметрия

Разбивка токенов по типам (base / cache read / cache write) была нужна мне для одного графика. По пути она случайно стала инструментом код‑ревью — заставила меня читать реальный вывод модели строка за строкой, а не верить агрегатным цифрам.

1. Формула hit‑rate в отчёте врала. Итоговая строка показывала 100%, хотя по стадиям — 75% и 81%. Делил cache_read на (in − cache_write) вместо полного in — из знаменателя случайно выпадали как раз ещё не закэшированные токены

2. Список «пустышек» не поспевал за моделью. В отчёте буквально reasoning: placeholder, потом reasoning: x, потом fix: >. Repair‑логика подставляла вытащенный из сломанного tool‑call текст только если текущее значение поля — известное слово‑пустышка. Модель писала что‑то своё, чего в словаре просто не было. Заменил словарь на проверку содержательности: reasoning короче 5 слов (схема просит «two to six sentences») — стоп, независимо от того, что там конкретно написано

3. Skeptic‑проход шёл в обход собственных guardrail'ов проекта. Второй, состязательный проход над каждым «не эксплуатируется» строил вердикт напрямую, минуя и repair, и проверку «нет evidence / confidence ниже порога → needs_human», которую первый проход проходит всегда. Теоретически эксплуатируемость без единой цитаты мог проскочить мимо защиты, которую я сам заявляю как гарантию проекта. Вынес обе проверки в общую функцию — оба прохода теперь идут через один путь

Проверка на настоящих уязвимостях: DVWA

Своего репозитория для проверки мало — там нет реальных дыр, только шум сканера. Нужны были данные на код с задокументированными уязвимостями, чтобы посчитать вторую половину — ловит ли реальное

Semgrep светит все четыре версии одинаково — паттерн‑матчинг не видит разницы между дырявым и пропатченным кодом. Claude — видит: все 6 находок на impossible.php ушли в not_exploitable, все 18 находок на low/medium/high — заблокированы. Ожидаемо для reasoning‑модели, но раньше это было утверждение на словах, а не число из реального прогона

Заодно вскрылась отдельная, ещё не починенная дыра: в категории exec/ (command injection) semgrep дал 24 находки, но реально уникальных уязвимых строк там всего 8. Три разных правила (exec-use, tainted-exec, injection.tainted-exec) сработали на одной и той же taint‑цепочке, и GateAI триажит каждое как отдельную находку — три независимых агентных цикла на один и тот же вопрос. 37% находок этого прогона — чистый дубль работы. В отличие от кэша, это не про переиспользование контекста, а про то, что вопрос физически задаётся трижды. Дедуп находок по file:line перед триажем — буду фиксить в следующем релизе

pyGoat: та же проверка, но на критике

Второй прогон — pyGoat, намеренно дырявое Django‑приложение. Вопрос тот же, но на более серьёзных находках: RCE и десериализация, не только «отличить low от impossible».

Поймал:

  • RCE через eval(request.POST['val']) — прочитал вход, нашёл единственный gate (is_authenticated), процитировал обе строки

  • RCE через pickle.loads() на значении, взятом прямо из cookie — классическая небезопасная десериализация

  • SQL‑инъекция через прямую конкатенацию в raw‑запрос

  • SSTI через динамическую запись пользовательского контента в файл, который потом рендерится как Django‑шаблон

  • yaml.Loader вместо yaml.SafeLoader на загруженном файле

Cache‑hit на этом прогоне — 82%, 58 находок, 8 минут

А теперь — экзистенциальный кризис Claude

На одной находке модель словила синтаксическую ошибку при вызове submit_verdict. Вместо того, чтобы просто попробовать ещё раз, она ушла в 40-строчный штопор прямо в поле reasoning:

Okay, clearly something is wrong with my output generation causing repeated broken pseudo‑tags.Stop meta‑commentary, call the tool.I'm going to stop apologizing and just do it.FINAL CALL:Note to self: just write the tool call block, nothing else.

и ещё несколько экранов в этом духе, прежде чем вердикт всё‑таки прилетел нормальный. Весь монолог утёк в финальный отчёт, потому что мой repair ловит утечки только с открывающей <parameter name="...">, а тут модель писала теги без скобки

Итог

Кэш агентного цикла — это не система, а один плавающий breakpoint. 80% входных токенов начинают читаться из кэша вместо пересчёта заново, ≈61% экономии по стоимости — и это держится на трёх разных языках, не случайность одного прогона

И неожиданно главным результатом этой оптимизации оказался даже не кэш. Телеметрия LLM‑агента, которую я добавил ради стоимости, заодно нашла три ошибки в логике самого продукта

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.