Skip to content

interface_risk: Signal.fire(**kwargs) loses all callback contract information #1779

@bluetoothbot

Description

@bluetoothbot

Problem

Signal._handlers is typed list[Callable[..., None]], and fire(**kwargs: Any) forwards arbitrary kwargs through to each registered handler. There is no compile-time, mypy-time, or runtime check that callers are passing the keys handlers expect (zc, service_type, state_change) or that handlers declare them. A typo in a fire-site key, or a handler that forgets one of the keyword args, fails only at the moment a real event happens to dispatch — which on quiet networks may be hours or days into the run. The signature also makes refactoring the dispatch contract effectively undetectable: adding or renaming a key shows up nowhere in type-checking. Because ServiceBrowser, AsyncServiceBrowser, and external consumers all wire callbacks through this mechanism, the contract is load-bearing for the whole browse API.

Why This Matters

The lack of contract masks integration bugs in third-party code that subscribes to ServiceBrowser, and it blocks mypy from helping anyone (including this repo) catch dispatch mismatches when the dispatch shape ever changes.

Suggested Fix

Lock the contract down. Define a Protocol describing the handler signature (def __call__(self, zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange) -> None), type _handlers as list[that protocol], and change fire to take positional/keyword parameters that match — def fire(self, *, zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange) -> None. Keep backward compatibility by leaving the register_handler accepting Callable[..., None] but type-narrowing internally. Test coverage already exists for the happy paths, so the migration is mostly mechanical.

Details

Severity 🟡 Medium
Category interface_risk
Location src/zeroconf/_services/__init__.py:51-78
Effort 🛠️ Moderate effort

🤖 Created by Kōan from audit session

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions