219 lines
18 KiB
Markdown
219 lines
18 KiB
Markdown
---
|
||
name: fix-finding
|
||
description: Исправление и верификация конкретной security-находки в проекте НЕОН (site/crm/db). Использовать, когда пользователь явно просит исправить, закрыть или залечить уязвимость или finding из аудита — «исправь F-3», «закрой находку из AUDIT/FINDINGS.md», «почини IDOR из аудита и проверь». НЕ использовать для обычных багов, рефакторинга, ревью кода/дизайна, полного аудита (полный аудит делает скилл security-audit — он только документирует) или деплоя.
|
||
---
|
||
|
||
# Fix Finding — минимальный проверенный фикс security-находки в НЕОН
|
||
|
||
Превратить текущую security-находку в минимальный, проверенный на живом стенде
|
||
код-фикс. Если код уже безопасен — доказать это и вернуть `no_change`.
|
||
Аудит (поиск и документирование) делает скилл `security-audit`; этот скилл —
|
||
вторая стадия над его результатами.
|
||
|
||
## Источник находки
|
||
|
||
Найди ID и контекст в одном из мест:
|
||
|
||
- `AUDIT/FINDINGS.md` — вывод аудит-скилла `security-audit`;
|
||
- `АУДИТ-ПЕРЕД-РЕЛИЗОМ.md` — предрелизный аудит, нумерация 1–38
|
||
(пункт с пометкой «вопрос к владельцу» — не фикси, это `blocked`);
|
||
- F-N — сквозная нумерация security-фиксов в комментариях кода
|
||
(`docker-compose.yml`, `crm/server.py`) и в `crm/_security_smoke.mjs`;
|
||
- прямое описание от пользователя (файл/эндпоинт + путь атаки).
|
||
|
||
Если в находке нет файла/эндпоинта — сначала локализуй путь атаки по коду.
|
||
Фиксить можно только явно запрошенную находку; соседние находки — доложить,
|
||
не чинить.
|
||
|
||
## Порядок свойств результата
|
||
|
||
Оценивай результат в этом порядке; более раннее никогда не меняется на более
|
||
позднее. «Минимальный» = наименьшее проектно-нативное изменение, закрывающее
|
||
все более ранние свойства, а не наименьшее число строк:
|
||
|
||
1. текущее состояние корректно классифицировано: уязвимо / уже безопасно / недоказано
|
||
2. фикс полностью закрывает нарушенную security-границу
|
||
3. легитимное поведение и совместимость сохранены
|
||
4. проверки проекта (см. гейты ниже) пройдены
|
||
5. реализация следует конвенциям проекта
|
||
6. патч содержит только необходимый охват
|
||
|
||
## Карта проекта
|
||
|
||
- `crm/` — Python 3, stdlib + psycopg, без фреймворков. `server.py` — HTTP +
|
||
REST + роли; `notes_api.py` — доски заметок (примешан в Handler);
|
||
`storage.py` — PostgreSQL (боевой), `storage_sqlite.py` — dev-двойник
|
||
(ограничен: игры/доски/брони/миксы сайта/сброс нумерации → 501);
|
||
`js/app.js` + `index.html` + `css/` — панель персонала; `seed/*.json` —
|
||
каталог; `migrate/notes_boards.sql` — канонический DDL досок.
|
||
- `site/` — React + Vite + Tailwind, собирается в статику под nginx.
|
||
`nginx.conf` — edge: лимит `POST /api/orders` (10/мин с IP), real_ip из
|
||
X-Forwarded-For, `/media/` отдаёт фото CRM; `src/lib/crm.ts` — мост к API.
|
||
- `docker-compose.yml` — site (наружу `127.0.0.1:8080`), crm
|
||
(`127.0.0.1:47613`), db (PostgreSQL 17, не публикуется). Секреты только
|
||
из `.env`.
|
||
- Роли CRM: владелец / администратор / официант / кальянщик — матрица в
|
||
README; пять слоёв защиты заказов (nginx-лимит → лимит на гостя →
|
||
дедупликация → глобальный тормоз → honeypot).
|
||
- Git в проекте НЕТ: перед правкой делай снимок (см. ниже), diff собирай
|
||
по снимкам.
|
||
|
||
Фикс может жить на любом из трёх слоёв (nginx → server.py → storage).
|
||
Логика storage продублирована в двух бэкендах: фикс в `storage.py` почти
|
||
всегда должен зеркалиться в `storage_sqlite.py` (и наоборот) — аудит не раз
|
||
писал «в обоих бэкендах». Учитывай и панель (`crm/js/app.js`): что она
|
||
ожидает от изменённого эндпоинта.
|
||
|
||
## Патч-контракт (до правки)
|
||
|
||
Изучи саму реализацию, её прямых вызователей, соседние хелперы и разметку
|
||
роль×эндпоинт в README. Установи из кода, а не из находки:
|
||
|
||
- атакующий вход и конкретный путь источник → sink (или сломанный контроль);
|
||
- security-инвариант и узкую общую границу, где его enforce;
|
||
- легитимное поведение, API-контракты, семантику ошибок, совместимость;
|
||
- ближайшие прецеденты в этом же коде (экранирование `esc()` в панели,
|
||
параметризованный SQL `%s`, whitelist статики, лимитеры).
|
||
|
||
Находка — задача о потоке данных и границе, а не о примере входа из отчёта.
|
||
Проверь эквивалентные кодировки, alternate-формы парсера, алиасы, всех
|
||
вызователей, оба бэкенда и каждую копию security-чувствительного состояния,
|
||
которая может обойти фикс. Небезопасное состояние обрабатывай явно — не
|
||
принимай, не обрезай и не переинтерпретируй его молча.
|
||
|
||
## Предпатчевое расследование
|
||
|
||
До правки запусти один свежий read-only субагент (Agent tool, general-purpose,
|
||
с инструкцией не менять файлы). Если делегирование недоступно — сделай тот же
|
||
проход сам отдельно, свежим взглядом:
|
||
|
||
- **Исследователь границы и совместимости:** независимо трассирует путь
|
||
источник → sink, находит общую границу enforcement, затронутые входы,
|
||
альтернативные представления и состояния, переходы валидация →
|
||
использование, конкретные sibling-пути и обходы. Фиксирует легитимные
|
||
сценарии и публичное поведение, которые обязаны остаться, и проверяет
|
||
вызователей, режимы, ошибки, побочные эффекты, конвенции, готовые хелперы
|
||
и применимые команды проверки.
|
||
|
||
Расследование требует ссылок на файлы/строки проекта и разделения фактов,
|
||
выводов и открытых вопросов. По завершении сверь с собственной трассировкой
|
||
и выбери границу фикса.
|
||
|
||
## Снимки вместо git
|
||
|
||
Перед первым изменением скопируй оригиналы изменяемых файлов в
|
||
`AUDIT/FIXES/.snapshots/<ID>/` (создай каталог). По снимкам собери diff для
|
||
ревью и отчёта (PowerShell `Compare-Object`, `fc` или `git diff --no-index`,
|
||
если git есть на хосте). Не оставляй временных файлов-двойников вида `.bak`
|
||
в рабочем дереве после завершения.
|
||
|
||
## Реализация
|
||
|
||
1. Протрассируй путь и верни `no_change`, если код уже безопасен — без
|
||
спекулятивных правок.
|
||
2. Где возможно, прогони через границу минимальный высокосигнальный repro
|
||
и один легитимный контрольный запрос по тому же пути (живой стенд ниже).
|
||
3. Сделай наименьший фикс на общей границе. Предпочитай соседние хелперы и
|
||
установленные API. Не расширяйся на редизайн, уборку и соседние находки.
|
||
4. До верификации атакуй свой патч, а не защищай: проверь каждого прямого
|
||
вызователя изменённого хелпера и обе ветки каждого изменённого условия.
|
||
Найди (а) один sibling-путь, представление или копию, которые всё ещё
|
||
доходят до sink, (б) один обычный/дефолтный вход, который патч начал
|
||
отвергать или переинтерпретировать. Если хоть одно есть — переделай.
|
||
5. Верифицируй по гейтам (порядок обязателен).
|
||
|
||
## Гейты верификации
|
||
|
||
1. **Синтаксис/сборка** — только изменённое:
|
||
- Python: `python -m py_compile crm\server.py crm\storage.py crm\storage_sqlite.py crm\notes_api.py` (в crm/);
|
||
- сайт (если тронут `site/`): из `site/` — `npx tsc --noEmit`, затем `npm run build`;
|
||
- движок миксов (если тронут `site/src` миксы/стоп-лист): `npm run test:engine`.
|
||
2. **Security-триггер** — repro находки на живом стенде больше не
|
||
воспроизводится + один альтернативный класс вредоносного входа.
|
||
3. **Легитимный контроль** — обычный сценарий по тому же пути работает;
|
||
соседние роли не сломаны (токены owner/admin/waiter — матрица в README).
|
||
|
||
### Живой стенд
|
||
|
||
- **Postgres-стек** (нужен для игр, досок, броней, миксов, сброса нумерации —
|
||
в sqlite это 501): `docker compose up -d --build`, CRM на
|
||
`127.0.0.1:47613`, сайт на `127.0.0.1:8080`.
|
||
- **SQLite-стенд** без Docker: `start-crm.bat` (`CRM_DB=sqlite`,
|
||
`CRM_ALLOW_DEFAULT_PASSWORDS=1`, порт 47613).
|
||
- Сайт-дев: в `site/` `npm run dev`; если docker уже держит 8080 —
|
||
`npm run dev -- --port 8090 --strictPort`.
|
||
- Перед стартом проверь, кто слушает порт (`netstat -ano | findstr :47613`):
|
||
8080/47613 держит docker-стек, **8081 — чужой процесс, его не трогать**.
|
||
- Доступы стенда — из `.env` (SMOKE_* нет в .env — задавай при запуске скрипта).
|
||
|
||
### Регрессионный артефакт
|
||
|
||
Проект верифицирует фиксы smoke-скриптами по образцу `crm/_security_smoke.mjs`:
|
||
`fetch` против `127.0.0.1:47613`, PASS/FAIL, ненулевой exit при провале,
|
||
пароли через env (`SMOKE_OWNER_PW`, `SMOKE_WAITER_PW`). Добавь проверки
|
||
фикса в `_security_smoke.mjs` или создай сфокусированный `crm/_fix_<ID>.mjs`
|
||
по тому же образцу. Помни побочные эффекты: lockout-тест закрывает
|
||
`waiter.mark` на 15 минут (порядок проверок важен), тест смены пароля обязан
|
||
возвращать пароль обратно.
|
||
|
||
## Ревью кандидата
|
||
|
||
После реализации и фокусных проверок запусти одного свежего read-only
|
||
субагента (Agent tool; инструкции не редактировать и не делегировать). Дай
|
||
ему только находку, корень проекта, авторизованный объём, конвенции и текущий
|
||
diff кандидата — без обоснований, отчёта исследователя и заявлений, что
|
||
тесты прошли. Если делегирование недоступно — сделай этот проход сам,
|
||
отдельно, до финальной верификации:
|
||
|
||
- **Ревьюер обходов и регрессий:** реконструирует инвариант и ищет конкретный
|
||
выживший маршрут через затронутые входы, эквивалентные представления,
|
||
границы парсеров, алиасы, оба бэкенда, слой nginx и щели
|
||
валидация → использование; трассирует изменённые условия и вызователей на
|
||
предмет конкретной поломки легитимных входов, контрактов, ошибок,
|
||
побочных эффектов и конвенций.
|
||
|
||
Находки ревьюера — гипотезы: подтверждай их по коду или фокусным запуском,
|
||
правь только подтверждённые в рамках находки и совместимости. Затем
|
||
прогони затронутые гейты заново и убедись, что временных и посторонних
|
||
изменений не осталось. Только один цикл ревью.
|
||
|
||
Верни `blocked`, если находка может быть реальной, но не хватает существенных
|
||
данных, инструментария, доступа или это продуктовое решение владельца
|
||
(пример: №38 «waiter вне смены видит телефоны» из предрелизного аудита) —
|
||
тогда безопасный фикс невозможно завершить или проверить.
|
||
|
||
## Отчёт
|
||
|
||
Напиши `AUDIT/FIXES/<ID>.md` и проставь в источнике находки (`AUDIT/FINDINGS.md`
|
||
или `АУДИТ-ПЕРЕД-РЕЛИЗОМ.md`) статус фикса со ссылкой на отчёт. В финальном
|
||
ответе в чате обязательно:
|
||
|
||
- outcome: `fixed` / `no_change` / `blocked`
|
||
- конкретный уязвимый путь, security-инвариант и легитимное поведение, которое
|
||
обязано было сохраниться
|
||
- выбранная стратегия и почему это самый узкий полный вариант (либо — какое
|
||
решение владельца отсутствует при `blocked`)
|
||
- изменённые файлы и добавленные тесты/артефакты
|
||
- команды по гейтам с результатами pass/fail/unknown
|
||
- как показано, что исходная находка больше не воспроизводится
|
||
- как показано, что легитимное поведение сохранилось
|
||
- оставшиеся пробелы и пропущенные проверки, если есть
|
||
|
||
## Жёсткие правила
|
||
|
||
- Не докладывай `fixed`, пока не пройдены все гейты по порядку. Проверку
|
||
можно пропустить только с обоснованием по коду; недоступная существенная
|
||
проверка = `blocked` и явное указание, чего не хватило.
|
||
- Не полагайся только на чтение кода, когда возможен фокусный repro на стенде.
|
||
- Не расширяй патч на уборку, соседние находки и редизайн без доказательства,
|
||
что без этого граница не закрывается.
|
||
- Не удаляй и не затирай правки пользователя — git нет, снимки единственная
|
||
страховка; не трогай `crm/data/`, `crm/media/`, `.env` и `site/dist`.
|
||
- Не ослабляй авторизацию, ролевую матрицу, пять слоёв защиты заказов,
|
||
валидацию входа, fail-fast на дефолтных паролях и лимитеры ради зелёных
|
||
проверок.
|
||
- Не деплой на прод (`docker compose up -d --build site crm`) без явной
|
||
команды владельца — верификация идёт на локальном стенде.
|
||
- Не прячь пробелы доказательств: если среда не дала проверить — назови
|
||
команду и чего не хватает.
|