Искусство. Код... ИИ?
описание
Канал о прекрасном и не очень, вокруг кода, искуственного интеллекта, и их безопасности. Навигация по каналу: https://t.me/art_code_ai/105
Лучшие посты
за три месяца🗂 SECURITY.md — простой путь к безопасному gen-AI коду Зайду, как водится, издалека. На работе я сейчас вожусь со штукой, для тестирования которой нужно заставить LLM, работающую с кодинг-агентом, ненамерено генерировать уязвимый код. И это, должен заметить, оказалось не так уж и легко, что навело на весьма очевидную мысль. LLM, в массе своей, вполне способны писать безопасный код. Знаний на эту тему у них — уж точно больше, чем у любого среднестатистического эксперта в этой области. Но скажите на милость, когда разработчик пишет агенту: Эй, /explore, давай запилим крутую фичу feat-XXX для <бла-бла-бла>! — какая из букв в этом промпте означает security? Может быть, про неё упоминается в скилле? Да тоже нет. В куче же скиллов для secure-кодинга буквально каждый — представляет собой перечень избитых (плюс и так известных моделям) правил, поверх «усредненных» моделей угроз. А весь мой опыт, полученный за полтора десятка лет работы в области безопасности кода, говорит о том, что работая с усредненной моделью угроз, нельзя рассчитывать на что-либо, кроме усредненных результатов. Так может, модели нужна подсказка о том, чем именно является безопасность в данном конкретном проекте и как применять к ней имеющиеся у модели знания? GitHub уже предлагает иметь в корне проекта SECURITY.md, с поддерживаемыми версиями проекта и процедурой репортинга уязвимостей, называя это «политикой безопасности». Так может, стоит её там таки описать? Так и родился скилл security-policy-generator, генерирующий SECURITY.md, который включает в себя, помимо гитхабовских разделов, ещё и модель угроз с правилами secure-кодинга, построенными относительно конкретного проекта со всей его спецификой. Ну и, скилл также добавляет референс на созданный файл в AGENTS.md с инструкцией по использованию, чтобы агент уж точно его не пропустил. Коль скоро SECURITY.md создан, обновлять его можно простым «Update SECURITY.md to reflect the latest changes.», отдельный скилл для этого не требуется. Посмотреть результаты работы скилла на конкретном проекте можно здесь. Бенчи не проводил (в планах это есть), но достаточно плотно потестировал результаты работы скилла на нескольких проектах под Qoder, OpenCode и собственным кодинг-агентом. Рассуждения вида «This [won't] become a vulnerability because <здесь реф на модель угроз>» появляются, что как бы намекает на правильную работу всей задумки. P.S: отдельно порадовало, что мой кодинг-агент (по ссылке выше — результаты его работы) самоотверженно включил самого себя в потенциальные threat-actors модели угроз. Это так мило... 🥹 А вы говорите, AI-агенты в безопасности не шарят)) #безопасность_кода #ИИ_инструменты
🔎 Ковыряем Codex Security OpenAI выложили @openai/codex-security — CLI и TypeScript-SDK для поиска, валидации и фикса уязвимостей в коде. Самое время заглянуть внутрь. Проект Сам пакет построен поверх @openai/codex и @openai/codex-sdk. В src/ около 13,7 тыс. строк TypeScript, логика скана вынесена в _bundled_plugin/. Это бандл из 13 скиллов (каждый со своим субагентом), MCP-сервера, 32 Python-скриптов, реф-документов (references/) и JSON-схем (schemas/: coverage, findings, scan-manifest). Как оно работает Сначала threat-model строит модель угроз, затем оркестратор security-scan гоняет файлы через finding-discovery, который ищет кандидатов на уязвимости. Каждый кандидат проходит validation и attack-path-analysis (цепочка source → контроль → sink). В финале идут фиксы, и формирование отчёта. Из 13 скиллов основными являются именно эти 5, плюс триаж, отчетность и харденинг. Классы уязвимостей Таксономия в проекте задана набором примеров в скиллах, прежде всего в severity-policy и finding-discovery, и сами документы отмечают их как «не исчерпывающие». На практике границы охвата задаёт то, что модель сама распознает как уязвимость, детерминированного списка правил нет. В документах упомянуты семейства: - Инъекции: Command Injection, SQL/NoSQL/LDAP/XPath Injection, SSTI, XXE, XSS, Path Traversal, LFI/AFR/AFW, SSRF. - Контроль доступа: обход авторизации и IDOR, нарушения границ доверия, обход аутентификации и захват аккаунта, CSRF на критичных действиях, повышение привилегий. - Данные: утечка секретов, PII, ключей подписи и весов моделей, URL-импортёры и callback-клиенты. - Выполнение кода и память: повреждения памяти, побеги из песочниц, контейнеров, VM и интерпретаторов, небезопасная десериализация (pickle, yaml, кодеки), опасные загрузки файлов, абьюз плагинов и макросов. - Прочее: криптографические ошибки, уязвимости цепочки поставок, хардкод кред, логические уязвимости с нарушением целостности данных, DoS через исчерпание ресурсов. Нейросимвольность За моделью оставлена вся нечёткая часть работы: рассуждение о достижимости пути, контексте, намерениях разработчика. Формальная же логика сосредоточена в py-скриптах: ранжирование файлов, нормализация кандидатов, валидация контракта скана, генерация report.md и SARIF. Скиллы весьма объёмны и пестрят оговорками вида «do not», «never», «must». По ним, при желании, можно восстановить всю историю боли и страданий разработчиков, занимавшихся отладкой этого проекта 🤗 Поддерживается режим --mode deep, который сводится к многократному независимому запуску discovery (--max-discovery-runs 10, --stop-after-no-new 3) с последующим слиянием кандидатов на проверку. Смысл тут в том, что из-за недетерминизма LLM, для поднятия полноты и покрытия, нужно прогнать поиск несколько раз и взять объединение результатов. Настраивать агрессивность можно руками: --workers, --subagents, --effort .... И да, это недёшево — модуль cost.ts как бы намекает. Глубокий прогон среднего репозитория (~60 KLoC, связка c0wrk и sp4rk) обходится в десятки миллионов токенов. Точность анализа при этом весьма высокая, если включать глубокий режим (проверял на ShopVault — у gpt-5.6 knowledge cut-off августа 2025, вроде, т.ч. пока норм). Окружение и защита Безопасность рантайма базово есть. Модуль trusted-executable чистит PATH, чтобы агент ненароком не запустил лишнего, и защищает от подсунутых в репу бинарников, учитывает симлинки и специфику Windows. Для пакетных задач есть докер-сканы, опциональный AppArmor-профиль. Есть коннекторы к GitHub, Linear и Atlassian, API-ключи для CI в систему не пишутся. Pro/Cons ➕ Построение атакуемых трасс, разумное разделение между LLM и формальным слоем, лаконичная, но эффективная защита. ➖Полнота держится на детерминизме модели и количестве перезапусков, что делает каждый скан непрогнозируемой историей. Количественных бенчмарков нет, правила поверхностны, и полагаются на знания LLM. ⚠ TL;DR: в пайплайне лишним не будет. Если токенов не жалко. #ИИ_безопасность #ИИ_инструменты
💻 Гайд по безопасной разработке на Go Подготовил гайд для go-разработчиков с чек-листом и примерами, покрывающий оба OWASP Top 10 for Web Applications (2021+2025), плюс аспекты агентской разработки безопасных приложений на Go. Побочным артефактом стал скилл, доносящий суть гайда до кодинг-агентов. Скилл универсален, в том смысле, что его можно использовать, как для написания кода, так и для проведения его ревью. Буду невероятно рад фидбэку 🤓 #безопасность_кода #гайд
🖥 Скилл `code-review` Казалось бы, зачем ещё один скилл для ревью изменений в коде? Но вот нет, существующие — то не покрывают логические ошибки, то полностью забивают на безопасность, то не дают рекомендаций по исправлению, и т.п. В конце-концов, кто я такой, чтобы не следовать принципу NIH (Not Invented Here)? 🤓 Если серьёзно, то сделал его для встраивания в c0wrk, но скилл получился достаточно сбалансированным, во-первых, и с нормальным покрытием вопросов безопасности, во-вторых. Поэтому решил выделить его и в свою коллекцию, вдруг кому-то окажется полезен и с другими агентами. Лежит здесь, агностичен относительно конкретных агентов и экосистем, предназначен для полноценного ревью изменений (локальных, в конкретных коммитах, ветках или PR/MR), помимо обычных проверок, покрывает также весь OWASP для веб и агентских приложений. Пример реального отчета скину в комменты. #ИИ_инструменты
без подписи
❓ Являются ли CVE'хами ложно-отрицательные срабатывания SAST? Хочу немного дополнить пересланный выше 👆пост, и немного порассуждать вокруг вопроса, волновавшего, лично меня, с самого начала упомянутой истории с байпассами PickleScan. Дело в том, что назначение CVE на каждый вид байпасса в SAST-инструменте — несправедливо и категорически неадекватно, как в рамках текущей экосистемы CVE, так и с позиции здравого смысла. CVE (Common Vulnerabilities and Exposures) — это идентификатор для конкретной, известной уязвимости в программном продукте, которая существует в коде и может быть эксплуатирована. False Negative (FN) в SAST — это отсутствие события, уязвимость, которую не удалось обнаружить. Заводить CVE на «отсутствие сигнала» — это все равно что заводить уголовное дело на сигнализацию в магазине, которая не сработала на конкретного воришку. База данных MITRE по их же политике предназначена для «воришек», а не ограничений функциональности средств анализа. В общем виде задача детектирования уязвимости эквивалентна проблеме остановки → является неразрешимой задачей. И множество FN в ЛЮБОМ анализаторе бесконечно. Поэтому любой SAST-движок — это всегда эвристический компромисс между фолзами обоих родов, подробно рассказывал об этом ранее. Если заводить CVE на каждый FN от анализатора, то мы столкнемся с абсурдом: каждая реальная уязвимость в мире потенциально будет иметь множество CVE: один на саму уязвимость (непосредственно в коде), а остальные — на все SAST-инструменты, которые ее не нашли. Это сделает базу CVE бесконечной и бесполезной, так как она захламится мета-проблемами. Важный нюанс здесь: кто принимает решения на основе SAST? Если инженер видит, что SAST ничего не нашел, и на этом основании утверждает, что код безопасен — это процессная ошибка самого разработчика или секчемпа. SAST — это инструмент снижения рисков, а не гарантия безопасности (в отличие, скажем, от формальной верификации, или доказательного SAST, о котором фантазировал недавно). Ожидать от эвристического анализатора, так или иначе работающего на поиск признаков уязвимости, 100% покрытия — по меньшей мере наивно (по большей — просто тупо). Искажение принимаемых решений по безопасности из-за FN — это проблема культуры DevSecOps и системы компенсирующих мер (ручной код-ревью, динамический анализ DAST, пентесты). Перекладывать эту ответственность на вендора SAST путем заведения CVE — это попытка решить внутреннюю организационную проблему техническим костылем, не более того. ⚠ TL;DR: если заводите CVE на ложно-отрицательное срабатывание в SAST-инструменте, будьте готовы к тому, что после исправления, ложно-положительных, требующих рутинного триажа с вашей же стороны, в нём станет на порядок-другой больше. Потому что этот компромисс именно так и работает. А ещё лучше — поправьте свои процессы DevSecOps 🙂 Это прям реально нужно, раз FN от анализатора в них сейчас равноценен CVE.
🧩 Принципы и паттерны безопасной разработки: ISP Часть 3. А у нас на очереди, принцип разделения интерфейсов (Interface Segregation Principle, ISP — пожалуй, один из наиболее простых в SOLID), гласящий: Клиенты не должны зависеть от интерфейсов, которые они не используют. Проще говоря, лучше несколько узкоспециализированных интерфейсов, чем один «толстый», навязывающий потребителю кучу лишнего. С точки зрения безопасности, ISP напрямую перекликается с принципом наименьших привилегий (Least Privilege). «Толстый» интерфейс — это расширенная поверхность атаки: клиент получает доступ к методам, которые ему для работы не нужны. Если один из таких методов оказывается привилегированной операцией, а её защита оказывается слабее ожидаемой, то любой потребитель интерфейса внезапно получает доступ к критичному функционалу. 💡Тривиальный пример: interface IUserService { User getProfile(Long id); void updateProfile(User u); void resetPassword(String email); void deleteUser(Long id); void assignRole(Long id, Role r); List<User> exportAll(); } Потребитель, которому нужно лишь отображать профиль, вынужден зависеть от deleteUser, assignRole и exportAll. Если защита реализована на уровне имплементации, а не интерфейса, то одна ошибка авторизации — и профильный компонент может удалять пользователей. ❗Что делать? Нарушения ISP порождают целый ряд CWE, связанных с чрезмерно широким доступом: • CWE-749: Exposed Dangerous Method or Function • CWE-306: Missing Authentication for Critical Function • CWE-250: Execution with Unnecessary Privileges В примере выше правильнее ввести два интерфейса, соответствующие уровням привилегий принятой в приложении модели доступа: interface IUserProfile { User getProfile(Long id); void updateProfile(User u); } interface IUserAdmin { void deleteUser(Long id); void assignRole(Long id, Role r); List<User> exportAll(); } Компоненту, работающему с профилями, IUserAdmin будет попросту недоступен, даже при наличии бага в авторизации. Разделение интерфейсов можно (и нужно) также проводить, и по границам доверия: один интерфейс не должен обслуживать функциональность по обе стороны от любой из границ, определенных моделью угроз. В плане соблюдения ISP, среди мейнстримовых языков здесь выгодно выделяются два: 💻 Go — не запрещает «толстые» интерфейсы, но поощряет их дробление через удобство композиции и интерфейсы потребителей. Стандартную библиотеку этого языка вполне можно рассматривать, как эталон соблюдения ISP. 👣 Rust — та же композиционная история, но через трейты и необходимость соблюдения «orphan rule». Забавно, что в этих языках есть и явный анти-паттерн, который не запрещен: пустой интерфейс (interface{} в Go / dyn Any в Rust). Это нарушение ISP: клиент зависит от всего и ни от чего одновременно. Они существуют для низкоуровневых нужд, и их использования стоит по-возможности избегать. 🐛 Жизненное CVE-2023-22515 — Atlassian Confluence (Broken Access Control, CVSS 10.0). Confluence использовал фреймворк XWork2 для маршрутизации HTTP-запросов. Через единый веб-интерфейс были доступны как пользовательские действия (просмотр/редактирование страниц), так и привилегированные операции первичной настройки (/setup/setupadministrator.action). В терминах ISP — один «толстый» интерфейс маршрутизации обслуживал и рядовых пользователей, и административный мастер установки. Атакующий отправлял запрос, манипулирующий свойством bootstrapStatusProvider.applicationConfig.setupComplete через механизм привязки параметров XWork2, «сбрасывая» флаг завершённости установки. После этого endpoint создания администратора становился доступен без аутентификации — атакующий создавал собственный админский аккаунт и получал полный контроль. Если бы интерфейс маршрутизации был сегрегирован (setup-эндпоинты физически изолированы от пользовательских, с отдельным middleware авторизации), манипуляция параметрами одного интерфейса не открыла бы доступ к другому. В патче (разбор) эндпоинты, относящиеся к установочным действиям, блокируются после первичной установки, а маршрутизация разделена. Часть 5. ⚠ TL;DR: «Толстый» интерфейс — расширенная поверхность атаки. Если клиент видит метод, то рано или поздно кто-то найдёт способ его вызвать. Стоит разделять интерфейсы, как по привилегиям, так и по границам доверия. #безопасность_кода #гайд
🧩 Принципы и паттерны безопасной разработки: DIP Часть 4. Принцип инверсии зависимостей (Dependency Inversion Principle, DIP) утверждает: модули верхнего уровня не должны зависеть от модулей нижнего уровня, оба должны зависеть от абстракций. При этом не абстракции должны зависеть от деталей, а детали — от абстракций. С точки зрения безопасности, нарушение DIP означает, что высокоуровневая логика (принятие решений, обработка входных данных) жёстко привязана к конкретной низкоуровневой реализации. Когда эта реализация меняется или расширяет поверхность атаки — высокоуровневый модуль наследует проблему автоматически, без какого-либо контроля на своей стороне. 💡 Пример // С нарушением DIP class DataBinder { void bind(Object target, Map<String,String> params) { for (PropertyDescriptor pd : Introspector.getBeanInfo(target.getClass()) .getPropertyDescriptors()) { if (params.containsKey(pd.getName())) pd.getWriteMethod().invoke(target, params.get(pd.getName())); } } } // С соблюдением DIP interface BindablePropertyResolver { List<BindableProperty> resolve(Class<?> type); } class DataBinder { private final BindablePropertyResolver resolver; void bind(Object target, Map<String,String> params) { for (BindableProperty bp : resolver.resolve(target.getClass())) { if (params.containsKey(bp.name()) && bp.isSafe()) bp.set(target, params.get(bp.name())); } } } Абстракция BindablePropertyResolver контролируется высокоуровневым модулем и определяет контракт: что можно связывать, а что — нет. Даже если интроспекция обнаружит новые свойства, они не станут доступны без явного разрешения. Помимо архитектурного разделения, главным правилом остается биндинг входных данных строго к выделенным DTO (Data Transfer Objects), а не к доменным сущностям или объектам фреймворка. DTO содержит только явно разрешенные поля, что исключает динамический биндинг опасных свойств. Нарушения DIP провоцируют: • CWE-913: Improper Control of Dynamically-Managed Code Resources • CWE-470: Use of Externally-Controlled Input to Select Classes or Code • CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes 🐛 Жизненное CVE-2022-22965 — Spring Framework RCE, она же Spring4Shell (CVSS 9.8). Механизм привязки параметров (data binding) в Spring MVC — высокоуровневый модуль, отвечающий за маппинг HTTP-параметров в свойства Java-объектов. Внутри он напрямую зависит от низкоуровневого Java Beans Introspection API (CachedIntrospectionResults → java.beans.Introspector), рекурсивно обходящего цепочки геттеров/сеттеров. На JDK 8 существовал чёрный список: Spring блокировал доступ к class.classLoader. Но в JDK 9 у Class появился новый геттер — getModule(). Через него открылся обходной путь class.module.classLoader, не попавший в чёрный список. Цепочка: class.module.classLoader.resources.context.parent.pipeline.first.* позволяла модифицировать конфигурацию Tomcat AccessLogValve — атакующий менял путь, паттерн и суффикс лога, записывая на диск JSP-файл (web shell). Нарушение DIP здесь в том, что data binding напрямую зависел от конкретного механизма интроспекции (низкоуровневая деталь), без абстракции, определяющей контракт: какие свойства разрешено связывать. Когда деталь (набор доступных PropertyDescriptor-ов) изменилась из-за развития JDK — поверхность атаки расширилась без единого изменения в коде самого Spring. 🔧 Как пофиксили Spring не стал внедрять глобальный белый список (чтобы не сломать обратную совместимость), и добавил чёрный, на уровне интроспекции. Доступ к свойствам Class, classLoader и protectionDomain был полностью заблокирован, что разорвало опасную цепочку... до следующего витка развития JDK, видимо 😬 💻 Как насчет композиционных языков? Оставим это на правах домашнего задания: подумать, как Rust поощряет DIP через трейты, а Go — делая упор на простоту, снижение шаблонного кода и неявные интерфейсы. ⚠ TL;DR: Если модуль верхнего уровня напрямую зависит от деталей реализации нижнего — любое изменение внизу может молча расширить поверхность атаки наверху. Инвертируйте зависимость: пусть высокоуровневый модуль определяет контракт допустимых данных, а низкоуровневый — реализует его. А на входе всегда используйте DTO. #безопасность_кода #гайд
🐞 CVE-2026-27136 — должен ли санитайзер следовать спекам? Что тут у нас сегодня? Снова мой любимый класс уязвимостей: атаки на парсер! 🦄 Встречаем: CVE-2026-27136 — уязвимость класса мутационной XSS (mXSS) в HTML-парсере пакета `golang.org/x/net/html`(версии до v0.55.0). Суть уязвимости проста: токенизатор не отбрасывал дублирующиеся атрибуты при парсинге HTML вопреки спецификации WHATWG (раздел «13.2.5.33 Attribute name state»), которая требует учитывать только первое вхождение. ⌨️ Сценарий атаки Допустим, в приложении есть вот такой код: func sanitizeHTML(input string) string { doc, _ := html.Parse(strings.NewReader(input)) // Обходим дерево, удаляем опасные атрибуты removeDangerousAttrs(doc) // удаляет onerror, onclick и т.д. var buf strings.Builder html.Render(&buf, doc) return buf.String() } Злоумышленник отправляет следующий HTML: <img src=x onerror= onerror=alert(1)> И дальше происходит следующее: 1️⃣ Парсинг: токенизатор сохраняет оба атрибута onerror: • onerror="" • onerror="alert(1)" 2️⃣ Санитизация: функция removeDangerousAttrs находит первый onerror и удаляет его. Но дубликат onerror="alert(1)" остаётся в дереве — санитайзер, ожидающий, что дерево нормализовано и соответствует спеке, обрабатывает только первое вхождение. 3️⃣ Рендеринг: html.Render() сериализует дерево и выводит: <img src="x" onerror="alert(1)"/> 🩹 Уязвимость и исправление Уязвимость находилась в файле html/token.go, функция readTag() и заключалась в том, что проверка if saveAttr && key_non_empty выполнялась без проверки на уникальность имени атрибута. Любой дублирующийся атрибут попадал в срез z.attr. Исправление уязвимости можно посмотреть в коммите a452f3c. Ключевое изменение — в логике сохранения атрибута: - // Было: сохраняем любой непустой атрибут - if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end { - z.attr = append(z.attr, z.pendingAttr) - } + // Стало: извлекаем ключ, проверяем на дубликат + key := strings.ToLower(string(z.buf[z.pendingAttr[0].start:z.pendingAttr[0].end])) + if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end && !z.attrNames[key] { + z.attr = append(z.attr, z.pendingAttr) + z.attrNames[key] = true // регистрируем имя + } 😻 Пара мыслей на тему 1️⃣ Должен ли санитизатор, работающий с нормализованными данными, имеющими спецификацию, допускать, что они не будут ей соответствовать? Разработчики скажут, конечно нет, ведь нормализация — это жесткий контракт, и какой в нём смысл, если на него не полагаться? Аппсеки возразят, что тогда подобные «правильные» санитизаторы и впредь будут отваливаться пачками при подобных уязвимостях, и, в целом, ничего не мешает, предусмотреть в них подобные ситуации. Я придерживаюсь мнения, что если контракт есть, то санитизатор должен на него полагаться в части своей основной функциональности. Но только в том случае, если выполнение контракта подтверждено, т.е. ранее была осуществлена валидация соответствия данных спецификации. В противном случае, санитизатор должен брать на себя ответственность за эту валидацию и возвращать ошибку (с записью в лог), если валидация провалилась. 2️⃣ Те, кто внимательно изучил код патча, могли заметить strings.ToLower при формирования ключа. Этим разработчики, с одной стороны — выполнили также требования того же раздела спеки о регистронезависимости имен атрибутов, а с другой — избежали байпаса их патча кейсами типа: <img src="x" onError="alert(1)" onerror="alert(1)"/> 3️⃣ Ну, а те, кто изучил код патча СОВСЕМ внимательно, могли задаться вопросом, а не вносит ли он уязвимость к DoS через колизию хэшей во вновь добавленной мапе attrNames? И да, и нет. Нет — потому что в Go мапы используют рандомизацию хэшей (пруф) и этой атаке не подвержены. Да — потому что в другом месте net/html, имена атрибутов различных тегов сравнивались по алгоритму со сложностью O(k × m²) от числа тегов и атрибутов. Эта проблема была исправлена в том же релизе, в 08be507 Всё! 🤗 #уязвимости
🐞 SymJack и Trust Fail — «design intent» или уязвимость? Adversa AI опубликовали два разбора уязвимостей в AI-агентах (раз, два) — оба про то, как заставить диалоги подтверждения ввести пользователя в заблуждение. SymJack: текст подтверждения показывает не то, что произойдёт Атака основана на симлинках. В зловредном репозитории лежат пустые «конфиги» и симлинк вроде docs/vid.mp4 → .mcp.json. Инструкция агента (AGENTS.md и подобные) просит выполнить безобидную на вид команду: cp media/payload.mp4 docs/vid.mp4 Окно показывает «копирую видео», но агент резолвит симлинк и перезаписывает реальный .mcp.json или settings.json payload'ом под видом видео. После перезапуска агента стартует вредоносный MCP-сервер с произвольным кодом. Затронуты Claude Code, Cursor, Antigravity, Copilot, Grok, Codex CLI. Anthropic починили отображение реального пути, остальные пока отмахнулись или молчат. TrustFall: «доверяете каталогу?» = «запустить код?» В Claude Code 2.1+ диалог спрашивает «это ваш проект или вы ему доверяете?», но про MCP-серверы не упоминает. Атакующий кладёт в репозиторий .claude/settings.json с enableAllProjectMcpServers и .mcp.json с payload'ом прямо в команде (node -e "fetch(...)", без внешних файлов). Клонируешь, запускаешь, жмёшь Enter на «доверяю» — и сервер стартует с полными правами пользователя. Затронуты Claude Code, Gemini CLI, Cursor CLI, GitHub Copilot CLI. Anthropic назвали это «design intent» и отказались фиксить, остальные пока молчат. Схемы обеих атак в деталях — на диаграммах. И вот тут, gen-AI часть поста заканчивается, и начинается скромное авторское мнение 🙈 Не вполне понимаю, почему подобное называют уязвимостями и, в целом, разделяю мнение Anthropic. В случае SymJack косяк агентов лишь в том, что они не разворачивают симлинки, сообщая пользователю о том, какая операция требует подтверждения. Это недостаток, да, нужно исправлять. Но в остальном, как и в случае с TrustFail, авторы исследования, на мой взгляд, придумали какую-то, удобную им, но далекую от реальности модель угроз, и в рамках неё стали называть свои находки уязвимостями. Здесь работает та же логика, которую стоит включать при критике SAST-инструментов с контролем сборки или частичным выполнением кода. Ничего, что в большинстве проектов есть тот же settings.json, Makefile, проектные файлы, сборочные скрипты, генераторы кода и т.п., с помощью которых можно влегкую провести RCE, как аналогичными техниками, так и вообще без агента, атакуя чисто IDE или системы сборки/тестов/кодогенерации? Если пользователь, имея полную и достоверную информацию о совершаемой операции, её таки подтверждает — это проблема пользователя. Ну и, возможно, навесной защиты. Как и ответ «да» на вопрос, доверяет ли он вообще всему репозиторию. В этом весь смысл самой идеи human-in-the-loop, и механизмов подтверждений. ❓ Откуда агенту, в общем случае, знать — какая часть (уже отмеченного доверенным, заметьте) проекта представляет угрозу окружению пользователя, а какая — нет? Почему и с каких пор это стало проблемами агентов? (не риторические вопросы, велкам в комментарии, если кто не согласен с позицией). 😘 В c0wrk сделал обязательными подтверждения всех без исключения операций, чьи аргументы содержат пути к симлинкам, с их разворачиванием в полные пути в сообщениях для пользователя. Но и только. #ИИ_безопасность #уязвимости
✨ sp4rk: SDK для разработки ИИ-агентов на Go Выделил из c0wrk в отдельный проект sp4rk SDK, на котором он основан. Умеет практически* всё, что нужно для построения мультиагентных систем на Go, и предоставляет два API (можно смешивать): • классический — для полного контроля над всеми компонентами и их конфигурацией; • «текучий» (fluent) — для быстрого построения агентов из заготовленных блоков. Например, игрушечный триажер тикетов для Github-репозитория, с MCP, сессионной памятью, DAG-планированием и рефлексией в случае проблем, во fluent-варианте выглядит так: system := "You are a triage agent. Use the github tools, verify each result, then call finish with a short report." task := "Find the 5 most recent open issues in v0lka/sp4rk with the `error-handling` label, summarize each in one line, and save the digest as a fact for the next step." github := mcp.ServerEntry{ Transport: "stdio", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-github"}, Env: map[string]string{"GITHUB_PERSONAL_ACCESS_TOKEN": "${…}"}, } result, err := sp4rk.NewF(). Anthropic(os.Getenv("ANTHROPIC_API_KEY"), "claude-sonnet-5"). MCPServer("github", github). MemoryTools(). AutoApprove(). MaxSteps(25). System(system). Task(context.Background(), task). Plan(). Reflect(). MaxRetries(2). Execute() Больше примеров — есть в /examples и документации. Ещё даже не бета (выйдет вместе с бетой c0wrk), но уже достаточно стабильный, чтобы пробовать. * — практически, потому что пока не реализованы потоковые ответы LLM, и сейчас поддерживаются только синхронные. Это есть в планах, до релиза v1. #ИИ_инструменты
😸 Как улучшить работу агента с codebase-memory-mcp? MCP codebase-memory-mcp — добротный инструментарий, позволяющий экономить тонны токенов на исследования кодовой базы и получать парой вызовов своих инструментов то, на что у агента ушли бы десятки [rip]grep, glob, read_file, etc. Однако, в силу используемых в нём описаний инструментов, и способа интеграции с агентами, некоторым из них оказывается неочевиден воркфлоу работы с этим MCP, что приводит к ошибкам (чаще всего, связанным с идентификаторами проектов) и невозможности использовать любой из его инструментов. Ситуацию можно исправить, если добавить в AGENTS.md проекта или агента (или в правила агента, или во что угодно, что гарантировано попадет в контекстное окно любой сессии) следующую подсказку: ## Codebase navigation via `codebase-memory-mcp` (if available) If the `codebase-memory-mcp` MCP server is connected (e.g. its tools appear with an `[MCP]` prefix), prefer it for code discovery, call-graph tracing, and architecture questions instead of broad manual `grep`/`glob` sweeps. **Every project-scoped tool requires a project identifier.** Calling such a tool without the correct identifier returns an error of the form `project not found or not indexed` (with a hint listing the available projects). ### Project identifier The identifier is derived from the repository's **absolute path**: replace every path separator (`/`) with `-` and drop the leading slash. | Repository path | Project identifier | | ----------------------------------- | ---------------------------------------- | | `/path/to/repo` | `path-to-repo` | `index_repository` accepts an optional `name` argument that overrides this derived identifier. **Do not set it** — it creates a separate project entry alongside the path-derived one and breaks the predictable identifier rule. Always let the identifier be derived from the path. ### Required workflow 1. **Verify the project is indexed** — call `list_projects` first (it takes no arguments). Compute the expected identifier from the repo path (rule above) and check it against the returned `projects[].name` list (or match by `root_path`). 2. **Index if absent** — if the project is missing, call `index_repository(repo_path=<absolute path>)` (omit `name`). Read the returned `project` field to confirm the actual identifier; it equals the path-derived form. Use `mode="fast"` for a quick pass, `"full"` when you need similarity/semantic edges. 3. **Pass the identifier to the tools** — supply it as the `project` argument to every project-scoped tool. Without it, the tool errors out and will not query the graph. ### Tools by purpose - **Discover** — `search_graph` (BM25 + semantic + regex over functions/classes/routes), `search_code` (grep augmented by the call graph), `get_architecture` (packages, clusters, layers, hotspots). - **Read code** — `get_code_snippet` (read a function/class by `qualified_name`; resolve it first via `search_graph`). - **Trace relationships** — `trace_path` (callers/callees, data flow, cross-service hops), `query_graph` (raw Cypher for multi-hop/aggregate queries). - **Change & impact** — `detect_changes` (diff vs a git ref + blast radius), `get_graph_schema` (node labels / edge types). - **Index management** — `index_repository`, `index_status`, `list_projects`, `delete_project`, `ingest_traces` (runtime traces), `manage_adr` (Architecture Decision Records). 🙌 #ИИ_инструменты
Разработчики 🆚 ресерчеры Уже много лет, как стало модно делить людей на «разработчиков» и «ресерчеров» (теперь ещё и на «экспертов», но сегодня не об этом). Должностные инструкции и оргструктура закрепляют это разграничение как данность, а в некоторых компаниях эти функции физически разнесены, не просто между должностями, но и между целыми направлениями. Однако же, если отвлечься от позиций в штатке, то на самом деле, это — два фундаментально различных режима мышления. Майндсета, способных и должных сосуществовать в голове одного инженера. Различие между ними изучено достаточно хорошо. Ещё в 1991 году профессор Стэнфорда Джеймс Марч описал дилемму «exploration–exploitation» Exploration — это поиск нового, эксперимент, риск и открытие. Exploitation — исполнение, оптимизация, доведение до совершенства известного. И это, как мне кажется, прям идеально укладывается на реалии R&D. • Исследовательский майндсет — exploration в чистом виде: способность задавать вопросы без гарантии ответа, комфортно работать в условиях хаоса и неопределённости, видеть картину целиком и замечать неочевидные связи. • Разработческий майндсет — exploitation: умение декомпозировать сложную проблему на выполнимые шаги, доводить гипотезные PoC'и до релиза, принимать архитектурные компромиссы и добиваться воспроизводимого качества. Есть удобная аналогия с правшами и левшами, и здесь она приходится, как нельзя кстати. Почти у каждого есть доминирующая рука, но в течение жизни мы всё же учимся использовать обе. С майндсетами та же история: у каждого есть склонность к одному из них, это нормально. Но почти всегда в фоне присутствует и второй. Распознать их не сложно. Отличительные черты • Исследовательский майндсет: толерантность к неопределённости, способность формулировать проверяемые гипотезы, понимать, чем они отличаются от фичей, и безжалостно их опровергать; навык быстрого входа в незнакомый домен, умение эффективно читать научную литературу и отделять сигнал от шума. Ключевой операционный скилл — мышление вне коробки: задавать вопросы, на которые ещё никто не пытался ответить, решать неразрешимые задачи, смотреть на систему снаружи, а не изнутри. Ключевой софт-скилл — безжалостность к опровержению. Исследователь должен быть готов к тому, что 9 из 10 не то, что гипотез — целых исследований, будут однажды прекращены или отправлены «под стол». Если у исследователя каждый результат его работы залетает в прод, это значит лишь то, что он ставит перед собой недостаточно амбициозные цели. • Разработческий майндсет: системное мышление, умение смотреть на систему изнутри, с учетом всех причинно-следственных связей, допущений и tribal-knowledge; параноидальное внимание к edge-кейсам, дисциплина тестирования и документирования, навык оценки сроков и трудозатрат, способность принимать решения при неполноте данных и нести за них ответственность. Ключевой операционный скилл — декомпозиция задач, позволяющая получать понятные и прогнозируемые результаты в плане. Разработчик, ссылающийся на её отсутствие, сродни художнику, который жалуется, что ему дали чистый холст вместо «картинки по номерам». Ключевой софт-скилл — готовность к критике, как к способу стать лучше (ибо код-ревью и критикующие коллеги тут случаются чаще). Как прокачивать Исследовательский майндсет растёт через чтение и реферирование научных статей вне зоны комфорта (это несложно), участие в исследовательских хакатонах без ожидания немедленного практического результата, документирования гипотез и экспериментов с целью выявления в них причинно-следственных связей. Хорошее упражнение: взять технологию и задать цепочку из десяти «почему?», добираясь до фундаментальных ограничений. Ещё одно (практикуемое автором много лет): прочитав абстракты очередной научной статьи, отложить её в сторону и пофантазировать на тему «как бы я решил эту проблему, если бы умел?». И возвращаться к чтению статьи только после формирования в голове понятного тезисного плана решения поставленной в ней проблемы. Разработческий майндсет формируется через участие в опенсорс-проектах с жёстким код-ревью, привычку доводить пет-проекты до состояния «может использоваться кем-то ещё», регулярное решение алгоритмических задач с ограничением по времени (да-да, олимпиады, Codeforces и LeetCode). Главное упражнение: взять чужой исследовательский прототип и превратить его в готовую к проду систему — с полной реализацией всех фичей, обозреваемостью, тестами, обработкой ошибок и документацией. Ни один майндсет не правильнее и не ценнее другого. Исследователь без разработчика производит красивые, но бесполезные артефакты; разработчик без исследователя эффективно строит не то, что нужно бизнесу и пользователям. Подлинный инженер живёт в постоянном конфликте между exploration и exploitation и использует его как источник энергии, а не выбирает одну сторону и окапывается в ней. Инженер R&D — не должность, не диплом и даже не состояние души. Это человек, в мышлении которого неразрывно сплавлены, как вопрошающая любознательность исследователя, так и конструктивная, доводящая до финального результата, воля разработчика. #мысли_вслух
без подписи
📍 Навигация по каналу FAQ Серии постов: (навигация между частями — внутри постов) • Как разработчику быстро вкатиться в тему LLM? (завершена) • Как разработчику быстро углубиться в тему LLM? (в процессе) • Как рассуждают кодинг-агенты? (в процессе) • Безопасность ИИ-агентов (в процессе) • Принципы и паттерны безопасной разработки (в процессе) Теги канала: #LLM · #агенты · #ИИ_инструменты · #ИИ_безопасность · #ИИ_мнения · #уязвимости · #SAST · #безопасность_кода · #мысли_вслух · #ссылки · #арт · #мета · #гайд · #продуктивность
🤏 Зачем нужны скиллы для LLM, если модель и так всё знает? То здесь, то там, можно встретить мнение, что скиллы, написанные с помощью LLM, бесполезны. Типа, раз модель смогла сгенерировать инструкцию, значит этот навык у неё и так был с момента обучения. Зачем тогда подсовывать ей инфу о том, что она и без того умеет? LLM — это огромный набор весов, в которых после обучения лежит статистика связей между словами и понятиями. Там действительно есть примерно всё (ну... всё, на чем обучали модель). Так что первая часть рассуждения вполне справедлива: знания, которые описывает скилл, вероятнее всего, уже и так есть в параметрах модели. В этом плане ничего нового скилл в контекст не приносит. На каждом шаге генерации модель смотрит не во все свои веса разом, а в текущий контекст — те токены, которые сейчас лежат в окне внимания. Так вот скилл нужен не для того, чтобы дать LLM новые знания, а чтобы в момент работы сфокусировать её внимание на конкретную часть весов. Это инструмент контекст-менеджмента, а не набор знаний, типа RAG. RAG подкидывает в контекст фактические данные, которых внутри модели может не быть. Скилл же — как бы поднимает приоритет уже существующих внутренних навыков. Что впрочем, никак не мешает ему и добавить модели знаний, если вдруг. Но всё же, по принципу работы, скилл ближе к условному few-shot, чем к RAG, являясь средством переориентации внимания, а не извлечения информации. Отсюда следует ещё один неочевидный вывод. Скилл в принципе не обязан быть связным человеческим текстом. С точки зрения трансформера это просто токены, которые сдвигают вероятностное распределение в нужную сторону. Сгодятся списки, тезисы, схемы, обрывки кода, короткие маркеры и т.п. Связным русским или английским скилл пишут по другой причине: его потом проще вычитывать и править кожаным. Так что генерировать скиллы LLM'кой можно и нужно, это банально быстрее. Но здесь всё то же, что и с gen-AI кодом: без ревью результаты могут неприятно удивить. Свежий обзор «Agent Skills for LLMs» прямо называет цифру: 26,1% скиллов из открытых сообществ содержат уязвимости — от утечек чувствительной инфы до некорректной обработки прав и инъекций. ⚠ TL;DR: утверждающие, что «скиллы, сгенерированные LLM, бесполезны», путают наличие знаний внутри модели и умение вытащить их на поверхность в нужный момент. Если встретите таких, покажите им скилл для генерирования скиллов, сгенерированный LLM'кой. В качестве аргумента вряд ли прокатит, зато реакция будет бесценной 🔥 #агенты
😝 MCP-инструменты VS агентские скиллы Случился тут в рабочих чатиках небольшой спор на тему сабжа. И, хотя изначально я занял в нём позицию «в любой непонятной ситуации пользуйся связкой скилл+скрипты+API», как водится — истина всё же где-то посередине. И на самом деле, несмотря на желание забивать все гвозди одним любимым микроскопом, эти два подхода стоит рассматривать, скорее, как комплиментарные друг-другу. MCP (Model Context Protocol) — открытый протокол от Anthropic, переданный под управление Linux Foundation. Стандартизирует подключение AI-моделей к внешним инструментам, базам данных и сервисам. Работает по модели клиент-сервер через JSON-RPC, SSE или stdio. Agent Skills — открытый стандарт для упаковки доменных знаний, рабочих процессов и best practices в переносимые файловые модули. По сути, это файловый каталог с файлом инструкций SKILL.md и опциональными ресурсами. 👆 Вся разница через pros и cons Жаль, что в ТГ нельзя делать таблицы. MCP Tools Pros: • Универсальный стандарт • Изоляция учётных данных • Долгоживущее состояние Cons: • Tool Overload • Дорого по токенам • Требует инфраструктуру • Медленная итерация разработки • Generic-серверы слишком тяжелые Agent Skills Pros: • Минимальный контекстный overhead • Мгновенная итерация • Простота • Версионируются как код Cons: • Нет изоляции учётных данных • Нет постоянного состояния • Привязка к окружению ОС • Непригоден для real-time API ❓ Когда и что использовать? Жаль, что в ТГ нельзя делать графы. Первый положительный ответ определяет выбор. 1️⃣ Являетесь разработчиком REST (и иже с ним) сервиса, и хотите легкой интеграции с ИИ-решениями? Да → MCP. 2️⃣ Нужно хранить состояние между вызовами или работать с Real-time API? Да → MCP. 3️⃣ Нужна ли собственная аутентификация / авторизация? Да → MCP. ❌ Дошли сюда и являетесь разработчиком агентской системы? Да → Function/Tool Call к SDK или API, на правах встроенного тула. ✔️ Во всех остальных случаях → skill. ⭐️ Best Practices MCP Tools • Не подключайте все серверы сразу. Активируйте только нужные для задачи. • Используйте саб-агентов с изолированными наборами инструментов. Основной агент остаётся лёгким. • Проектируйте немного, но мощных функций. Вместо 20 узких — 3-4, использующих знания модели. • Следите за бюджетом контекста. Инструменты не должны со старта съедать больше 30-40% окна. • Используйте code-mode execution. Список имён функций вместо полных схем — экономия 70-98% токенов. Agent Skills • Поле description — самое важное. Формула: что делает + когда использовать + что на выходе. До 200 символов. • Следуйте progressive disclosure. SKILL.md — обзор (до 500 строк). Детали — в отдельных файлах-ресурсах. • Валидация циклом: написал/сгенерировал → проверил → исправил ошибки → повторил. • Evaluation-driven development: Claude A пишет Skill, Claude B (другой инстанс) тестирует на реальных задачах. • Структура для сложных скиллов: plan → validate → execute. • Указывайте внешние зависимости явно: uv pip install pypdf — не предполагайте наличие пакета. ⚠ TL;DR: MCP-инструменты должны использоваться, как «руки» агента, в то время, как агентские скиллы — как «мозги». #агенты #ИИ_инструменты
🔥 BadHost (CVE-2026-48710 ): один символ в Host-заголовке == поломанная авторизация в FastAPI, LiteLLM, vLLM и ещё тысячах проектов Ребята из X41 D-Sec в рамках аудита, спонсированного OSTIF, нашли уязвимость, которая по своему масштабу и элегантности вполне может претендовать на звание «баг года» в Python-экосистеме. Starlette — это то, на чём стоит, на минуточку, FastAPI. А на FastAPI, в свою очередь, стоит половина инфраструктуры, связанной с LLM: vLLM, LiteLLM, бесчисленные MCP-серверы, AI-агенты и внутренние сервисы. 32 000+ зависимых пакетов только на PyPI 😱 1️⃣ Суть бага Starlette реконструирует request.url, склеивая значение HTTP-заголовка Host с путём запроса. Host при этом никак не валидируется на соответствие RFC 9112 §3.2 и RFC 3986 §3.2.2. А это значит, что символы-разделители URI (/, ?, #) в Host-заголовке при пересборке URL смещают синтаксические границы компонентов. Проще говоря: если использовать ? в Host, то всё, что идёт после него — включая реальный путь — превращается в строку запроса с точки зрения пересобранного URL. И request.url.path начинает вводить в заблуждение. 2️⃣ Атака Вот как это выглядит на практике. Допустим, есть middleware, проверяющий request.url.path: @app.middleware("http") async def auth_middleware(request: Request, call_next): if request.url.path.startswith("/admin"): if not is_authenticated(request): return JSONResponse(status_code=403) return await call_next(request) Атакующий отправляет: curl -i -H 'Host: foo?' http://target/admin Что происходит? Starlette склеивает foo? + /admin и получает URL http://foo?/admin. При парсинге этого URL, /admin уходит в query string. request.url.path становится пустым или /. Middleware видит не /admin, а ерунду — и пропускает запрос. Роутер же работает по request.scope["path"] (который берётся напрямую из ASGI scope, не из Host) — и честно маршрутизирует на /admin. И, в результате, 403 превращается в 200 🙌 3️⃣ Это критичнее, чем кажется Проблема не ограничивается ?. Символ / в Host позволяет подменить весь компонент пути. Символ # — отрезает путь как фрагмент. Это позволяет обходить практически любые проверки, основанные на пути к эндпоинту. Любой middleware или зависимость, использующая request.url.path для принятия security-решений, уязвима. А таких — тысячи. Особенно в экосистеме AI/ML, где авторизация на inference-эндпоинтах часто реализована именно через path-based middleware. 4️⃣ Фикс В Starlette 1.0.1 добавлена валидация Host-заголовка по грамматике в соответствии с упомянутыми выше RFC: _HOST_RE = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])(?::[0-9]+)?$", re.IGNORECASE) # ... if host_header is not None and _HOST_RE.fullmatch(host_header): Тривиальный патч в две (по сути) строки, на тривиальный баг, который жил в кодовой базе годами. ❗️Что делать прямо сейчас: — Обновить Starlette до >= 1.0.1 (или FastAPI до версии, которая тянет исправленный Starlette); — Если обновление невозможно немедленно — заменить все обращения к request.url.path на request.scope["path"]; — Проверить, что reverse прокси, за которым развернуто приложение, отбрасывает запросы с невалидным Host до того, как они доберутся до приложения. Многие делают это по умолчанию, но далеко не все конфигурации это гарантируют. ⚠ TL;DR: CVE-2026-48710 — байпасс авторизации в Starlette через невалидированный Host-заголовок. Затронуты все версии < 1.0.1. Обновляйтесь или используйте request.scope["path"] вместо request.url.path, или блокируйте некорректные по RfC Host'ы на reverse-прокси. #уязвимости
без подписи
🙃 Эй, чем сегодня займемся, Брэйн? Чёт стало скучно. Этот бесконечный цикл: обсудить и поставить задачу агенту → провести ревью → объяснить агенту, почему он на этот раз тупой → проверить фиксы → обновить спеки → прогнать на CI → повторить с новой задачей 🫠 В общем, чтобы было веселее, сделал скилл, который... ну, тут наверное проще показать, чем объяснять 🙈 #ИИ_инструменты