cd /news/machine-learning/wallflower-reproducing-a-wifi-identi… · home topics machine-learning article
[ARTICLE · art-60615] src=github.com ↗ pub= topic=machine-learning verified=true sentiment=↓ negative

Wallflower – reproducing a WiFi identity-inference attack

Researchers at a WiFi-sensing lab reproduced the BFId identity-inference attack, which uses beamforming feedback information (BFI) broadcast in the clear by 802.11ac/ax/be devices to identify individuals by their gait with 99.5% accuracy. The reproduction, called wallflower, captures BFI and CSI traces from multiple perspectives and trains an LSTM classifier, aiming to verify the attack's generalizability beyond a single room. The work highlights a privacy vulnerability in WiFi beamforming protocols that leak person-specific channel perturbations even on encrypted networks.

read6 min views1 publishedJul 15, 2026
Wallflower – reproducing a WiFi identity-inference attack
Image: source

A WiFi-sensing lab that reproduces the BFId paper — identity inference from the beamforming feedback 802.11 devices broadcast in the clear:

Todt, Morsbach, Strufe.

"BFId: Identity Inference Attacks Utilizing Beamforming Feedback Information."CCS '25.

wallflower

  • records synchronised CSI + BFI traces from a 4-perspective setup, - segments and parses them into ML-ready variable-length time series, and
  • trains/evaluates a baseline LSTM identity classifier.

The headline paper result it targets is BFI identity accuracy 99.5% ± 0.38 from normal walking; the next milestone is reproducing it on our own captures and showing it generalises beyond one room.

802.11ac/ax/be beamforming requires a receiver to periodically send the transmitter Beamforming Feedback Information (BFI) — compressed (quantised-angle) channel-state matrices — in the clear, even on encrypted networks. BFId shows that a passive observer who records this BFI while a person walks can infer who the person is, because gait perturbs the channel in a person-specific way. wallflower reproduces this by capturing BFI alongside ground-truth CSI (channel state information) from multiple perspectives and training an identity classifier.

Feature dimensions (from the paper, defined once in wallflower/contract.py

):

Modality Features Composition Nominal rate
BFI 740 (+1 dt) 10 quantised angles × 74 channels ~10 Hz
CSI 212 (+1 dt) (phase + magnitude) × 53 subcarriers × 2 antennas ~285 Hz

The +1

is an appended time-delta column the models consume; parsers store it separately as dt

in each .npz

.

Operative setup (current pilot).A single lab-ownedASUS RT-AXE7800AP on5 GHz / channel 36 / 80 MHz(SSIDLAB_AP

, BSSIDAA:BB:CC:DD:EE:F4

) serves both the CSI and BFI roles — this is whatconfigs/ap_channels.yaml

andwallflower/contract.py

actually encode. On node1 the hostwlp1s0

AX210 doesBFI capture + traffic + the live RSSI dashboard;CSI is captured bare-metal by FeitCSIon the second AX210. Everything runs on bare metal — no VMs. Real BFI is captured and decoded. The 6 GHz / dual-AP plan described below is the paper'snominalfull-deployment target, not the current rig.

Paper-nominal full 4-perspective deployment (logical inventory in configs/nodes.yaml

under topology.full

):

2× access points, both 6 GHz / 160 MHz:** AP-CSIon channel 37**— carries the CSI-driving traffic.** AP-BFIon channel 85**— its beamforming sounding is what the passive recorder collects.

4 perspective nodes, each with** 2× Intel AX210/AX1675 2×2radios: radio A = CSIcapture (monitor mode; FeitCSI). radio B = BFI**client (associates to AP-BFI to elicit sounding).

1 CSI-traffic node— generatesiperf3

traffic (200 Mb/s TCP drives BFI sounding; 30 Kb/s UDP keeps CSI flowing).1 passive BFI recorder— a single monitor-mode capture (tcpdump

) of all BFI sounding into onebfi_recorder.pcapng

.1 controller— orchestrates sessions over SSH and (optionally) trains.

              AP-CSI (ch37)            AP-BFI (ch85)        6 GHz / 160 MHz
                  |                         |
   csi-traffic ---+                         +--- bfi-recorder (passive, 1 pcapng)
                  |                         |
        +---------+----------+   +----------+----------+   ... x4 perspectives
        | perspective node N |   | radioA=CSI monitor  |
        | 2x AX210           |   | radioB=BFI client   |
        +--------------------+   +---------------------+
                         \             /
                          \           /
                        controller (SSH orchestrator + trainer)

The controller talks to each node over SSH (or locally in pilot) via:

