feat(corewlan): add CoreWLAN binding for macOS Wi-Fi scanning - #5
Merged
Conversation
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.
This was referenced Aug 24, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
macOS 26 removed the
airportCLI that seed shelled out to for all darwin Wi-Fi work. Verified on macOS 27.0 (26A5416b): theApple80211.frameworkdirectory still exists butResources/airportis gone. Seed'sinternal/wifi/scanner_darwin.goandwifi_darwin.goexec 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/csrfprecedent 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.DecodeScanmaps it ontoErrLocationDeniedinstead. 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:
ErrLocationDeniedCross-compile verified in the pinned release image (
goreleaser-cross:v1.27.0@sha256:3ce3506e…): a darwin/arm64 Mach-O binary built withCGO_ENABLED=1 CC=oa64-clanglinks both frameworks, confirmed viaotool -L. Its osxcross SDK (MacOSX26.1.sdk) carries the full 294-framework set. No macOS runners required.Gates:
gofmt·go vet·golangci-lint run0 issues (v2.13.1) ·go test -race·govulncheckclean.Notes for review
corewlan, notwifi— this is a CoreWLAN binding, not a general fleet Wi-Fi abstraction. Seed's cross-platformScannerseam stays where it is; a second abstraction layer here would be speculative.CLLocationManagersettle — the first manager in a process readsNotDeterminedwhile it connects tolocationdasynchronously. A per-call manager reports the grant as absent even when held (caught on hardware:Scan()failed whileCurrent()succeeded microseconds later). One manager per process, settled before its status is trusted.CWNetworkgenuinely exposes no PHY property; only a live association does. Security probessupportsSecurity:strongest-first rather than reading a field that does not exist.Follow-on work
ScannedNetwork, delete the airport parser and its testsdeploy/macos/build-pkg.sh(currently has none)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.