# Advertised Edge AI, Implemented Rule Engine: A Black-Box Measurement of the PILHOME Smart-Home Gateway

Daozhendong Yang, School of Intelligent Science and Engineering,
Yunnan Technology and Business University, Kunming, China — <hi@yxpil.com>

Paper about **PILHOME**, a Rust smart-home security gateway: an axum server with 60
HTTP routes, a `Core` coordinator, nine plugin crates (network discovery, edge
inference, behaviour profiles, rule evolution, self-audit, control, scheduling), a
Home Assistant WebSocket bridge, an MQTT client, an MCP tool endpoint, a QuickJS tool
runtime, and a TypeScript control UI. The paper measures the shipped binary against
the capabilities its README and 13 design documents advertise.

## arXiv classification

- **Primary:** `cs.CR` — Cryptography and Security
- **Cross-lists:** `cs.SE` (Software Engineering), `cs.IOT`

The same classification is stated in the header comment of `main.tex`.

## Abstract (plain-text rendering)

Smart-home gateways are security-critical infrastructure with an unusually ambitious
feature list: edge inference, self-evolving rules, behaviour profiles, full-home
takeover through Home Assistant, an agent-facing tool endpoint, and an audit trail.
PILHOME v0.1.0 advertises all of these. This paper measures how much of that surface
is enforced by the shipped code, using a harness that runs the real gateway against a
mock Home Assistant WebSocket server, a mock MQTT broker, and HTTP/UDP sinks, and that
reads back the gateway's own state over its 60 HTTP routes. Three results stand out.
First, the released tree does not build: one of the three files the router references
was never committed, so the crate fails with E0583. Supplying a minimal shim is not
enough to use the system, because the authentication middleware never reads its own
`auth_enabled` flag — with the documented default of off, 12/12 endpoints answer 401 —
and the allow-listed `/api/token` hands a 64-character token to any anonymous caller,
turning 12/12 endpoints green. Second, the "edge AI" is a 4-point nearest-neighbour
lookup over 8 hand-written feature vectors plus a detector that returns an empty
vector; there are 0 model files and no inference dependency in the lock file, yet the
gateway reports 3 models as ready. Video severity is computed from detections the
caller posts: a client can request a `critical` alert, though 0 such alerts reach the
event log and so cannot actuate anything. Third, what is implemented is solid: the
Home Assistant bridge completes a real auth/get_states/call_service exchange (4
entities, 4 mirrored devices, 5 service calls), the automation engine fires in about a
second, WOL packets are well formed, and 8 agent tools work. Meanwhile the shipped
configuration file can never take effect — three independent defects, each measured —
the configured scan profile is ignored, and 8/14 security-relevant actions are
audited.

## Abstract for the arXiv form (1,920-character limit)

The abstract printed in the paper is longer than the form accepts; paste this
version into the arXiv metadata field instead. It keeps every measured number
and drops only prose.

> Smart-home gateways are security-critical infrastructure with an ambitious
> feature list: edge inference, self-evolving rules, behaviour profiles,
> full-home takeover through Home Assistant, an agent-facing tool endpoint,
> and an audit trail. PILHOME v0.1.0 advertises all of these. This paper
> measures how much of that surface is enforced by the shipped code, using a
> harness that runs the real gateway against a mock Home Assistant WebSocket
> server, a mock MQTT broker, and HTTP/UDP sinks, and reads its state over 60
> HTTP routes. First, the released tree does not build: one of the three files
> the router references was never committed, so the crate fails with E0583.
> Supplying a minimal shim is not enough to use the system, because the
> authentication middleware never reads its own auth_enabled flag — with the
> documented default of off, 12/12 endpoints answer 401 — and the allow-listed
> /api/token hands a 64-character token to any anonymous caller, turning 12/12
> endpoints green. Second, the "edge AI" is a 4-point nearest-neighbour lookup
> over 8 hand-written feature vectors plus a detector that returns an empty
> vector; there are 0 model files and no inference dependency in the lock
> file, yet the gateway reports 3 models as ready. Video severity is computed
> from detections the caller posts: a client can request a critical alert, but
> 0 reach the event log and cannot actuate anything. Third, what is
> implemented is solid: the Home Assistant bridge completes a real
> auth/get_states/call_service exchange (4 entities, 4 mirrored devices, 5
> service calls), the automation engine fires in about a second, WOL packets
> are well formed, and 8 agent tools work. Meanwhile the shipped configuration
> can never take effect — three independent defects, each measured — the
> configured scan profile is ignored, and 8/14 security-relevant actions are
> audited.

## The shim, and why it is disclosed

`Server/src/api/mod.rs:29` declares `pub mod token;` and registers three routes for
it, but `Server/src/api/token.rs` was never committed, so `cargo build --release`
fails with `error[E0583]` at the published commit. To measure anything we wrote
`scripts/shim/api_token.rs`: three handlers over the `TokenStore` methods that already
exist in `Server/src/auth.rs` (`issue`, `revoke`, `count`). It adds no policy.

