1.0.9 — imitation d'empreinte TLS en seconde tentative
CI / Backend — lint, types, tests (push) Successful in 32s
CI / Interface — types et compilation (push) Successful in 14s
CI / Construction des images (sans publication) (push) Successful in 1m35s
Publication / Images vers Harbor (push) Successful in 2m40s

This commit is contained in:
hcornet committed 2026-09-13 11:17:49 +02:00
1 parent 12973dab5d
commit d215b32a89
11 files changed
+86 -18

No files matched your search

+14
View File
@@ -2,6 +2,20 @@
Format inspiré de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/). Format inspiré de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/).
## [1.0.9] — 2026-09-13
### Ajouté
- **Imitation d'empreinte TLS sur la seconde tentative.** Les protections
modernes identifient un client par son empreinte TLS et HTTP/2 avant même de
lire ses en-têtes — c'est pourquoi les en-têtes seuls n'avaient récupéré que
3 sites sur 506. Maigret embarque `CurlCffiChecker`, qui imite l'empreinte
d'un vrai navigateur, mais ne l'active que pour les 108 sites portant
`tls_fingerprint` en amont. Limier l'étend désormais à tous les sites rejoués
en seconde passe. Désactivable par `LIMIER_TLS_IMPERSONATION_RETRY=false`.
Plus lent qu'aiohttp, donc réservé aux sites ayant déjà refusé la première
requête — le coût ne porte que là où il peut rapporter.
## [1.0.8] — 2026-09-12 ## [1.0.8] — 2026-09-12
### Modifié ### Modifié
+1 -1
View File
@@ -1 +1 @@
1.0.8 1.0.9
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "limier" name = "limier"
version = "1.0.8" version = "1.0.9"
description = "Plateforme OSINT auto-hébergée : pseudonyme, nom, e-mail, domaine" description = "Plateforme OSINT auto-hébergée : pseudonyme, nom, e-mail, domaine"
requires-python = ">=3.12,<3.14" requires-python = ">=3.12,<3.14"
license = { text = "MIT" } license = { text = "MIT" }
+1 -1
View File
@@ -1,3 +1,3 @@
"""Limier — plateforme OSINT auto-hébergée.""" """Limier — plateforme OSINT auto-hébergée."""
__version__ = "1.0.8" __version__ = "1.0.9"
+9
View File
@@ -127,6 +127,15 @@ class Settings(BaseSettings):
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
) )
tls_impersonation_retry: bool = True
"""Imiter l'empreinte TLS d'un navigateur lors de la seconde tentative.
S'appuie sur ``curl_cffi``, déjà embarqué par Maigret. C'est le levier qui
agit sur les protections modernes : elles identifient un client par son
empreinte TLS bien avant de lire ses en-têtes. Plus lent qu'aiohttp, donc
réservé aux sites ayant déjà refusé la première requête.
"""
outbound_browser_headers: bool = True outbound_browser_headers: bool = True
"""Présenter un jeu d'en-têtes de navigateur sur les requêtes sortantes. """Présenter un jeu d'en-têtes de navigateur sur les requêtes sortantes.
@@ -173,8 +173,30 @@ def selectionner_sites(
) )
def dupliquer_avec_entetes(sites: dict) -> dict: def dupliquer_pour_seconde_passe(sites: dict) -> dict:
"""Copie des sites donnés, avec les en-têtes de navigateur ajoutés. """Copie des sites donnés, préparée pour une seconde tentative renforcée.
Deux renforts sont appliqués, du moins au plus efficace :
**Les en-têtes de navigateur**, pour les sites qui n'en déclarent pas. Gain
mesuré en conditions réelles : 3 sites récupérés sur 506. Faible, mais réel.
**L'imitation d'empreinte TLS**, qui est le vrai levier. Les protections
modernes identifient un client par son empreinte TLS et HTTP/2 avant même
de lire ses en-têtes : annoncer Firefox tout en présentant l'empreinte de
Python est précisément ce qui déclenche le blocage. Maigret embarque
``CurlCffiChecker``, qui imite l'empreinte d'un vrai navigateur, mais ne
l'active que pour les sites portant ``tls_fingerprint`` dans leur champ
``protection`` — 108 sites sur 3 302 en amont. On l'étend ici à tous les
sites de la seconde passe.
Ce renfort n'est pas gratuit : curl_cffi est plus lent qu'aiohttp et ouvre
ses propres connexions. Le réserver aux sites ayant déjà échoué limite ce
coût aux seuls cas où il peut rapporter quelque chose.
Note : pour ces sites, curl_cffi retire le User-Agent afin d'utiliser celui
du navigateur imité — c'est voulu, la cohérence entre empreinte et en-têtes
est justement ce qu'on cherche.
**Duplication et non modification sur place.** Les objets rendus par **Duplication et non modification sur place.** Les objets rendus par
``ranked_sites_dict`` proviennent de la base en cache, partagée par tous les ``ranked_sites_dict`` proviennent de la base en cache, partagée par tous les
@@ -186,12 +208,26 @@ def dupliquer_avec_entetes(sites: dict) -> dict:
""" """
from maigret.sites import MaigretSite from maigret.sites import MaigretSite
from limier.config import get_settings
settings = get_settings()
entetes = entetes_navigateur() entetes = entetes_navigateur()
copies = {} copies = {}
for nom, site in sites.items(): for nom, site in sites.items():
copie = MaigretSite(site.name, dict(site.json)) copie = MaigretSite(site.name, dict(site.json))
copie.headers = dict(site.headers) if site.headers else dict(entetes)
if settings.outbound_browser_headers:
copie.headers = dict(site.headers) if site.headers else dict(entetes)
if settings.tls_impersonation_retry:
protection = list(site.protection or [])
if "tls_fingerprint" not in protection:
protection.append("tls_fingerprint")
copie.protection = protection
copies[nom] = copie copies[nom] = copie
return copies return copies
@@ -347,8 +347,12 @@ class MoteurPseudonyme(Engine):
module=self.name, module=self.name,
level="info", level="info",
message=( message=(
f"Seconde tentative sur {len(a_rejouer)} site(s) en échec, " f"Seconde tentative sur {len(a_rejouer)} site(s) en échec"
"avec des en-têtes de navigateur." + (
", avec imitation d'empreinte TLS."
if self.settings.tls_impersonation_retry
else ", avec des en-têtes de navigateur."
)
), ),
) )
# Le total inclut désormais cette seconde passe, sinon la barre de # Le total inclut désormais cette seconde passe, sinon la barre de
@@ -357,7 +361,7 @@ class MoteurPseudonyme(Engine):
recuperes_avant = len(deja_vus) recuperes_avant = len(deja_vus)
async for evenement in self._chercher_un( async for evenement in self._chercher_un(
pseudo=pseudo, pseudo=pseudo,
sites=database.dupliquer_avec_entetes(a_rejouer), sites=database.dupliquer_pour_seconde_passe(a_rejouer),
compteur=compteur, compteur=compteur,
label=f"{label} — seconde tentative", label=f"{label} — seconde tentative",
deja_vus=deja_vus, deja_vus=deja_vus,
+6 -1
View File
@@ -7,7 +7,7 @@
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
REGISTRY=registry.tips-of-mine.com REGISTRY=registry.tips-of-mine.com
TAG=1.0.8 TAG=1.0.9
LIMIER_HOST=limier.tips-of-mine.com LIMIER_HOST=limier.tips-of-mine.com
LIMIER_PUBLIC_URL=https://limier.tips-of-mine.com LIMIER_PUBLIC_URL=https://limier.tips-of-mine.com
LIMIER_ENVIRONMENT=prod LIMIER_ENVIRONMENT=prod
@@ -76,4 +76,9 @@ LIMIER_DNS_RESOLVERS=1.1.1.1,9.9.9.9
# refusé la première requête. Mettre à false pour s'en tenir au comportement # refusé la première requête. Mettre à false pour s'en tenir au comportement
# natif de Maigret et comparer les taux de blocage. # natif de Maigret et comparer les taux de blocage.
LIMIER_OUTBOUND_BROWSER_HEADERS=true LIMIER_OUTBOUND_BROWSER_HEADERS=true
# Imitation d'empreinte TLS sur la seconde tentative (curl_cffi). C'est le
# levier qui agit réellement sur les protections anti-robot. Mettre à false
# pour mesurer ce qu'il apporte.
LIMIER_TLS_IMPERSONATION_RETRY=true
LIMIER_WORKER_MAX_JOBS=4 LIMIER_WORKER_MAX_JOBS=4
+4 -4
View File
@@ -56,7 +56,7 @@ x-limier-env: &limier-env
services: services:
limier-api: limier-api:
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.8} image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.9}
container_name: limier-api container_name: limier-api
restart: unless-stopped restart: unless-stopped
command: ["limier-api"] command: ["limier-api"]
@@ -99,7 +99,7 @@ services:
- "homepage.description=Plateforme OSINT" - "homepage.description=Plateforme OSINT"
limier-worker: limier-worker:
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.8} image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.9}
container_name: limier-worker container_name: limier-worker
restart: unless-stopped restart: unless-stopped
command: ["limier-worker"] command: ["limier-worker"]
@@ -131,7 +131,7 @@ services:
- "com.centurylinklabs.watchtower.enable=true" - "com.centurylinklabs.watchtower.enable=true"
limier-web: limier-web:
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-frontend:${TAG:-1.0.8} image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-frontend:${TAG:-1.0.9}
container_name: limier-web container_name: limier-web
restart: unless-stopped restart: unless-stopped
volumes: volumes:
@@ -214,7 +214,7 @@ services:
# ``--profile``, pour que l'installation par défaut reste minimale. # ``--profile``, pour que l'installation par défaut reste minimale.
limier-holehe: limier-holehe:
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-holehe:${TAG:-1.0.8} image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-holehe:${TAG:-1.0.9}
container_name: limier-holehe container_name: limier-holehe
restart: unless-stopped restart: unless-stopped
profiles: ["holehe"] profiles: ["holehe"]
+3 -3
View File
@@ -12,7 +12,7 @@ Ni base de données, ni Redis, ni Authentik. On appelle le moteur directement.
cd /opt # ou n'importe où, il suffit d'avoir essai_moteur.py sous la main cd /opt # ou n'importe où, il suffit d'avoir essai_moteur.py sous la main
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \ docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
registry.tips-of-mine.com/limier/limier-backend:1.0.8 \ registry.tips-of-mine.com/limier/limier-backend:1.0.9 \
python /tmp/essai.py domain tips-of-mine.com python /tmp/essai.py domain tips-of-mine.com
``` ```
@@ -24,7 +24,7 @@ Le moteur pseudonyme, en version courte :
```bash ```bash
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \ docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
-e LIMIER_USERNAME_TOP_SITES=80 \ -e LIMIER_USERNAME_TOP_SITES=80 \
registry.tips-of-mine.com/limier/limier-backend:1.0.8 \ registry.tips-of-mine.com/limier/limier-backend:1.0.9 \
python /tmp/essai.py username soxoj python /tmp/essai.py username soxoj
``` ```
@@ -33,7 +33,7 @@ Et la recherche par nom, qui montre les variantes déduites :
```bash ```bash
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \ docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
-e LIMIER_USERNAME_TOP_SITES=60 \ -e LIMIER_USERNAME_TOP_SITES=60 \
registry.tips-of-mine.com/limier/limier-backend:1.0.8 \ registry.tips-of-mine.com/limier/limier-backend:1.0.9 \
python /tmp/essai.py name "Hubert Cornet" python /tmp/essai.py name "Hubert Cornet"
``` ```
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "limier-frontend", "name": "limier-frontend",
"version": "1.0.8", "version": "1.0.9",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {