Skip to content

Cross-Host Session Handoff — Threat Model

logi uses a host=role separation architecture. api.1pass.dev (end-user) and start.1pass.dev (developer/account) do not share cookie scope (we cannot widen the cookie domain to .1pass.dev because of the embed.1pass.dev iframe isolation). To use the same session, authenticated, on both hosts, we mint a per-host cookie via a single-use handoff token (SessionHandoffToken).

The policy in one line

HostRequired role
api.1pass.devuser
start.1pass.devuser (relaxed 2026-05-19; previously developer)

HandoffPolicy::REQUIRED_ROLES (app/policies/handoff_policy.rb).

Why we relaxed it

/account is the self-service entry point for ordinary users, yet it is host-locked to start.1pass.dev. If the policy allowed only developer, an ordinary user who visited /session/new directly and signed in could not reach /account → a dead-end UX.

Why it is safe — each controller carries its own gate

Relaxing the policy means an ordinary user can create a start.1pass.dev cookie, but that only means "they can reach the start host." Sensitive surfaces carry their own gates:

surfacegate
/developer/*Developer::BaseController#require_developer_or_admin
/console/applications, etc.same
/cli/auth/start, /cli/auth/approveCli::AuthorizationsController#require_developer_or_admin (added 2026-05-19, per a codex BLOCKER)
/account/*users access only their own account area — no gate (intended behavior)

Mandatory checklist when adding a new surface

When you put a new route/controller on start.1pass.dev, you must verify:

  1. Is it a sensitive operation? (RP registration, secret issuance, admin privileges, etc.) → add require_developer_or_admin (or a stricter gate) to the controller.
  2. Does it expose PII? → add a Current.user match check so only the owner can access it.
  3. Is it a privilege-escalation surface like CLI scope issuance? → require developer + add protection such as RequireRecentOtp.

If a gate is missing, an ordinary user can reach the start host with a start cookie and then escalate privileges → a privilege-escalation vulnerability. The codex review (2026-05-19) first caught this (the case where /cli/auth/start could issue apps:manage with no gate).

SessionHandoffToken's own invariants

  • TTL 30s — the token expires if it is not redeemed within 30 seconds of issuance.
  • Single-use enforced by the consumed_at column (consume! is an atomic update).
  • The target_host field pins the host at mint time. Redeeming on a different host makes consume! return nil (a codex post-merge fix). An attempt to redeem an API token on start is rejected.
  • The mint side runs cookies.delete(:session_id) — the token and the cookie are never alive at the same time for the same session (a single-auth-artifact policy).

Threat 1 — token theft + reuse

Scenario: an attacker intercepts the handoff URL (/session/handoff?token=...).

Mitigation:

  • TTL 30 seconds — even if intercepted, it expires quickly.
  • Single-use — once the legitimate user redeems the token, it is dead (replay blocked).
  • target_host binding — cannot be diverted to another host.
  • HTTPS enforced (production) — cleartext interception blocked.

Threat 2 — open redirect via return_to

Scenario: can /session/handoff?token=...&return_to=https://attacker.com send the user to an arbitrary URL?

Mitigation: CrossHostHandoff.safe_path? admits only a path-only value on the per-host allowlist. External URLs, protocol-relative URLs, and prefix traps (/accountXYZ) are all rejected → it redirects to the fallback path instead.

Regression spec: spec/requests/session_handoffs_spec.rb#"failure modes".

Threat 3 — privilege escalation (calling a sensitive surface after entering the start host)

Scenario: an ordinary user obtains a start.1pass.dev cookie via handoff → calls /cli/auth/start?...&scope=apps:manage.

Mitigation: see the "gate checklist" above. Each controller verifies Current.user.developer? itself. The policy relaxation only permits host entry; calling a power surface requires a separate gate.

Regression specs:

  • spec/policies/handoff_policy_spec.rb
  • spec/requests/cli/authorizations_spec.rb#"rejects regular user"
  • app/policies/handoff_policy.rb
  • app/controllers/session_handoffs_controller.rb
  • app/controllers/api/v1/me/cross_host_handoffs_controller.rb
  • app/models/session_handoff_token.rb
  • config/initializers/cross_host_handoff.rb

Identity가 제품의 신뢰를 만듭니다.