ipc: close unauthenticated event-spoofing gap in emit's no-source path

The IPC "emit" method's no-source path took a bare event+data and sent it
straight to emit_tx tagged AdapterSource::System with zero validation of
the event name. Any same-UID process on the socket could send e.g.
{"event":"bread.power.ac.connected",...} and have it delivered to every
Lua subscriber indistinguishable from a real adapter event, since System
is the same tag the daemon uses for its own trusted, Rust-originated
sends (bread.system.startup, bread.profile.activated).

Fix, scoped to the actual threat model (same-UID Unix socket trust means
there's no way to cryptographically distinguish "the real bread-cli
binary" from any other local process, so a generic connection-identity
handshake would be theater):

- New AdapterSource::Manual tag for the no-source emit path. System is
  now reserved for daemon-internal, Rust-code-originated sends only and
  can never again be produced from data that arrived over the wire.
- The event name is rejected if its top-level dotted segment is one of
  the reserved, adapter-owned domains (RESERVED_DOMAINS in
  bread-shared/src/apps.rs) -- extended with bluetooth/workspace/window/
  monitor, event families the Hyprland and Bluetooth adapters already
  publish under but that were missing from that list. Freely-named
  custom/test event names are untouched, so `bread emit <name>` and
  bread-emit's fire-and-forget single-line-write design keep working
  exactly as documented.

Bumped API_VERSION to 1.5.0 and updated Documentation.md's IPC emit
section and Namespaces reserved-domains list accordingly.

Also fixed a subscribe/emit race that surfaced while adding regression
tests for this: events.subscribe's ack is written to the client before
the server task actually registers on the broadcast channel, so a test
that emits immediately after reading the ack can race the registration.
Added a settle delay plus an explicit timeout (instead of an unbounded
read loop) so a future regression fails the test instead of hanging the
whole binary.
This commit is contained in:
Breadway 2026-08-04 17:41:18 +08:00
parent 96639516b1
commit 7bb6fbb20f
6 changed files with 281 additions and 11 deletions

View file

@ -32,9 +32,25 @@ pub enum AdapterSource {
Power,
/// Network state (rtnetlink / NetworkManager).
Network,
/// Internal events synthesized by the daemon itself
/// (e.g. `bread.profile.activated`, `bread.state.changed.*`).
/// Internal events synthesized by the daemon itself, i.e. trusted,
/// Rust-code-originated sends via `emit_tx` (e.g. `bread.system.startup`,
/// `bread.profile.activated`, `bread.state.changed.*`, and Lua's
/// `bread.emit()` binding). Never assignable from data that arrived
/// over the IPC socket — see [`Manual`](AdapterSource::Manual) for that
/// case. *Since: v1.5 — this constraint is now enforced; previously the
/// IPC `emit` method's no-`source` path could also tag events `System`.*
System,
/// A manual `emit` IPC request with no `source` param — a human or
/// script poked the daemon's Unix socket directly (e.g. `bread emit
/// <event>` for testing Lua handlers without unplugging cables).
/// Distinct from [`System`](AdapterSource::System) so downstream Lua
/// modules and tooling can tell "someone manually injected this event"
/// apart from "a real adapter observed this" or "the daemon itself
/// produced this." The IPC boundary restricts which event names may be
/// tagged this way — it may not claim an adapter-owned namespace (see
/// `apps::is_reserved_domain`), but is otherwise free for custom/test
/// event names. *Since: v1.5*
Manual,
/// BlueZ Bluetooth stack via D-Bus.
Bluetooth,
/// Shell precmd/preexec hooks (terminal command lifecycle, cwd changes).
@ -222,6 +238,10 @@ mod tests {
serde_json::to_string(&AdapterSource::System).unwrap(),
"\"system\""
);
assert_eq!(
serde_json::to_string(&AdapterSource::Manual).unwrap(),
"\"manual\""
);
assert_eq!(
serde_json::to_string(&AdapterSource::Bluetooth).unwrap(),
"\"bluetooth\""
@ -268,6 +288,7 @@ mod tests {
AdapterSource::Power,
AdapterSource::Network,
AdapterSource::System,
AdapterSource::Manual,
AdapterSource::Bluetooth,
AdapterSource::Terminal,
AdapterSource::Git,