Skip to content

macOS: daemon fails to start — secure local state (local_state.py) is Linux-only #7

Description

@Micaxes

The daemon can't start on macOS: the new secure-local-state preflight (src/tendwire/local_state.py, introduced with the 0.7.0 hardening) hard-fails with no opt-out. I've root-caused it, confirmed macOS has the pieces for a portable fix, and started one — but there's a second, deeper issue I'd like maintainer guidance on before completing, since this is security-critical code.

Repro

$ tendwire daemon --db-path <path>/tendwire.db
tendwire.local_state.LocalStateError: secure local state requires supported POSIX filesystem operations   # UNSUPPORTED_PLATFORM

Root cause (blocker 1 — fixable)

proc_fd_path and canonical_path_from_fd resolve an open directory fd back to a pathname via /proc/self/fd/<dir_fd>, gated on sys.platform.startswith("linux"). macOS has no procfs, so both raise UNSUPPORTED_PLATFORM (these feed the sqlite terminal preflight).

Notably, the capability preflight is fine on macOS — it already passes O_NOFOLLOW, O_DIRECTORY, dir_fd support for open/stat/mkdir/unlink/chmod/chown/link, and stat/chown follow_symlinks. The only Linux-specific dependency is the /proc fd→path resolution.

macOS/BSD have the exact portable equivalent: fcntl(fd, F_GETPATH) returns the descriptor's canonical pathname (verified round-trip on macOS 26.5: F_GETPATH(/tmp fd) -> /private/tmp, same inode).

Partial fix (offered)

Branch Micaxes/tendwire:macos-local-state (commit 28edb66, off current main) adds a _fd_directory_path() helper that uses F_GETPATH on non-Linux POSIX hosts, re-verified against the fd's inode exactly as the /proc path is. This clears the UNSUPPORTED_PLATFORM wall — the two functions resolve correctly and the daemon gets past that preflight.

Blocker 2 (needs your guidance)

With blocker 1 fixed, the daemon then fails ENTRY_CHANGED deeper in the strict path-walk. An isolated probe of proc_fd_path/canonical_path_from_fd on macOS passes the same_inode check, so the mismatch is elsewhere in the walk — I suspect an interaction between the O_NOFOLLOW component-by-component walk and F_GETPATH's canonicalization (possibly APFS firmlinks like /Users). Separately, tests/test_store.py has ~257 macOS failures, so there may be more Linux-specific assumptions in the secure-local-state model.

Since this is a security boundary, I didn't want to keep changing its guarantees blind. Happy to complete the macOS port (and the F_GETPATH change above) if you can point me at the intended invariants — especially whether the path-walk assumes Linux-only /proc-anchored resolution semantics that F_GETPATH can't fully reproduce.

Environment

  • macOS 26.5.1 (arm64) · Python 3.14.3 · tendwire v0.1.0rc5 (33fa90a)
  • Context: driving the Herdres→Telegram bridge on macOS (a downstream vendors this engine). Prior macOS gaps (turn adapter) were resolved upstream in herdres #138.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions