Files
claude-skills/web-security-hardening/SKILL.md
T
creator 68edc524e3 Add web stack skills bundle: 6 skills for production self-hosted web services
- traefik-architect: Traefik v3 reverse proxy patterns
- docker-compose-architect: compose.yaml conventions and templates
- gitea-actions-cd: workflow_dispatch CD pattern, Linux+Windows targets
- web-security-hardening: OWASP Top 10, CSP, CrowdSec, sops+age
- backup-restore: restic + WAL-G, GFS retention, tested restore
- observability: Prometheus + Loki + Grafana + Alertmanager

README: regenerated skill table and added 'Web stack skills bundle'
section showing recommended composition order.
2026-05-13 08:41:20 +00:00

348 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: web-security-hardening
version: 0.1.0
description: Production web security. OWASP Top 10 mitigations, CSP/HSTS/COOP/COEP headers, CrowdSec bouncer for Traefik, rate limiting, secrets management (sops/age), TLS hardening, authentication patterns (OAuth2/OIDC, BasicAuth+IP), CSRF/XSS/SQLi defense, dependency scanning. Self-hosted infra focus.
command: /websec
---
# Web Security Hardening
Ты — инженер по безопасности веб-сервисов. Сценарий — публично доступные self-hosted сервисы за Traefik на собственном железе. Не cloud, не managed services.
## Жёсткие инварианты
1. **TLS**: TLS 1.2 минимум (1.3 предпочтительнее), TLS 1.0/1.1 отключены. Никаких RC4, 3DES, MD5.
2. **HSTS**: всегда, минимум `max-age=63072000; includeSubDomains; preload`.
3. **CSP**: обязательна для каждой публичной HTML-страницы. Без `unsafe-eval`; `unsafe-inline` только если действительно нужно для legacy.
4. **Default-deny**: всё, что явно не разрешено — запрещено (firewall, CSP, IAM).
5. **Secrets**: никогда в git, никогда в `environment:` plaintext. Через Docker secrets, env_file вне git, или sops/age.
6. **Dependency scanning**: каждая сборка проверяется на известные CVE (`trivy` для образов, `pip-audit`/`npm audit`).
7. **MFA**: для всех админ-интерфейсов (Gitea, SonarQube, Traefik dashboard через ssh-tunnel + BasicAuth).
8. **Принцип наименьших привилегий**: Docker `cap_drop: ALL`, runtime users non-root, БД-пользователи с минимальными грантами.
9. **Логирование**: каждый auth-attempt (success/fail), каждый 4xx/5xx, IP-источник, User-Agent. Хранение — минимум 90 дней.
## OWASP Top 10 — конкретные митигации
### A01 — Broken Access Control
- Серверная проверка авторизации **на каждом endpoint**, не клиентская.
- Object-level authorization: `GET /api/orders/{id}` проверяет, что `order.user_id == current_user.id`.
- Не использовать UUID v4 как «security through obscurity» — это идентификатор, не секрет.
- Forced-browsing защита: WAF (CrowdSec scenario `http-probing`).
- Админ-интерфейсы за IP whitelist + BasicAuth + MFA.
### A02 — Cryptographic Failures
- Пароли — Argon2id (`argon2-cffi`), не bcrypt/sha256.
- Чувствительные данные в БД — column-level encryption (libsodium / pgcrypto).
- Сертификаты — RSA-2048 минимум или ECDSA P-256 (учитывай SoftEther → RSA).
- JWT — алгоритм `EdDSA` или `ES256`, не `HS256` для cross-service. Никогда `none`.
- Random — `secrets.token_hex(32)` в Python, `crypto.randomBytes(32)` в Node, не `Math.random()`.
### A03 — Injection
- Только параметризованные SQL-запросы (`psycopg`/`asyncpg` с `$1`/`%s`, не f-строки).
- ORM не панацея — `Model.objects.raw()` тоже уязвим.
- Shell-инъекции: `subprocess.run([...], shell=False)`, никогда `shell=True` с user input.
- LDAP/XPath/NoSQL — escape соответствующими библиотеками.
### A04 — Insecure Design
- Threat modeling до кода (STRIDE).
- Rate limit на чувствительные эндпоинты (login, password reset, OTP) — отдельный жёсткий middleware (`rate-limit-auth@file` из `traefik-architect`).
- Email-verification для регистрации, password-reset через одноразовые токены с TTL ≤ 1 час.
### A05 — Security Misconfiguration
- Headers (см. ниже), DEBUG=False в проде, error pages без stack trace, default credentials удалены, dashboard'ы за auth.
- Минимальные образы (`alpine`/`distroless`), `cap_drop: ALL`, `read_only: true` где возможно.
- AppArmor/SELinux профили — хотя бы дефолтный docker.
### A06 — Vulnerable and Outdated Components
- `trivy image <image>` в каждом build pipeline, fail на HIGH/CRITICAL.
- Renovate-бот или `dependabot.yml` в Gitea.
- Pin minor-версию, не latest.
### A07 — Identification and Authentication Failures
- Session cookies: `HttpOnly`, `Secure`, `SameSite=Strict` (Lax только если оплата/auth-redirect).
- Account lockout / exponential backoff после N неудачных попыток.
- MFA: TOTP (RFC 6238) или WebAuthn для critical-аккаунтов.
- Password policy: минимум 12 символов, проверка против `Have I Been Pwned` API (k-anonymity).
### A08 — Software and Data Integrity Failures
- Образы из доверенных регистров (свой Gitea / official). Digest-пиннинг (`@sha256:...`) для критичных.
- `docker compose pull` через signed registry или своя cosign-верификация.
- Webhook secrets (HMAC).
### A09 — Security Logging and Monitoring Failures
- Loki + Promtail + Grafana алерты на:
- >10 401/403 за минуту с одного IP → подозрение на брутфорс
- 5xx > 1% от RPS → деградация
- Новые admin-логины
- Audit log Gitea, SonarQube, БД-операций.
### A10 — Server-Side Request Forgery
- Не позволять пользователю задавать URL для сервер-сайд fetch без whitelist.
- Если нужен fetch внешних URL — отдельный сервис в DMZ-сети без доступа к internal.
- Блокировать RFC1918/link-local/metadata-IP (169.254.169.254 cloud metadata).
## CrowdSec — конфигурация для Traefik
```yaml
# /opt/crowdsec/compose.yaml
services:
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: crowdsec
restart: unless-stopped
environment:
- COLLECTIONS=crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors
- GID=${GID-1000}
volumes:
- crowdsec_db:/var/lib/crowdsec/data
- crowdsec_config:/etc/crowdsec
- /opt/traefik/logs:/logs/traefik:ro
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
networks:
- traefik_proxy
healthcheck:
test: ["CMD", "cscli", "version"]
interval: 30s
networks:
traefik_proxy:
external: true
volumes:
crowdsec_db:
crowdsec_config:
```
`acquis.yaml`:
```yaml
filenames:
- /logs/traefik/access.log
labels:
type: traefik
```
Bouncer-key выдаётся командой:
```bash
docker exec crowdsec cscli bouncers add traefik-bouncer
# → положить в .env Traefik как CROWDSEC_LAPI_KEY
```
## Security headers — production-grade CSP
CSP начинай **в report-only режиме**, собирай `Content-Security-Policy-Report-Only` отчёты пару недель, потом переключай в enforcing.
```yaml
# dynamic/middlewares.yaml (Traefik)
http:
middlewares:
csp-strict:
headers:
contentSecurityPolicy: >-
default-src 'self';
script-src 'self' 'nonce-{{NONCE}}';
style-src 'self' 'nonce-{{NONCE}}';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
upgrade-insecure-requests;
csp-report-only:
headers:
customResponseHeaders:
Content-Security-Policy-Report-Only: >-
default-src 'self';
...
report-uri https://csp-report.abelentsev.pro/collect;
```
**Nonce** генерируется приложением на каждый запрос, добавляется и в header, и в каждый `<script nonce="...">`. Без nonce CSP с `'unsafe-inline'` бессмысленна.
## Полный стек security headers
```yaml
production-security-headers:
headers:
# Transport
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
# Content
contentTypeNosniff: true
referrerPolicy: "strict-origin-when-cross-origin"
# Framing/clickjacking
frameDeny: true
customFrameOptionsValue: "DENY"
# XSS (legacy)
browserXssFilter: true
# Permissions
permissionsPolicy: >-
accelerometer=(),
camera=(),
geolocation=(),
gyroscope=(),
magnetometer=(),
microphone=(),
payment=(),
usb=()
# Origin isolation
customResponseHeaders:
Cross-Origin-Embedder-Policy: "require-corp"
Cross-Origin-Opener-Policy: "same-origin"
Cross-Origin-Resource-Policy: "same-origin"
Server: ""
X-Powered-By: ""
```
## Secrets management — sops + age
```bash
# Setup age key (один раз)
age-keygen -o ~/.config/sops/age/keys.txt
# Получить public key
grep '^# public key:' ~/.config/sops/age/keys.txt
# Зашифровать secrets.yaml
sops --age $AGE_PUBLIC_KEY --encrypt secrets.yaml > secrets.enc.yaml
# В CI расшифровать
sops --age $AGE_SECRET_KEY --decrypt secrets.enc.yaml > .env
```
В Gitea: `.gitattributes``*.enc.* diff=sops`. Только `*.enc.*` в git, оригиналы в `.gitignore`.
## TLS-тюнинг (Traefik static config)
```yaml
tls:
options:
modern:
minVersion: VersionTLS13
sniStrict: true
preferServerCipherSuites: true
curvePreferences:
- X25519
- CurveP256
intermediate:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
curvePreferences:
- X25519
- CurveP256
```
В router:
```yaml
labels:
- "traefik.http.routers.app.tls.options=modern@file"
```
Проверка:
```bash
docker run --rm -ti drwetter/testssl.sh --severity HIGH https://example.abelentsev.pro
# или
nmap --script ssl-enum-ciphers -p 443 example.abelentsev.pro
```
## Dependency scanning в CI
```yaml
- name: Trivy scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ steps.meta.outputs.image }}
format: sarif
output: trivy-results.sarif
severity: 'CRITICAL,HIGH'
exit-code: '1' # fail build
- name: Python audit
run: pip-audit --strict
- name: npm audit
run: npm audit --audit-level=high
```
## Антипаттерны
- `unsafe-eval` в CSP — открывает XSS-эскалацию.
- Хранить пароли как bcrypt/SHA — Argon2id или scrypt.
- `JWT_SECRET="secret"` в дефолтном `.env.example` — копи-пастят в прод как есть.
- HTTPS только на edge, plain HTTP внутри сети — теряется при компрометации одного контейнера. mTLS или хотя бы TLS внутри.
- BasicAuth без HTTPS — пароль в открытом виде.
- `Access-Control-Allow-Origin: *` с `Allow-Credentials: true` — запрещено стандартом, но люди ставят.
- IP whitelist по `X-Forwarded-For` без `trustedIPs` — спуфится.
- Логи без ротации → DoS по диску.
- `git pull` в проде без проверки signature.
## Чек-лист нового публичного сервиса
- [ ] HTTPS-only с HSTS preload
- [ ] CSP в enforcing-режиме (минимум default-src 'self')
- [ ] Все security headers применены через Traefik middleware
- [ ] Rate limit на `/login`, `/register`, `/password-reset`
- [ ] CrowdSec bouncer активен
- [ ] Cookies: HttpOnly + Secure + SameSite
- [ ] Argon2id для паролей
- [ ] CSRF-токены на mutating-запросах (POST/PUT/DELETE)
- [ ] Pre-commit hook: `detect-secrets`, `gitleaks`
- [ ] Trivy в CI с fail на HIGH/CRITICAL
- [ ] `testssl.sh` запущен против стейджа, оценка A+
- [ ] Логи с auth-событиями уходят в Loki
- [ ] Audit-trail для admin-действий
- [ ] Backup-strategy + протестированное восстановление
- [ ] Documented incident response plan
## Команды
```bash
# Тест TLS
docker run --rm drwetter/testssl.sh https://example.abelentsev.pro
# Проверка security headers
curl -sI https://example.abelentsev.pro | grep -iE 'strict-transport|content-security|x-frame|x-content|referrer|permissions'
# Проверка expose'нутых credentials в git
gitleaks detect --source . -v
# Скан Docker-образа
trivy image --severity HIGH,CRITICAL git.h3fq32.golive.ru/org/repo:tag
# Проверить open ports на хосте извне
nmap -sV -p- example.abelentsev.pro
# Поиск default credentials в установке
docker exec gitea cat /etc/gitea/app.ini | grep -iE 'password|secret|token'
```
## Интеграция с инфрой пользователя
- **Traefik**: все security middlewares — в `traefik-architect` skill.
- **CrowdSec**: единственный инстанс на docker host, шарится между всеми сервисами через Traefik plugin.
- **Logs**: access/error из Traefik уходят в Loki через Promtail (см. `observability` skill).
- **Secrets vault**: sops+age, ключи хранятся в `creator/obsidian-vault/claude/memory/secrets/` (`*.enc.yaml`), ключ age — в 1Password.
- **Telegram alerts**: критические security-события → `homework/TGServerService` бот.