`scripts/build_server.py` copies the repository (excluding `target/` and `.git/`) to
`scripts/build/shimmed/`, drops the shim in, and builds there; `--shim-only` builds an
unmodified copy so the paper can quote the exact compiler diagnostic. The repository
itself is never modified. Both build logs and their JSON summaries are committed under
`scripts/build/`.

Every claim that depends on the shim is labelled in the paper: the `/api/token`
endpoint we exercise is our handler over the author's store and the author's
middleware allow-list, so the bypass in §3 is a property of shipped code, but its
*reachability* requires the missing file.

## What is measured

| Scenario | Fixture | What it answers |
| --- | --- | --- |
| `build` | pristine vs shimmed tree | does the release compile |
| `auth` | 12 read endpoints | anonymous status, token minting, allow-list |
| `gesture` | 4 templates, midpoints, 5 noise levels × 40 samples | what the "edge AI" computes |
| `video` | 8 audit probes incl. two malformed | who decides alert severity |
| `models` | seeded + a deliberately impossible path | can a model be "ready" without weights |
| `automation` | device → rule → event, duplicates, non-match, toggle | engine latency, dedupe, matching |
| `evolve` | seed, 12 ticks | does evolution reach the rules |
| `bayes` | 20 positive samples, unseen features | habit model saturation |
| `ha_bridge` | mock HA WebSocket server | auth, get_states, entity sync, call_service, scenes |
| `mqtt` | mock broker on port 1883 | CONNECT/SUBSCRIBE/PUBLISH actually sent |
| `wol` | UDP sink on port 9 | magic packet structure |
| `scan` | 3 open loopback listeners | configured vs default scan profile |
| `mcp` | JSON-RPC initialize / tools-list / tools-call | agent control surface |
| `tools_js` | two same-second tools, an `httpGet` tool | script execution, egress, id collision |
| `websocket` | raw WS client | broadcast latency |
| `audit_coverage` | 14 actions | which actions reach the audit log |
| `config_defect` | shipped file + two repairs | can `config.toml` configure the gateway |
| `config_nesting` | TOML parse of the repaired file | where the keys actually land |
| `persistence` | snapshot files over a 70 s window | snapshot interval and content |
| `restart` | second process, same data directory | what survives a restart |
| `port_conflict` | port pre-bound by the harness | behaviour on listen collision |
| `footprint` | 40 + 40 timed requests | RSS, handles, request latency |

## File manifest

| Path | Contents |
| --- | --- |
| `main.tex` | Paper source: 11 pages, two columns, five figures, fourteen tables |
| `refs.bib` | Bibliography database (all entries verified against the cited URL) |
| `common.tex` | Shared preamble, copied from `papers/_shared/common.tex` |
| `numbers.tex` | Generated `\newcommand` definitions for every inline number |
| `tables/*.tex` | Generated table environments (setup, surface, access, ai, videotable, automation, learning, integration, toolids, configab, nesting, scan, persistence, auditcov) |
| `figures/fig1_architecture.pdf` | Measured system and its mock peers |
| `figures/fig2_access.pdf` | Anonymous vs token-issued answers on 12 endpoints |
| `figures/fig3_ai.pdf` | Gesture decision surface, midpoint scores, video severities |
| `figures/fig4_config.pdf` | Config-defect A/B and the scan probe budget |
| `figures/fig5_persistence.pdf` | What survives a restart; audit-log coverage |
| `scripts/home_lab.py` | Config generation, mock HA server, mock MQTT broker, HTTP/UDP sinks, WebSocket client, process control |
| `scripts/measure.py` | Scenario driver, writes the JSON artifact |
| `scripts/audit_repo.py` | Static audit (crates, routes, tests, stub markers, config tree) |
| `scripts/build_server.py` | Copies the tree, applies the shim, builds; `--shim-only` for the pristine diagnostic |
| `scripts/shim/api_token.rs` | The three missing handlers, disclosed in §2.1 |
| `scripts/make_tables.py` | Generates `numbers.tex` and `tables/*.tex` |
| `scripts/make_figures.py` | Generates `figures/*.pdf` |
| `scripts/results/pilhome_measure.json` | Every measured number in the paper |
| `scripts/results/run/` | Gateway configs, logs, and the exported data directory |
| `scripts/build/build_{pristine,shimmed}.{log,json}` | Build evidence for both trees |
| `scripts/data/audit.json` | Static audit artifact |
| `README.md` | This file |

## Build

Requires a TeX distribution with `pdflatex`, `bibtex`, `natbib` and `abbrvnat.bst`
(TeX Live or MiKTeX), or Tectonic. The paper uses `xspace`, `xurl`, `enumitem` and
`array`.

```bash
tectonic main.tex
# or
pdflatex main && bibtex main && pdflatex main && pdflatex main
```

## arXiv submission

