Skip to content

feat(corewlan): add CoreWLAN binding for macOS Wi-Fi scanning - #5

Merged
krisarmstrong merged 1 commit into
mainfrom
feat/corewlan-binding
Aug 24, 2026
Merged

feat(corewlan): add CoreWLAN binding for macOS Wi-Fi scanning#5
krisarmstrong merged 1 commit into
mainfrom
feat/corewlan-binding

Conversation

@krisarmstrong

Copy link
Copy Markdown
Contributor

Why

macOS 26 removed the airport CLI that seed shelled out to for all darwin Wi-Fi work. Verified on macOS 27.0 (26A5416b): the Apple80211.framework directory still exists but Resources/airport is gone. Seed's internal/wifi/scanner_darwin.go and wifi_darwin.go exec that exact path, so seed's macOS Wi-Fi is currently dead code — its tests pass only because they exercise the line parser against fixture strings.

CoreWLAN is the supported replacement and carries no deprecation attributes in the macOS 27 SDK. This lands the shared binding that both seed and trellis will consume, matching the pkg/license / pkg/csrf precedent for cross-product code.

The redaction trap

CoreWLAN redacts SSID and BSSID unless the process holds Location Services authorization — and a redacted scan does not fail. It returns the correct number of networks with correct RSSI and channel, and every identifier emptied to "". Passing that through as a successful scan reports an empty airspace instead of a permissions problem.

DecodeScan maps it onto ErrLocationDenied instead. The decoding rules are pure Go and unit-tested without an adapter.

Verification

Hardware-tested on macOS 27.0 both ways, via a signed bundle holding the grant:

Result
With location grant 13/13 networks with non-nil BSSID; correct SSID, RSSI, channel width, band, security. Association reports PHY mode + SNR
Without grant ErrLocationDenied

Cross-compile verified in the pinned release image (goreleaser-cross:v1.27.0@sha256:3ce3506e…): a darwin/arm64 Mach-O binary built with CGO_ENABLED=1 CC=oa64-clang links both frameworks, confirmed via otool -L. Its osxcross SDK (MacOSX26.1.sdk) carries the full 294-framework set. No macOS runners required.

Gates: gofmt · go vet · golangci-lint run 0 issues (v2.13.1) · go test -race · govulncheck clean.

Notes for review

  • Named corewlan, not wifi — this is a CoreWLAN binding, not a general fleet Wi-Fi abstraction. Seed's cross-platform Scanner seam stays where it is; a second abstraction layer here would be speculative.
  • JSON across the cgo boundary — scans are infrequent and the payload is small, so the cost is irrelevant next to holding the C surface to one string return with a single owner and a single free.
  • CLLocationManager settle — the first manager in a process reads NotDetermined while it connects to locationd asynchronously. A per-call manager reports the grant as absent even when held (caught on hardware: Scan() failed while Current() succeeded microseconds later). One manager per process, settled before its status is trusted.
  • PHY mode is empty for scanned networksCWNetwork genuinely exposes no PHY property; only a live association does. Security probes supportsSecurity: strongest-first rather than reading a field that does not exist.

Follow-on work

  1. Seed adapter — map into the BSSID-keyed ScannedNetwork, delete the airport parser and its tests
  2. Signed helper LaunchAgent holding the grant + unix-socket IPC with peer-cred checks
  3. macOS signing/notarization in deploy/macos/build-pkg.sh (currently has none)
  4. Trellis, once its macOS app exists

Owner-agreed constraint: macOS Wi-Fi scans only while a user is logged in — TCC location consent is per-user and needs a GUI session.

macOS 26 removed the `airport` command-line tool that seed shelled out to
for all darwin Wi-Fi work, leaving `internal/wifi/scanner_darwin.go` and
`wifi_darwin.go` exec'ing a path that no longer exists. CoreWLAN is the
supported replacement and carries no deprecation attributes in the macOS 27
SDK. This lands the shared binding both seed and trellis will consume.

CoreWLAN redacts SSID and BSSID unless the process holds Location Services
authorization, and a redacted scan does not fail: it returns the right number
of networks with correct RSSI and channel and every identifier emptied. That
silent-nil behaviour is what made this hard to diagnose, so DecodeScan maps it
onto ErrLocationDenied rather than reporting an empty airspace. The decoding
rules are pure Go and tested without an adapter.

Scan results cross the cgo boundary as JSON. Scans are infrequent and the
payload is small, so the cost is irrelevant next to holding the C surface to
one string return with a single owner and a single free.

Verified on macOS 27.0 (26A5416b) against a signed bundle holding the grant:
13/13 networks with non-nil BSSID, correct SSID, RSSI, channel width, band and
security; association reports PHY mode and SNR. Without the grant the same
binary returns ErrLocationDenied.

Note: the first CLLocationManager in a process reads NotDetermined while it
connects to locationd asynchronously, so a per-call manager reports the grant
as absent even when held. One manager is created per process and allowed to
settle before its status is trusted.
@krisarmstrong
krisarmstrong merged commit 55f994d into main Aug 24, 2026
2 checks passed
@krisarmstrong
krisarmstrong deleted the feat/corewlan-binding branch August 24, 2026 16:11
krisarmstrong added a commit that referenced this pull request Aug 24, 2026
The Packages table still listed only license and csrf, three PRs after the
CoreWLAN binding shipped (#5 scanning, #6 interface control and power, #8
Location Services authorization state).

Notes the darwin-only constraint: this is the one package in foundation whose
import does nothing useful off macOS, since the !darwin stub returns
ErrUnsupported.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant