Problem
The v2 product contract described in docs/NATIVE_ONLY_REDESIGN.md is "an app-assisted credential broker" that "uses public Apple APIs such as AuthenticationServices" to return iCloud Keychain credentials for associated domains.
The shipping implementation does not do that. native-app/Sources/NativeAppLib/BrokerCore.swift:
- Imports
AuthenticationServices but only touches ASCredentialIdentityStore.shared for side effect (line ~18). There is no ASAuthorizationController, no ASAuthorizationPasswordRequest, and no ASPasswordCredential handling.
- Responds to
apw login https://example.com by reading a bundled ~/.apw/native-app/credentials.json (see loginResponse at lines 266-325) that contains a plaintext demo password (apw-demo-password).
- Uses
NSAlert as the "approval UI" instead of Apple's native credential-selection picker.
That means APW today is a credential replayer, not a credential broker. Nothing in the shipped path talks to iCloud Keychain. Until this is fixed, apw login and apw fill cannot return real user credentials, domain expansion (#8) is moot, and the security posture in docs/SECURITY_POSTURE_AND_TESTING.md overstates what the app actually does.
Proposed Fix
- Introduce an
AuthenticationServicesBroker type in NativeAppLib that:
- Builds an
ASAuthorizationPasswordRequest scoped to the requested associated domain.
- Runs the request through
ASAuthorizationController on the main thread, with the native app as the presentation context provider.
- Converts
ASPasswordCredential results into the existing ResponseEnvelope shape, preserving the userMediated: true semantic.
- Maps
ASAuthorizationError.Code values (.canceled, .failed, .invalidResponse, .notHandled, .unknown) to stable APW broker error codes so the Rust CLI can keep its typed error surface.
- Route the
login and fill dispatch branches in BrokerCore.dispatch through the new broker instead of loadCredentials() / NSAlert.
- Keep the credentials-file path behind an explicit
APW_DEMO=1 env flag (or a --demo install flag) so it can still be used for the bootstrap end-to-end test, but is never the default user-visible flow.
- Update
docs/NATIVE_ONLY_REDESIGN.md Phase 3 exit criteria to reflect that AuthenticationServices is actually wired, and add a sentence to README.md distinguishing the demo mode from the real broker.
Acceptance Criteria
Problem
The v2 product contract described in
docs/NATIVE_ONLY_REDESIGN.mdis "an app-assisted credential broker" that "uses public Apple APIs such asAuthenticationServices" to return iCloud Keychain credentials for associated domains.The shipping implementation does not do that.
native-app/Sources/NativeAppLib/BrokerCore.swift:AuthenticationServicesbut only touchesASCredentialIdentityStore.sharedfor side effect (line ~18). There is noASAuthorizationController, noASAuthorizationPasswordRequest, and noASPasswordCredentialhandling.apw login https://example.comby reading a bundled~/.apw/native-app/credentials.json(seeloginResponseat lines 266-325) that contains a plaintext demo password (apw-demo-password).NSAlertas the "approval UI" instead of Apple's native credential-selection picker.That means APW today is a credential replayer, not a credential broker. Nothing in the shipped path talks to iCloud Keychain. Until this is fixed,
apw loginandapw fillcannot return real user credentials, domain expansion (#8) is moot, and the security posture indocs/SECURITY_POSTURE_AND_TESTING.mdoverstates what the app actually does.Proposed Fix
AuthenticationServicesBrokertype inNativeAppLibthat:ASAuthorizationPasswordRequestscoped to the requested associated domain.ASAuthorizationControlleron the main thread, with the native app as the presentation context provider.ASPasswordCredentialresults into the existingResponseEnvelopeshape, preserving theuserMediated: truesemantic.ASAuthorizationError.Codevalues (.canceled,.failed,.invalidResponse,.notHandled,.unknown) to stable APW broker error codes so the Rust CLI can keep its typed error surface.loginandfilldispatch branches inBrokerCore.dispatchthrough the new broker instead ofloadCredentials()/NSAlert.APW_DEMO=1env flag (or a--demoinstall flag) so it can still be used for the bootstrap end-to-end test, but is never the default user-visible flow.docs/NATIVE_ONLY_REDESIGN.mdPhase 3 exit criteria to reflect thatAuthenticationServicesis actually wired, and add a sentence toREADME.mddistinguishing the demo mode from the real broker.Acceptance Criteria
apw login https://<associated-domain>returns a real iCloud Keychain credential viaASAuthorizationControlleron a machine with that domain's AASA configured.apw loginnever readscredentials.jsonunlessAPW_DEMO=1is set.ASAuthorizationErrorcases map to documented broker error codes with human messages.native-app/Tests/NativeAppTests/covers the cancel, denied, and success paths using an injectedAuthenticationServicesBrokerfake.docs/NATIVE_ONLY_REDESIGN.mdanddocs/SECURITY_POSTURE_AND_TESTING.mdreflect the real integration surface.