Tamper detection and behavioral anomaly profiling that protect the sensing system from manipulation. These modules detect replay attacks, signal injection, jamming, and unusual behavior patterns -- all running on-device with no cloud dependency.
| Module | File | What It Does | Event IDs | Budget |
|---|---|---|---|---|
| Signal Shield | ais_prompt_shield.rs |
Detects replay, injection, and jamming attacks on CSI data | 820-823 | S (<5 ms) |
| Behavioral Profiler | ais_behavioral_profiler.rs |
Learns normal behavior and detects anomalous deviations | 825-828 | S (<5 ms) |
What it does: Detects three types of attack on the WiFi sensing system:
- Replay attacks: An adversary records legitimate CSI frames and plays them back to fool the sensor into seeing a "normal" scene while actually present in the room.
- Signal injection: An adversary transmits a strong WiFi signal to overpower the legitimate CSI, creating amplitude spikes across many subcarriers.
- Jamming: An adversary floods the WiFi channel with noise, degrading the signal-to-noise ratio below usable levels.
How it works:
- Replay detection: Each frame's features (mean phase, mean amplitude, amplitude variance) are quantized and hashed using FNV-1a. The hash is stored in a 64-entry ring buffer. If a new frame's hash matches any recent hash, it flags a replay.
- Injection detection: If more than 25% of subcarriers show a >10x amplitude jump from the previous frame, it flags injection.
- Jamming detection: The module calibrates a baseline SNR (signal / sqrt(variance)) over the first 100 frames. If the current SNR drops below 10% of baseline for 5+ consecutive frames, it flags jamming.
use wifi_densepose_wasm_edge::ais_prompt_shield::PromptShield;
let mut shield = PromptShield::new(); // const fn, zero-alloc
let events = shield.process_frame(&phases, &litudes); // per-frame analysis
let calibrated = shield.is_calibrated(); // true after 100 frames
let frames = shield.frame_count(); // total frames processed| Event ID | Constant | Value | Frequency |
|---|---|---|---|
| 820 | EVENT_REPLAY_ATTACK |
1.0 (detected) | On detection (cooldown: 40 frames) |
| 821 | EVENT_INJECTION_DETECTED |
Fraction of subcarriers with spikes [0.25, 1.0] | On detection (cooldown: 40 frames) |
| 822 | EVENT_JAMMING_DETECTED |
SNR drop in dB (10 * log10(baseline/current)) | On detection (cooldown: 40 frames) |
| 823 | EVENT_SIGNAL_INTEGRITY |
Composite integrity score [0.0, 1.0] | Every 20 frames |
| Constant | Value | Purpose |
|---|---|---|
MAX_SC |
32 | Maximum subcarriers processed |
HASH_RING |
64 | Size of replay detection hash ring buffer |
INJECTION_FACTOR |
10.0 | Amplitude jump threshold (10x previous) |
INJECTION_FRAC |
0.25 | Minimum fraction of subcarriers with spikes |
JAMMING_SNR_FRAC |
0.10 | SNR must drop below 10% of baseline |
JAMMING_CONSEC |
5 | Consecutive low-SNR frames required |
BASELINE_FRAMES |
100 | Calibration period length |
COOLDOWN |
40 | Frames between repeated alerts (2 seconds at 20 Hz) |
The composite score (event 823) is emitted every 20 frames and ranges from 0.0 (compromised) to 1.0 (clean):
| Factor | Score Reduction | Condition |
|---|---|---|
| Replay detected | -0.4 | Frame hash matches ring buffer |
| Injection detected | up to -0.3 | Proportional to injection fraction |
| SNR degradation | up to -0.3 | Proportional to SNR drop below baseline |
The hash function quantizes three frame statistics to integer precision before hashing:
hash = FNV_OFFSET (2166136261)
for each of [mean_phase*100, mean_amp*100, amp_variance*100]:
for each byte in value.to_le_bytes():
hash ^= byte
hash = hash.wrapping_mul(FNV_PRIME) // FNV_PRIME = 16777619
This means two frames must have nearly identical statistical profiles (within 1% quantization) to trigger a replay alert.
Calibration (frames 1-100):
Normal CSI with varying phases -> baseline SNR established
No alerts emitted during calibration
Frame 150: Normal operation
phases = [0.31, 0.28, ...], amps = [1.02, 0.98, ...]
hash = 0xA7F3B21C -> stored in ring buffer
No alerts
Frame 200: Attacker replays frame 150 exactly
phases = [0.31, 0.28, ...], amps = [1.02, 0.98, ...]
hash = 0xA7F3B21C -> MATCH found in ring buffer!
-> EVENT_REPLAY_ATTACK = 1.0
-> EVENT_SIGNAL_INTEGRITY = 0.6 (reduced by 0.4)
Frame 300: Normal amplitudes
amps = [1.0, 1.1, 0.9, 1.0, ...]
Frame 301: Adversary injects strong signal
amps = [15.0, 12.0, 14.0, 13.0, ...] (>10x jump on all subcarriers)
injection_fraction = 1.0 (100% of subcarriers spiked)
-> EVENT_INJECTION_DETECTED = 1.0
-> EVENT_SIGNAL_INTEGRITY = 0.4
What it does: Learns what "normal" behavior looks like over time, then detects anomalous deviations. It builds a 6-dimensional behavioral profile using online statistics (Welford's algorithm) and flags when new observations deviate significantly from the learned baseline.
How it works: Every 200 frames, the module computes a 6D feature vector from the observation window. During the learning phase (first 1000 frames), it trains Welford accumulators for each dimension. After maturity, it computes per-dimension Z-scores and a combined RMS Z-score. If the combined score exceeds 3.0, an anomaly is reported.
| # | Dimension | Description | Typical Range |
|---|---|---|---|
| 0 | Presence Rate | Fraction of frames with presence | [0, 1] |
| 1 | Average Motion | Mean motion energy in window | [0, ~5] |
| 2 | Average Persons | Mean person count | [0, ~4] |
| 3 | Activity Variance | Variance of motion energy | [0, ~10] |
| 4 | Transition Rate | Presence state changes per frame | [0, 0.5] |
| 5 | Dwell Time | Average consecutive presence run length | [0, 200] |
use wifi_densepose_wasm_edge::ais_behavioral_profiler::BehavioralProfiler;
let mut bp = BehavioralProfiler::new(); // const fn
let events = bp.process_frame(present, motion, n_persons); // per-frame
let mature = bp.is_mature(); // true after learning
let anomalies = bp.total_anomalies(); // cumulative count
let mean = bp.dim_mean(0); // mean of dimension 0
let var = bp.dim_variance(1); // variance of dim 1| Event ID | Constant | Value | Frequency |
|---|---|---|---|
| 825 | EVENT_BEHAVIOR_ANOMALY |
Combined Z-score (RMS, > 3.0) | On detection (cooldown: 100 frames) |
| 826 | EVENT_PROFILE_DEVIATION |
Index of most deviant dimension (0-5) | Paired with anomaly |
| 827 | EVENT_NOVEL_PATTERN |
Count of dimensions with Z > 2.0 | When 3+ dimensions deviate |
| 828 | EVENT_PROFILE_MATURITY |
Days since sensor start | On maturity + periodically |
| Constant | Value | Purpose |
|---|---|---|
N_DIM |
6 | Behavioral dimensions |
LEARNING_FRAMES |
1000 | Frames before profiler matures |
ANOMALY_Z |
3.0 | Combined Z-score threshold for anomaly |
NOVEL_Z |
2.0 | Per-dimension Z-score threshold for novelty |
NOVEL_MIN |
3 | Minimum deviating dimensions for NOVEL_PATTERN |
OBS_WIN |
200 | Observation window size (frames) |
COOLDOWN |
100 | Frames between repeated anomaly alerts |
MATURITY_INTERVAL |
72000 | Frames between maturity reports (1 hour at 20 Hz) |
Each dimension maintains running statistics without storing all past values:
On each new observation x:
count += 1
delta = x - mean
mean += delta / count
m2 += delta * (x - mean)
Variance = m2 / count
Z-score = |x - mean| / sqrt(variance)
This is numerically stable and requires only 12 bytes per dimension (count + mean + m2).
Learning phase (day 1-2):
Normal pattern: 1 person, present 8am-10pm, moderate motion
Profile matures -> EVENT_PROFILE_MATURITY = 0.58 (days)
Day 3, 3am:
Observation window: presence=1, high motion, 1 person
Z-scores: presence_rate=2.8, motion=4.1, persons=0.3,
variance=3.5, transition=2.2, dwell=1.9
Combined Z = sqrt(mean(z^2)) = 3.4 > 3.0
-> EVENT_BEHAVIOR_ANOMALY = 3.4
-> EVENT_PROFILE_DEVIATION = 1 (motion dimension most deviant)
-> EVENT_NOVEL_PATTERN = 3 (3 dimensions above Z=2.0)
| Attack | Detection Module | Method | False Positive Rate |
|---|---|---|---|
| CSI frame replay | Signal Shield | FNV-1a hash ring matching | Low (1% quantization) |
| Signal injection (e.g., rogue AP) | Signal Shield | >25% subcarriers with >10x amplitude spike | Very low |
| Broadband jamming | Signal Shield | SNR drop below 10% of baseline for 5+ frames | Very low |
| Narrowband jamming | Partially -- Signal Shield | May not trigger if < 25% subcarriers affected | Medium |
| Behavioral anomaly (intruder at unusual time) | Behavioral Profiler | Combined Z-score > 3.0 across 6 dimensions | Low after maturation |
| Gradual environmental change | Behavioral Profiler | Welford stats adapt, may flag if change is abrupt | Very low |
| Attack | Why Not | Recommended Mitigation |
|---|---|---|
| Sophisticated replay with slight phase variation | FNV-1a uses 1% quantization; small perturbations change the hash | Add temporal correlation checks (consecutive frame deltas) |
| Man-in-the-middle on the WiFi channel | Modules analyze CSI content, not channel authentication | Use WPA3 encryption + MAC filtering |
| Physical obstruction (blocking line-of-sight) | Looks like a person leaving, not an attack | Cross-reference with PIR sensors |
| Slow amplitude drift (gradual injection) | Below the 10x threshold per frame | Add longer-term amplitude trend monitoring |
| Firmware tampering | Modules run in WASM sandbox, cannot detect host compromise | Secure boot + signed firmware (ADR-032) |
- Always run both modules together: Signal Shield catches active attacks, Behavioral Profiler catches passive anomalies.
- Allow full calibration: Signal Shield needs 100 frames (5 seconds) for SNR baseline. Behavioral Profiler needs 1000 frames (~50 seconds) for reliable Z-scores.
- Combine with Temporal Logic Guard (
tmp_temporal_logic_guard.rs): Its safety invariants catch impossible state combinations (e.g., "fall alert when room is empty") that indicate sensor manipulation. - Connect to the Self-Healing Mesh (
aut_self_healing_mesh.rs): If a node in the mesh is being jammed, the mesh can automatically reconfigure around the compromised node.
| Module | State Size (approx) | Static Event Buffer |
|---|---|---|
| Signal Shield | ~420 bytes (64 hashes + 32 prev_amps + calibration) | 4 entries |
| Behavioral Profiler | ~2.4 KB (200-entry observation window + 6 Welford stats) | 4 entries |
Both modules use fixed-size arrays and static event buffers. No heap allocation. Fully no_std compliant.