Skip to content

Secrets and Logins

Sam LABBE edited this page Oct 13, 2026 · 1 revision

Secrets and Logins

The login story is layered by who holds the secret: the human registers once, the keychain keeps it, the daemon injects it, the agent never sees it.

Layer 1 — logged-in states (cookie jars)

The simplest flow: a human logs in once in a visible window, the state is parked in the OS keychain, and every later run restores it.

curl -s -X POST http://127.0.0.1:8765/sessions/show -d '{"session":"setup"}' -H 'Content-Type: application/json'
# … human logs into the site …
navette state save devto --session setup
# forever after, any agent / any run:
curl -s -X POST http://127.0.0.1:8765/sessions/load -d '{"session":"work","store":"devto"}' -H 'Content-Type: application/json'

The jar lives in the OS keychain; only metadata (name, origin, timestamp) ever leaves the daemon. macOS re-prompts on a new binary (ACL) — an implicit per-release consent.

Layer 2 — origin-bound credentials (no passwords anywhere near the agent)

The human saves a login once:

# navigate a session to the site's login page first — the credential binds to THAT exact origin
navette creds set mysite --session setup
# prompts for username + password on hidden stdin

Then any agent fills the login form:

curl -s -X POST http://127.0.0.1:8765/login -d '{"site":"mysite","session":"work"}' -H 'Content-Type: application/json'

Guarantees:

  • The password is resolved inside the daemon and injected into the page. It never appears in the request, the response, or a log.
  • No read-back API at all — GET /creds lists sites, origins and usernames only.
  • The fill is refused on any other origin (403) — a hostile page or a prompt-injected agent can neither harvest the secret nor aim it at another site.
  • The one honest limit: at fill time the password does reach the page — the same as human typing. The origin-binding is the defense.

Layer 3 — human moments (OAuth, 2FA, captcha)

2FA stays human by design:

curl -s -X POST http://127.0.0.1:8765/sessions/show -d '{"session":"work"}' -H 'Content-Type: application/json'
# the human does the 2FA / captcha / consent screen on the now-visible window
curl -s -X POST http://127.0.0.1:8765/sessions/hide -d '{"session":"work"}' -H 'Content-Type: application/json'

Captcha policy — hard line: navette never solves a captcha and never helps bypass one. Not for legality-only reasons: automated captcha solving is fingerprint warfare you always lose, and it poisons the sessions you depend on. session_show is the designed answer.

Layer 4 — passkeys / WebAuthn (plumbing shipped, one measured wall)

WKWebView only runs a passkey ceremony when the app implements the WKUIDelegate panel runner — and navette does:

  • /health reports auth_ceremony on builds with the plumbing.
  • GET /sessions carries a per-session auth state while a sheet waits for the human.
  • The credential never crosses into the daemon: the gesture happens entirely inside Apple's system UI.
  • The chain provably reaches Apple's AuthenticationServicesAgent — verified in the unified log.

The honest limit (measured, logged): the system agent refuses to present the authorization sheet to builds without a real signing Team ID — Told not to present authorization sheet (ASC AuthorizationError 1 → ASAuthorization 1004). Ad-hoc and unsigned binaries (brew services included) get the ceremony cancelled before the sheet appears. A Developer ID–signed build is the unlock to retest — everything else is in place.

As a side effect of the same WKUIDelegate work, JS dialogs are now logged and auto-dismissed.

What the agent is never allowed to do

  • See a password (there is no read-back route).
  • Ask for a password in chat — automation flows route around credentials entirely (/login, state jars, session_show).
  • Solve or bypass a captcha.
  • Use a credential off its saved origin (daemon-side refusal).

See also Security for the enforcement table and CLI Reference for the creds/state subcommands.

Clone this wiki locally