Findings from the 2026-06-24 security review, now mandatory before this
platform leaves the LAN for a public VPS (see budget/externalization note):
- Timing attack on admin password: hmac.compare_digest instead of !=
- CORS wildcard removed, restricted to ALLOWED_ORIGINS (needed for credentials)
- hCaptcha now fails closed on network error instead of open
- HMAC signatures use the full SHA-256 digest instead of a 64-bit truncation
- Consultation-scoped submissions were missing the consent/cooldown checks
the global submission route already had
- Admin search capped at 200 chars (ILIKE cost)
- Webhook URLs validated against SSRF (private/loopback/link-local/metadata
IPs rejected, re-checked at trigger time too, redirects not followed)
- Flood tracker backed by Redis when REDIS_URL is set (shared across Gunicorn
workers, survives restarts), falls back to in-memory otherwise
- Admin session moved from sessionStorage/Bearer token to an httpOnly+Secure+
SameSite=Strict cookie; new /api/admin/logout and /api/admin/session routes
Also found while wiring ProxyFix for the cookie's Secure flag: the app never
told Flask about the Nginx reverse proxy, so request.remote_addr saw Nginx's
own IP for every request in production — silently breaking rate limiting,
flood detection, and IP blacklist (everyone would collapse onto one shared
key). Added ProxyFix (1 hop).
CSV exports now stream instead of loading everything into memory. AI prompts
delimit citizen-submitted content explicitly and instruct the model to treat
it as inert data, never instructions, to reduce prompt injection surface.
Docs (DEX.md, SECURITE_ANTI_ABUS.md, README.md, DEPLOIEMENT.md) and
dev-local.sh updated to match.
Implements a two-layer approach to ensure legal texts are accurate and current:
Option A — runtime injection (legifrance_client.py):
- LegifranceClient: OAuth2 via PISTE (oauth.piste.gouv.fr), fetches consolidated
article text via api.piste.gouv.fr/dila/legifrance/lf-engine-app
- Redis cache with 24h TTL (in-memory fallback if no Redis)
- build_filter_prompt() injects live texts into the prompt with explicit priority
over static descriptions — model uses official Légifrance text, not training memory
- Graceful fallback to static prompt if API is unavailable — filtering never blocks
Option B — weekly sync script (scripts/check_legal_refs.py):
- Reads legal_refs.yaml (11 tracked articles, priority critical/high/medium)
- Fetches current text from Légifrance, diffs against stored baseline
- --init: resolves LEGIARTI IDs on first run
- --check: report only; --update: saves new texts to YAML
- Exit code 2 when changes detected (CI/n8n-compatible)
Supporting changes:
- legal_refs.yaml: tracked articles with code LEGITEXT IDs, priorities, rationale
- requirements.txt: add pyyaml>=6.0.2 and requests>=2.32.0
- .env.example: document PISTE_CLIENT_ID, PISTE_CLIENT_SECRET, LEGIFRANCE_CACHE_TTL
- transparence.tsx: document the Légifrance injection for public transparency
- Fixed portal URL: piste.gouv.fr (not piste.api.gouv.fr — verified June 2026)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Venv Python : création automatique si absent (détecte python3.11,
python3, python >= 3.9 ; erreur guidée si introuvable)
- Dépendances pip : pip install -r requirements.txt à chaque lancement
(no-op si rien n'a changé, garantit la cohérence après un pull)
- pnpm : installation automatique via corepack (si dispo) ou npm
- Node.js : non installable sans root — message d'erreur avec commandes
exactes pour Rocky/RHEL, Debian/Ubuntu et nvm
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nouveau script scripts/dev-local.sh : lance Flask (port 8080) et le
serveur Vite dev (port 5173) en parallèle avec nettoyage propre
(trap EXIT/INT/TERM). VITE_API_URL est injecté pour pointer vers
Flask local, accessible depuis le client via tunnel SSH.
- docs/INSTALL_ROCKY.md : nouvelle section 16 avec schéma ASCII,
procédure pas-à-pas et commande tunnel SSH exacte.
- docs/INSTALL_ROCKY.md : corrige les deux références cassées à
vite.config.selfhost.ts (sections 9 et 15.1) supprimé lors de la
dé-Replit-isation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- .env.example : variables à jour (Mistral, SECRET_KEY, ADMIN_SECRET, Redis,
hCaptcha, anti-abus) — l'ancienne version référençait encore OpenAI uniquement
- Nouveau set-domain.sh : supprime la référence à vite.config.selfhost.ts supprimé
- Nouvelle page /consultations : index public des consultations actives/clôturées,
toggle "afficher les clôturées", lien dans le footer
- App.tsx : route /consultations + lien footer Consultations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a detailed guide for installing the application on Rocky Linux, including systemd service setup and Nginx configuration. Streamline domain setting by introducing a script to update environment variables and rebuild the frontend, and remove the URL input from the flyer component.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 3d999b96-66af-4728-92b9-3a39ade05f44
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/qCk7LE3
Replit-Helium-Checkpoint-Created: true