Skip to content

Security — Threat model + defense layers

Threat model

#ThreatImpactDefense
T-1MFA bombing (approval fatigue)User taps [Approve] without thinking6-digit number_match + 10-minute cool-down + in-flight suppression + Rack::Attack throttle
T-2Agent token theftAttacker fires arbitrary approval requestssigned mode = Ed25519 key separation (the token alone is useless), instant revoke, optional IP allowlist
T-3Push not delivered (Focus mode)TTL exceeded → work blockediOS time-sensitive notification + FCM HIGH priority + user-defined TTL of 30s–30min
T-4Shifting blame (user repudiation)In a dispute, "I didn't tap that"WebAuthn assertion + display_payload_hash seal + binding_message + device ID + IP record
T-5Audit log leakHigh-value transaction data exposedApplication-layer encryption (encrypts :context_ciphertext), hash chain + DB WORM trigger, separate retention policy
T-6Agent bypasses logiIgnores the rules and acts directlylogi cannot prevent this — we provide a post-hoc audit reconciliation cookbook
T-7Agent identity spoofingImpersonating another agentAsymmetric key issued at registration, used to sign requests, "verified" badge in the user's inbox
T-8Replay attackResending a captured signaturetimestamp ±5min + single-use nonce (5-minute window) + single-use idempotency_key
T-9Display tamperingAgent's display ≠ server's recorddisplay_payload_hash seals every field the user saw with SHA-256, verified at decision time
T-10auth_mode downgradeAttempting to demote a signed agent to bearer_onlyauth_mode is loaded only from the DB row; request parameters are ignored
T-11Host bypassCalling the mobile API from an attacker's hostThe mobile endpoint enforces an api.1pass.dev exact-match host constraint (end_user_host_constraint)
T-12Hash chain raceConcurrent writes fork the chainchronological.lock.last + transaction (borrowed from the Authentication::AuditLogger pattern)

Defense layers in detail

6-digit Number Matching

The same 6 digits appear on the user's phone screen and on the agent's side. After confirming they match, the user uses Face ID. This gives an attacker a 1-in-1,000,000 chance of guessing correctly.

SecureRandom.random_number(1_000_000) gives a uniform distribution — no modulo bias.

2 digits (1%) is not safe — even with the uniform distribution of random_number(100), 100 attempts will pass right away.

Ed25519 signature (signed mode)

Every request's canonical string includes the method, path, timestamp, nonce, body digest, and agent ID. If any one of them is tampered with, the signature breaks.

The key is shown only once at registration, and the server stores only the public key. If a key is leaked, the user presses rotate_credentials! in the console and everything — including in-flight requests — is invalidated.

In-flight suppression

If a pending or delivered request already exists for the same (user, agent, action_type), a new request returns the existing row as-is. No new push is sent. Even 100 POSTs in one second produce just one notification on the user's phone.

Cool-down

For 10 minutes after a rejection, a new request with the same action_type is blocked with 429. This stops infinite retries.

WORM (Write-Once-Read-Many)

agent_action_audit_logs:

  1. Rails readonly? is true — an attempted update! / destroy! raises ActiveRecord::ReadOnlyRecord
  2. PostgreSQL trigger — even raw SQL UPDATE / DELETE is blocked (the only bypass is the logi.allow_agent_audit_purge session flag)
  3. Hash chain — forging even a single row breaks every row after it in the chain

User control

In the "Agents" tab of the logi mobile app, the user can:

  • View the list of registered agents and each one's last-used time
  • Instantly revoke an individual agent
  • Report suspicious activity to the logi security team

Cautions when using bearer_only mode

ItemRecommendation
Environmentdemo / dev / local testing only
Token expirydefault 30 days; shorter is better
IP allowlistrequired. A single IP or a narrow CIDR
Productioncan be blocked with the AGENT_BEARER_ONLY_ALLOWED=false environment variable
Auditevery request records auth_mode, so it can be separated out after the fact

If a single bearer_only token leaks, an attacker can immediately issue arbitrary approval requests with 100% success. Take signed mode as your default.

Reporting security issues

Email security@1pass.dev — PGP or plaintext both welcome. We reply within 24 hours.

A bounty program is coming. We'll announce it later on a separate page.

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