fix(ops): defauts surs a la copie, CDN epingles, actions epinglees

Quatre taches de la matrice du rapport, toutes sans dependance, qu aucune
liste de « ce qui reste » ne reprenait.

OPS-003 — app/.env.exemple disait « copiez ce fichier et remplissez les
valeurs pour la production », puis posait FLASK_DEBUG=true,
SESSION_COOKIE_SECURE=false et FORCE_HTTPS=false. Le debogueur Werkzeug
execute du code soumis par le navigateur : cette ligne transformait un
copier-coller en shell distant. Chaque valeur est desormais sure a la
copie, et le fichier refuse de demarrer tant que les deux secrets
obligatoires ne sont pas remplis plutot que de demarrer grand ouvert.

Renomme en .env.example : l orthographe francaise ne correspondait pas a
l exception !.env.example du .gitignore, donc le fichier n etait suivi que
par accident de l ordre des regles. Les deux points de la decision ouverte
du §8 tombent d un seul git mv.

OPS-002 — trusted_proxy='*' et HOST ne sont plus soudes dans wsgi.py. Les
defauts sont **inchanges**, deliberement : choisir sans connaitre la
topologie coupe la prod si nginx est ailleurs, ou casse la limitation de
debit pour tout le monde si on cesse de croire X-Forwarded-For alors que
c etait la seule source d adresses. Ce sont maintenant des variables, les
valeurs sures sont dans .env.example pour un nouveau deploiement, et
docs/deployment.md donne les quatre topologies avec la valeur de chacune.
wsgi.py avertit au demarrage tant que les deux defauts sont en place.

Le commentaire de HOST annoncait « bind to localhost by default » a cote
d un defaut a 0.0.0.0 : il decrivait l intention pendant que le code
faisait l inverse. Il dit maintenant ce qu il fait.

QUA-004 — Font Awesome et FullCalendar etaient charges sans empreinte,
depuis des hotes que la CSP autorise nommement. Qui controle ces CDN
controlait ce qui s execute sur chaque page. Empreintes posees, avec ce
que SRI promet et ce qu il ne promet pas ecrit a cote : ca fige le fichier,
ca ne prouve pas qu il etait honnete au moment du calcul.

**Le CSS de FullCalendar n existait pas.** La v6 embarque ses styles dans
le JS et ce fichier n est pas publie : le <link> repondait 404 a chaque
ouverture du calendrier depuis la montee de version. Une feuille de style
en echec est silencieuse dans le navigateur, c est ce qui l a fait durer.

CI-003 — actions epinglees sur un commit, version en commentaire, dans les
deux forges. Un tag est un pointeur mobile : deplacer v4 fait executer du
code arbitraire dans le job qui detient la cle SSH de production. Ce job
recoit aussi enfin un bloc permissions.

517 tests.
This commit is contained in:
GGThed
2026-08-11 14:42:23 -04:00
parent 39808dd04e
commit 3882b6035f
10 changed files with 401 additions and 55 deletions
+46
View File
@@ -62,6 +62,52 @@ BACKUP_RETENTION_DAYS=30
**Important**: Never commit `.env` to version control.
Start from `app/.env.example`, which carries production-safe defaults and
documents every variable. Copying it verbatim gives a configuration that
refuses to start until `SECRET_KEY` and `DATABASE_URL` are filled in, rather
than one that starts and is wide open (OPS-003).
### Binding and proxy trust — read this before going live (OPS-002)
Two variables decide whether the rate limiter, the account lockout and the
audit log mean anything: `HOST` and `TRUSTED_PROXY`. Their built-in defaults
(`0.0.0.0` and `*`) are what this application has always done, kept so that
an existing deployment is not changed under it — **they are not the right
values**, and which values are right depends on your topology.
`TRUSTED_PROXY` decides whose `X-Forwarded-For` Waitress believes, and
therefore which address is recorded and counted. Getting it wrong fails in
one of two directions:
- **too trusting** — anyone who can reach the app without going through nginx
sets their own client address. Rate limiting, lockout and the `ip=` field
in `auth.log` all become suggestions;
- **not trusting enough** — every request appears to come from the proxy. One
shared bucket, so the first person to mistype a password five times locks
the limiter for the whole club.
Find your case:
| Topology | `HOST` | `TRUSTED_PROXY` | Why |
|---|---|---|---|
| **nginx on the same machine as the app** (the common case) | `127.0.0.1` | `127.0.0.1` | Waitress is unreachable except through nginx, and only nginx's forwarded header is believed |
| **App in a Pterodactyl container, nginx elsewhere** | `0.0.0.0` | the proxy's address on the container network, e.g. `10.0.0.5` | The app must accept connections from outside the container, so it cannot bind to loopback. Name the proxy rather than trusting `*` |
| **Same as above, but the port is only reachable from the proxy** (firewall or container network) | `0.0.0.0` | `*` | Acceptable *only* because the network already prevents anyone else connecting. If that is not enforced, this is the first failure above |
| **No proxy at all** | `0.0.0.0` | *(empty)* | Nothing forwards anything; the peer address is the client |
To find out which one you are in, on the node:
```powershell
# Does anything answer on the app's port from outside the machine?
Test-NetConnection <public-ip> -Port 5000
# What address does nginx come from, as the app sees it?
# Set TRUSTED_PROXY= (empty) briefly, make one request, and read auth.log:
# the ip= field is then the real peer — which is the proxy.
```
`wsgi.py` prints a warning at startup while both defaults are in place, so an
unconfigured deployment says so in the Pterodactyl console.
## Step 3: Configure Nginx
1. Copy `app/nginx.conf` to your Nginx installation directory (e.g., `C:\nginx\conf\`)