fix(ops/alloy): второй слой маскировки query-секретов на сборщике логов (apps + infra) — defence-in-depth к #3353 #3377

Merged
bot-backend merged 1 commit from fix/3354-alloy-query-secret-scrub into main 2026-09-05 20:53:43 +00:00
2 changed files with 32 additions and 0 deletions

View file

@ -172,6 +172,26 @@ loki.process "scrub_credentials" {
expression = "://[^:@/ ]+:([^@ ]+)@"
replace = "***"
}
// ── Секреты в query-строке (#3354) ─────────────────────────────────────────
// Прод-факт (#3154): uvicorn пишет в access-log ПОЛНЫЙ путь с query, и туда
// уезжал `?secret=<64 hex>` вебхука GlitchTip. Приложение чистит это у себя
// (tradein-mvp/backend/app/core/log_scrub.py), здесь — второй слой на случай
// строки, пришедшей мимо фильтра: другой процесс, sidecar, будущий логгер.
//
// Множество имён держим ОДИНАКОВЫМ с log_scrub.py. Префикс `[\w.-]*` перед
// альтернацией — ради суффиксных имён (`client_secret`, `refresh_token`,
// `webhook_secret`); значение обрывается на `&`, пробеле или кавычке, потому
// что access-строка uvicorn обрамляет запрос кавычками.
//
// Группа захвата стоит на ЗНАЧЕНИИ, а не на имени параметра: Alloy заменяет
// содержимое групп, а не весь совпавший фрагмент (ровно как у DSN выше — там
// группа на пароле, поэтому пользователь и хост уцелевают). Обернуть группой
// `?secret=` значило бы затереть имя и оставить сам секрет.
stage.replace {
expression = "(?i)[?&][\\w.-]*(?:secret|token|api[-_]?key|apikey|access[-_]?token|password|signature|sig)=([^&\\s\"'<>]+)"
replace = "***"
}
}
loki.relabel "journal" {

View file

@ -131,6 +131,18 @@ loki.process "scrub_credentials" {
expression = "://[^:@/ ]+:([^@ ]+)@"
replace = "***"
}
// ── Секреты в query-строке (#3354) ─────────────────────────────────────────
// То же выражение, что в alloy-apps.alloy, и по той же причине — подробности
// там. Здесь оно нужно не меньше: на инфра-хосте живут Forgejo и GlitchTip,
// у обоих есть ручки с `?token=` в адресе, и их логи идут в тот же Loki.
//
// Группа захвата — на ЗНАЧЕНИИ: Alloy заменяет содержимое групп, а не весь
// совпавший фрагмент (как у DSN выше, где группа на пароле).
stage.replace {
expression = "(?i)[?&][\\w.-]*(?:secret|token|api[-_]?key|apikey|access[-_]?token|password|signature|sig)=([^&\\s\"'<>]+)"
replace = "***"
}
}
loki.relabel "journal" {