Upload the contents of `dist/pilhome.tar.gz` (built with
`bash ../make_package.sh pilhome`) at <https://arxiv.org/submit>. The archive contains
`main.tex`, `refs.bib`, `common.tex`, `numbers.tex`, `tables/` and `figures/`; the
generated `main.bbl` may be included as well. Choose `cs.CR` as the primary category
and `cs.SE` plus `cs.IOT` as cross-lists.

## Reproduce the measurements

Requirements: Windows (Win32 process and memory APIs, and the UDP/9 bind), Python 3.13
with PyYAML and matplotlib, a Rust toolchain, and the PILHOME repository.

```bash
# 1. build both trees (pristine fails; that is the result, not a mistake)
python scripts/build_server.py --repo /path/to/PILHOME --shim-only
python scripts/build_server.py --repo /path/to/PILHOME

# 2. static audit + dynamic measurement
python scripts/audit_repo.py --repo /path/to/PILHOME --out scripts/data/audit.json
python scripts/measure.py --repo /path/to/PILHOME

# 3. regenerate every number, table and figure
python scripts/make_tables.py
python scripts/make_figures.py
```

`measure.py` refuses to start if another `pilhome-server` is running or if ports 1883
and 8080 are busy: the gateway dials 1883 regardless of configuration, and falls back
to `127.0.0.1:8080` whenever its configuration file fails to parse, so both ports must
be free for the A/B experiments to mean anything. A full run takes about four minutes.

## Key results

- Build: `cargo build --release -p pilhome-server` exits 101 with
  `error[E0583]: file not found for module 'token'` at `Server/src/api/mod.rs:29`.
- Access: 12/12 read endpoints return 401 to an anonymous client under the documented
  `auth_enabled = false`; one anonymous `POST /api/token` returns a 64-hex token that
  makes 12/12 return 200.
- Gesture: 4 templates × 8 floats, Euclidean nearest neighbour, 0.70 µs internal;
  fist/palm midpoint classifies as `peace`; 160/160 correct at σ ≤ 0.30.
- Video: `NoopDetector` returns `Vec::new()`; caller-posted detections set the level
  (person @0.90 + motion → `critical`; @0.50 → filtered by `min_confidence 0.55`;
  `toaster` → ignored by the watch list); 0 of 8 probes added an event-log entry;
  a detection without a 4-tuple `bbox` is rejected with HTTP 422.
- Models: 2 seeded models report `ready`, 0 of their paths exist on disk, 0 ONNX
  files in the tree, and a posted impossible path is accepted as `ready`.
- Automation: rule action observed 1,677 ms after the event (1 s poll); 3 duplicate
  events added 6 log entries (no dedupe or loop guard); 5 webhook POSTs delivered.
- Evolution: 12 ticks advance the generation and drift a window from [7,8] to [2,3],
  fitness 0.50 → 0.14, with the rule count unchanged; `Evolver::reward` and
  `RuleGene::covers` have no call sites; `Core`'s bus has 4 publishers and 0 readers.
- Habits: 20 positive samples move probability 0.50 → 1.00, and an untrained feature
  pair also scores 1.00.
- Integrations: mock HA completes auth → get_states → 4 entities → 4 mirrored devices
  → 5 `call_service` frames, `arm` scene actuates 3 entities; MQTT CONNECT + SUBSCRIBE
  observed; WOL packet has the broadcast prefix and 16 MAC repeats; MCP lists 8 tools
  and adds a rule; the JS tool runtime exposes 12 bindings and reached the harness
  sink over `httpGet`; two tools created in the same second share an id and the second
  one silently executes the first.
- Configuration: the shipped `config.toml` fails on an unterminated string, then on
  two missing `[probe]` fields; even when repaired, `webhook_url` and
  `snapshot_interval_secs` parse as keys of `[probe]` and the `[ai]` table has no
  counterpart, so the gateway listens on the compiled-in `127.0.0.1:8080`.
- Scan: two runs took 19.96 s and 19.99 s with 3 loopback listeners open and found 0
  alive hosts — the duration matches the 2,032-probe default profile, not the
  762-probe configured one, because `cfg.scan` is never passed to `AppState`.
- State: 4,096 events and 876 audit records survive a restart; devices, automation
  rules and user-created tools do not; the configured 2 s snapshot interval is floored
  to 30 s; a busy listen port panics at `main.rs:101` after restoring state.
- Audit coverage: 8/14 probed actions add audit entries; device events, scans,
  discovery, HA sync, evolution ticks and Bayes training do not.

## Documentation vs. measured behavior

The paper treats the README and `docs/` as the specification and reports where the
execution path diverges: authentication documented as opt-out but always on; "edge AI
inference" implemented as a 4-point lookup and a stub detector; "self-evolving" rules
that no component consumes; "append-only" audit implemented as an evicting ring that
covers 8 of 14 actions; a configuration file that cannot load. Where the source
documents the gap itself — 8 explicit placeholder/演示/占位 markers, and the
`StaticDetector` comment "模拟检测结果(未接真实 YOLO 时可注入演示)" — the paper cites the
marker rather than inferring intent.
