Незакоммиченная работа сессии 2026-09-05 (уже на проде): - lib/owner.js — реестр владельцев файлов-результатов, owner-check во всех download-роутах (IDOR) - lib/limits.js — createLimiter(): skip для админа, ipKeyGenerator (фикс ERR_ERL_KEY_GEN_IPV6) - video: обработчики 'error' у ffmpeg/ffprobe (spawn ENOENT больше не роняет сервер), таймауты - pdf: runGhostscript/gsUnavailable, водяной знак с кириллицей (fontkit + TTF) - снятие лимитов размера/количества для админа в pdf/video - журнал сессии docs/sessions/2026-09-05-0231.md
11 KiB
11 KiB
2026-09-05 02:31 — ffmpeg/ghostscript в образ, водяной знак PDF, снятие лимитов админа
Сделано:
- Найдена причина «Ошибка загрузки» видео: в контейнере не было ни
ffmpeg/ffprobe, ниgs(контейнер поднимался из гологоnode:20-slim). ВgetVideoDuration()не было обработчика'error'→spawn ENOENTуходил вuncaughtException→process.exit(1), то есть загрузка видео роняла весь сервер. Лимиты размера были ни при чём. - Контейнер переведён на собственный образ: создан
/opt/sites/wadevelop/Dockerfile(FROM node:20-slim+ffmpeg+ghostscript), в/opt/sites/docker-compose.ymlсервисwadevelopпереключён сimage: node:20-slimнаbuild: ./wadevelop+image: wadevelop-node:latest. Образ собран, контейнер пересоздан. Проверено на бою:gs 10.00.0, ffmpeg с libx264/libvpx/libmp3lame/aac, тестовые mp4 и gif собираются. - PDF: водяной знак не работал вообще — в
public/pdf.htmlвsendRequest()отсутствовала веткаcase 'watermark', запрос падал вdefault(«Неизвестная операция: watermark»). Добавлена ветка, параметры сделаны опциональными, кириллица — через встраиваемый TTF (@pdf-lib/fontkit+ Manrope), т.к.StandardFonts.Helveticaкодирует только WinAnsi. - Админ освобождён от всех ограничений — единая фабрика лимитов
lib/limits.js(skip: isAdmin), сняты лимиты размера/количества в PDF и размера в video (включая клиентскую отсечку). - Закрыт IDOR на скачивании результатов во всех четырёх download-роутах через новый реестр
владельцев
lib/owner.js(SQLite, та же БД что и очередь) + случайные суффиксы в именах файлов. - Всё выкачено на прод и проверено: сайт 200,
/health200, стартовый лог чистый — спамERR_ERL_KEY_GEN_IPV6(4 шт. при каждом старте) исчез; реестр владельцев проверен прогоном на проде (свойtrue, чужойfalse, неизвестныйfalse).
Изменённые / созданные файлы:
- lib/owner.js — новый: реестр владельцев файлов-результатов
(
file_ownersвjobs.db),claim/isOwner/displayName, авточистка старше 24 ч - lib/limits.js — новый:
createLimiter()— общая фабрика rate-limit,skip: isAdmin, ключ черезipKeyGenerator(чинитERR_ERL_KEY_GEN_IPV6) - routes/video.js — обработчики
'error'для ffmpeg/ffprobe (crash-guard),FFMPEG_MISSING→ 503, таймаутVIDEO_TIMEOUT_MS(10 → 30 мин),-ssдо-iи-tвместо-to, прогресс от длительности результата, валидацияstartTime/endTime, owner-check в/download, снят лимит размера для админа - routes/pdf.js —
runGhostscript()+gsUnavailable()(503 вместоspawn gs ENOENT) для preview/compress/toImages, водяной знак с TTF и опциональными параметрами, owner-check в/download,crypto.randomBytesв именах, лимиты размера/количества сняты для админа - routes/compress.js — случайные суффиксы в именах архива и single-файла,
owner.claim, owner-check в/compress/download, лимитер через общую фабрику - server.js — owner-check в
/download/:filename - routes/api.js, routes/auth.js,
routes/favicon.js, routes/redirects.js,
routes/parser.js, routes/httpclient.js —
переведены на
createLimiter(в auth —skipAdmin: false) - public/pdf.html — ветка
case 'watermark'вsendRequest(), убрана обязательность текста, метка «(необязательно)» - public/video.html — детект админа вынесен на загрузку страницы
(раньше
/auth/meспрашивался уже после проверки размера — первый большой файл всегда отклонялся),MAX_SIZE = Infinityдля админа - package.json — добавлен
@pdf-lib/fontkit - На VPS:
/opt/sites/wadevelop/Dockerfile(новый),/opt/sites/docker-compose.yml(сервис наbuild)
Решения:
- Бинарники через Dockerfile, а не
aptв живом контейнере —docker exec apt-get installслетел бы при первом же пересоздании контейнера. - Кириллица в водяном знаке через
@pdf-lib/fontkit+public/vendor/fonts/manrope-600.ttf, с откатом на Helvetica + транслитерацию, если пакет/шрифт недоступны — чтобы код работал в любой среде, а не падал 500-й. - Владелец файла хранится в SQLite, а не в памяти — переживает рестарт; отсутствие записи трактуется как «чужой» (записи живут 24 ч против 30 мин у самих файлов).
- Лимит входа
/auth/loginдля админа НЕ снят — на этапе логина сессии ещё нет, отличить админа технически не от чего, и это защита самого аккаунта от подбора. - Правки делались от локальной копии: перед деплоем сверены md5 всех 13 затрагиваемых файлов —
VPS был идентичен git HEAD (
a3fb5a2), расхождений нет. - Заливка — через PowerShell, а не Bash: Git Bash конвертирует POSIX-пути (
/opt/...→C:/Program Files/Git/opt/...) и ломаетsftp_put.py.
Открытые вопросы / следующие шаги:
- Проверить под залогиненной сессией: водяной знак PDF (кириллица, дефолты) и конвертацию/ сжатие видео с прогрессом — у меня нет учётной записи для UI-проверки.
- Очередь не ограничивает параллельность —
queue.getPending()нигде не вызывается, каждый/video/convertспавнит ffmpeg немедленно. Пока ffmpeg отсутствовал, это не проявлялось; теперь 3–4 одновременные конвертации положат VPS. /mnt/webdata/storageживёт внутри контейнера, а не в томе — пересоздание контейнера (как сегодня) обнуляетjobs.dbи папки результатов. Нужен том вdocker-compose.yml.routes/api.js/ai/explainбьётся в192.168.31.100:11434(домашний PC) и127.0.0.1:8080— с VPS недоступно, объяснялка regex и security-аудит на проде мертвы.routes/logs.js(68 строк) нигде не подключён — мёртвый файл, ссылок на/logsв HTML нет.routes/api.js—req.on('close')дляAbortControllerрегистрируется после дочитывания стрима: отмена при отвале клиента не работает никогда.server.js:44-47— публичные/api/*смонтированы какapp.get('/api/settings', apiRouter); путь внутри роутера не совпадает, запрос проваливается дальше и обрабатывается вторым монтированиемapp.use('/api', ...). Работает случайно,publicLimiterсрабатывает не там.- Хвосты прошлого аудита: DNS-rebinding pinning (HIGH-14), обновление 36 уязвимых npm-зависимостей, сведение VPS-прод и rpi4-двойника в один git.
- Локальные правки не закоммичены — рабочее дерево грязное (14 изменённых + 2 новых файла).
Как продолжить (для чистой сессии):
- Контекст: прод = VPS 195.140.146.84, контейнер
wadevelop, код/opt/sites/www/wadevelop.ru/, теперь собирается из/opt/sites/wadevelop/Dockerfile. rpi4 — двойник, НЕ деплоить. - SSH:
python3 C:/Users/tream/.claude/ssh_cmd.py root@195.140.146.84 "<cmd>". Заливка:python3 C:/Users/tream/.claude/sftp_put.py root@195.140.146.84 <remote_dir> <files>— запускать через PowerShell, Bash ломает POSIX-пути. - Пересборка образа:
cd /opt/sites && docker compose build wadevelop && docker compose up -d wadevelop. Обычный деплой без смены зависимостей: заливка файлов +docker restart wadevelop. - Проверка:
curl -s -o /dev/null -w "%{http_code}" https://wadevelop.ru/(200);docker logs wadevelop --tail 20;docker exec wadevelop which ffmpeg ffprobe gs. - Откат: бэкапы
*.bak-20260905в/opt/sites/www/wadevelop.ru/(13 файлов) и/opt/sites/docker-compose.yml.bak-20260905.
Ссылки:
- Прод: https://wadevelop.ru — VPS 195.140.146.84 (FirstVDS, treamz.fvds.ru), контейнер
wadevelop - Предыдущая сессия: 2026-07-24 23:48 — аудит безопасности
- Git: локаль на коммите
a3fb5a2+ незакоммиченные правки этой сессии