python3 -m nodes.<agent> <action> --participant P001 --style normal --trial 001 \
    [--perspective N] [--out-dir DIR]

Every agent prints one structured JSON object to stdout ({agent, action, ok, node, ts_utc, ...}

). start

writes a pidfile so stop

can terminate the capture. Output filenames come from wallflower.contract

(csi_raw_name(p)

, bfi_recorder_name()

).

data/raw/participant=P001/style=normal/trial=001/
    metadata.json
    csi_p1.raw  csi_p2.raw  csi_p3.raw  csi_p4.raw
    bfi_recorder.pcapng
    logs/
wallflower/
├── wallflower/            # shared CONTRACT (constants, paths, dataclasses) — import this
│   └── contract.py  #   single source of truth: feature dims, channels, layout
├── orchestrator/    # controller CLI (`wallflower`) — session/trial orchestration over SSH
├── nodes/           # per-node agents (csi, bfi client, bfi recorder, traffic)
├── capture/         # low-level capture helpers (tcpdump/iw wrappers, stdlib-only)
├── parsers/         # CSI .raw + BFI .pcapng -> variable-length .npz / parquet
├── models/          # baseline LSTM identity classifier + training/eval
├── configs/         # lab.yaml, nodes.yaml, ap_channels.yaml
├── scripts/         # operator convenience scripts
├── experiments/     # experiment configs / run outputs
└── data/            # raw / processed / models / reports  (git-ignored; .gitkeep)

Config files (kept consistent with wallflower/contract.py

):

configs/lab.yaml

— data root, 80/20 split, perspectives, styles + repeats, band/width/channels, clock-sync tolerance, sample-rate targets, traffic.configs/nodes.yaml

— full 4-perspective inventoryand thepilot

profile mapping every role onto node1.configs/ap_channels.yaml

— operative RF plan: single ASUS AP, 5 GHz / 80 MHz, both roles on ch36 (SSIDLAB_AP

, BSSIDAA:BB:CC:DD:EE:F4

), with the paper-nominal 6 GHz / dual-channel plan noted as historical context.

pip install -e .

pip install -e ".[ml]"

pip install -e ".[capture]"

pip install -e ".[dev]"

Requires Python ≥ 3.12.

  • Ubuntu 26.04 LTS, kernel 7.0.0, Python 3.14; iw

6.17,tcpdump

,ssh

present. 2× Intel AX210/AX1675 2×2 radios (iwlwifi loaded, firmware present):- PCI 01:00.0

wlp1s0

,phy1

, MACAA:BB:CC:DD:EE:02

BFI client / traffic role. - PCI 02:00.0

wlp2s0

,phy2

, MACAA:BB:CC:DD:EE:03

BFI recorder role.

  • PCI
  • Wired control plane: eno1

. Assumed MISSING(degrade gracefully / document install):tshark

,iperf3

,chrony

/ntp

,ptp4l

, PicoScenes,git

,gcc

/make

/cmake

/dkms

.

sudo

on node1 requires a password — non-interactive root is not available. Anything needing root (monitor mode, channel set via iw

, package install, raw socket capture) is printed for the operator to run, prefixed clearly, e.g.:

[OPERATOR-RUN] sudo iw dev wlp1s0 set type monitor
[OPERATOR-RUN] sudo iw dev wlp1s0 set channel 36 80MHz   # 5 GHz

Read-only inspection (lspci

, iw dev

, ip link

, reading /sys

) works without root.

The pilot collapses every role onto node1 (see configs/nodes.yaml

profile pilot

). It validates the end-to-end pipeline on a single machine before scaling to 4 perspectives.

python3 -m nodes.csi_agent detect --perspective 1

wallflower init-session --participant P001 --profile pilot

wallflower start-trial --participant P001 --style normal --trial 001


wallflower stop-trial --participant P001 --style normal --trial 001

wallflower validate-session --participant P001

Any privileged step that cannot run will print an [OPERATOR-RUN]

command for you to execute manually, then continue without crashing.

Installed as the wallflower

console script (orchestrator.cli:main

):

Command Purpose
init-session
Create/validate a session and select a profile (pilot or full ).
start-trial
Run the clock-sync gate, spawn node agents (CSI, BFI client, BFI recorder, traffic), write the trial metadata.json .
stop-trial
Stop captures via their pidfiles and finalise metadata.json .
validate-session
Check raw layout, file presence, sample-rate / clock-sync tolerances (per configs/lab.yaml ) and report problems.

Apache 2.0 — see LICENSE.

── more in #machine-learning 4 stories · sorted by recency
── more on @bfid 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/wallflower-reproduci…] indexed:0 read:6min 2026-07-15 ·