Aller au contenu
Tous les challenges
OSINT Résolu Facile · 1 800 m

AlpAnalytics 1: Off the Record

Données ouvertes et registre du commerce : retrouver le voyage secret de la CEO.

Auteur
Aubry Mangold (Aubrax)
Scripts
2 fichiers

Énoncé (résumé)

Le 2 février 2026, Gaëlle, CEO d’AlpAnalytics, déclare à la presse que l’entreprise ne sera jamais vendue. On dit pourtant qu’elle a rencontré des acheteurs en Suisse centrale quelques semaines plus tôt. On nous fournit les données ouvertes de l’entreprise (version v1, téléchargée en février et retirée en mars), un extrait du registre du commerce et le site web http://alpanalytics.ctf.blackalps.ch. Il faut trouver le premier voyage de Gaëlle en Suisse centrale et donner son ticket ID.

Fichiers

  • register_extract.txt : AlpAnalytics SA, Avenue des Remparts 31, Yverdon-les-Bains. « Gaëlle P., of Lucens, in Grandson: founder, chair and CEO » ; Lorris G. habite aussi à Grandson.
  • places.csv : gares, parkings et remontées mécaniques, avec leur code postal (NPA).
  • v1/people.parquet : 14 347 utilisateurs (pid, birth_year, home_npa, ev).
  • v1/events.parquet : 2 168 018 événements (pid, ts, place_id, mode, ticket_id, tap, charge_min), du 01.11.2025 au 06.02.2026.

Démarche OSINT

  1. Registre : Gaëlle habite à Grandson, code postal 1422. Dans people.parquet, 152 utilisateurs ont ce code postal.

  2. Données seules, insuffisant : une vingtaine d’habitants de 1422 vont en Suisse centrale (cantons LU, ZG, SZ, OW, NW, UR : Zoug, Lucerne, Rigi, Pilatus, Titlis…). Une dizaine font des allers-retours en semaine vers Zoug. Ce sont des leurres : il faut d’autres critères.

  3. Site web, critères discriminants :

    • team.html : « Gaëlle P. founded AlpAnalytics in May 2019, at 34 ». Elle est donc née en 1984 ou 1985.
    • blog/2024-05-21-going-electric.html : elle a acheté une voiture électrique (ev = true). « I drive to the office every working day and plug in each morning at the Yverdon station car park ». Ce sont des événements ev_charge quasi quotidiens à P-YVE-GARE.
    • blog/2025-04-08-charger-wars.html : depuis novembre, les bornes sont parfois toutes prises, d’où quelques jours en simple parking. Lorris G. recharge lui aussi à ces bornes.
    • press/2026-04-30-acquisition.html : l’acheteur, BlackData Switzerland GmbH, est installé à Zoug.
    • blog/2026-03-09-withdrawing-v1.html et 2026-03-16-v2-released.html : la v1 a été retirée pour une faille de vie privée. Elle contenait l’année de naissance exacte et le code postal complet, une combinaison quasi identifiante. La v2 est k-anonyme : tranches d’âge, deux premiers chiffres du code postal, zones, heure tronquée. Elle ne couvre que la période du 16.01 au 28.02. C’est le piège « off the record » : seule la v1 permet de réidentifier Gaëlle et de voir ses voyages de décembre.
  4. Filtre : code postal 1422, naissance en 1984 ou 1985, voiture électrique. Il reste 4 pid :

    pid naiss. recharges à P-YVE-GARE stationnements sans recharge à P-YVE-GARE remarque
    471c06bd 1984 51 10 va au bureau en voiture et recharge presque chaque jour → Gaëlle
    a0a3b4db 1985 9 49 se gare surtout sans recharger
    189d2230 1984 13 0 pas de trajet quotidien (44 événements)
    f2f223eb 1984 0 0 travaille à Berne

    À noter : 728d18ae recharge aussi chaque jour, mais il est né en 1979 (probablement Lorris).

  5. Voyages de 471c06bd : trois allers-retours en train Yverdon → Zoug avant le 2 février :

    • 04.12.2025 : ticket QD8SM8KA (in YVE 07:35, out ZUG 09:49, in ZUG 18:51, out YVE 21:05)
    • 18.12.2025 : YKQBQATM
    • 22.01.2026 : HMQDQ4VU

    Le premier voyage en Suisse centrale porte le ticket QD8SM8KA.

Reproduction

cd .
uv venv .venv && uv pip install --python .venv/bin/python pandas pyarrow
unzip alpanalytics-1.zip -d files
.venv/bin/python -I gaelle.py files/v1        # filtre 1422 / 1984-85 / VE + habitudes de recharge
.venv/bin/python -I candidates.py files/v1    # voyages en Suisse centrale des habitants de 1422

Requête minimale (pandas) :

e = pd.read_parquet('files/v1/events.parquet'); p = pd.read_parquet('files/v1/people.parquet')
c = p[(p.home_npa == 1422) & p.birth_year.isin([1984, 1985]) & p.ev]
# -> garder le pid avec ev_charge quotidien à P-YVE-GARE : 471c06bd
e[(e.pid == '471c06bd') & (e['mode'] == 'train')]

Flag

QD8SM8KA

Code

import pandas as pd, sys
e=pd.read_parquet(sys.argv[1]+'/events.parquet'); p=pd.read_parquet(sys.argv[1]+'/people.parquet')
c=p[(p.home_npa==1422)&p.birth_year.isin([1984,1985])&p.ev]
print(c.to_string())
bd=pd.bdate_range('2025-11-03','2026-02-06')
for pid in c.pid:
    d=e[e.pid==pid]; days=set(d.ts.dt.normalize())
    ch=d[(d.place_id=='P-YVE-GARE')]
    print(pid,len(d),'charge days',(ch['mode']=='ev_charge').sum(),'park',(ch['mode']=='parking').sum(),'missing weekdays',sum(x not in days for x in bd), d.place_id.value_counts().to_dict())