Мы никогда не склеиваем запрос строкой. Всегда плейсхолдеры драйвера, значения уходят отдельно от текста запроса:
row := db.QueryRowContext(ctx, "SELECT id FROM users WHERE email = $1", email)
Имена таблиц и колонок так подставить нельзя, для них белый список допустимых значений:
var allowed = map[string]string{"created": "created_at", "email": "email"}
col, ok := allowed[r.URL.Query().Get("sort")]
if !ok {
col = "created_at"
}
query := fmt.Sprintf("SELECT id FROM users ORDER BY %s DESC", col)Дополнительно ограниченные права у пользователя базы, чтобы ошибка не стоила всей схемы.
XSS
Пользовательский текст не попадает в HTML напрямую. Рендерим через
html/template, он экранирует с учётом контекста, то есть по разному внутри тега, атрибута, href и блока <script>. text/template не экранирует ничего, в веб хендлере его быть не должно.Приводить ввод к
template.HTML нельзя, это отключение защиты. Если пользователю нужна разметка, чистим санитайзером вроде bluemonday по белому списку тегов.Для API хватает
encoding/json, но заголовки обязательны, иначе браузер угадает тип и отрендерит ответ как страницу:w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")Сверху
Content-Security-Policy и куки с HttpOnly, чтобы случайная дыра не привела к краже сессии.🐸 Библиотека Go для собеса