Summary
The Swift broker (BrokerServer.run() in native-app/Sources/NativeAppLib/BrokerCore.swift) accepts connections on ~/.apw/native-app/broker.sock and immediately dispatches login / fill / status / doctor requests. It never verifies the identity of the connecting peer (no getpeereid() / LOCAL_PEERCRED). The only access control is the filesystem: the socket is chmod 0600 inside a 0700 directory.
The Rust client mirrors this — socket_path_safe_to_connect() in rust/src/native_app.rs checks the socket's file type and that its mode is exactly 0600, but does not verify the socket is owned by the current euid.
Why it matters
For a credential broker, the value proposition over reading the keychain directly is mediation. Relying only on 0600 perms means the trust boundary is "any process running as the same user." That boundary is widening with the newly added automation surfaces (AppleScript request login/request fill, AppIntents/Shortcuts, and the direct-exec fallback), all of which can reach the broker without the operator's CLI in the loop.
Credential return is still user-mediated (the AuthenticationServices OS picker in production, or the approval prompt in APW_DEMO), so this is defense-in-depth rather than a silent-exfiltration bug. But the request channel itself is unauthenticated.
Recommendation
- In the Swift
accept() loop, call getpeereid() on the accepted fd and reject connections whose euid != the broker's euid.
- In
socket_path_safe_to_connect(), also assert metadata.uid() == geteuid() (mirroring the ownership check already used in validate_external_provider_path).
- Consider request audit logging (requesting PID/path) to the broker log so credential requests are attributable.
References
native-app/Sources/NativeAppLib/BrokerCore.swift (run(), bindListeningSocket())
rust/src/native_app.rs (socket_path_safe_to_connect)
- Note:
SECURITY.md lists cross-user access as out of scope; this is a same-user defense-in-depth hardening for a mediating broker.
Severity: Medium (defense-in-depth)
Filed by an automated deep security review.
Summary
The Swift broker (
BrokerServer.run()innative-app/Sources/NativeAppLib/BrokerCore.swift) accepts connections on~/.apw/native-app/broker.sockand immediately dispatcheslogin/fill/status/doctorrequests. It never verifies the identity of the connecting peer (nogetpeereid()/LOCAL_PEERCRED). The only access control is the filesystem: the socket ischmod 0600inside a0700directory.The Rust client mirrors this —
socket_path_safe_to_connect()inrust/src/native_app.rschecks the socket's file type and that its mode is exactly0600, but does not verify the socket is owned by the current euid.Why it matters
For a credential broker, the value proposition over reading the keychain directly is mediation. Relying only on
0600perms means the trust boundary is "any process running as the same user." That boundary is widening with the newly added automation surfaces (AppleScriptrequest login/request fill, AppIntents/Shortcuts, and the direct-exec fallback), all of which can reach the broker without the operator's CLI in the loop.Credential return is still user-mediated (the AuthenticationServices OS picker in production, or the approval prompt in
APW_DEMO), so this is defense-in-depth rather than a silent-exfiltration bug. But the request channel itself is unauthenticated.Recommendation
accept()loop, callgetpeereid()on the accepted fd and reject connections whose euid != the broker's euid.socket_path_safe_to_connect(), also assertmetadata.uid() == geteuid()(mirroring the ownership check already used invalidate_external_provider_path).References
native-app/Sources/NativeAppLib/BrokerCore.swift(run(),bindListeningSocket())rust/src/native_app.rs(socket_path_safe_to_connect)SECURITY.mdlists cross-user access as out of scope; this is a same-user defense-in-depth hardening for a mediating broker.Severity: Medium (defense-in-depth)
Filed by an automated deep security review.