# EloPhanto — Agent Verification & 2FA

> **Status: TOTP Done** — 4 TOTP tools implemented (generate, enroll, list, delete). Secrets encrypted in vault, codes generated via pyotp. SMS via user-provided number (conversational). Email verification already works via agent's own inbox.

## Overview

When the agent signs up for services, logs into accounts, or performs sensitive actions, it encounters verification challenges: email codes, TOTP authenticator prompts, SMS codes, CAPTCHAs. The agent needs a unified strategy for handling each type.

EloPhanto's verification approach follows one principle: **use what the agent owns, ask the user for what it doesn't.**

| Method | Who owns it? | How the agent handles it |
|--------|-------------|--------------------------|
| **Email** | Agent's own inbox | Fully autonomous — poll inbox, extract code/link, verify |
| **TOTP** | Agent's own authenticator | Fully autonomous — extract secret, store in vault, generate codes |
| **SMS** | User's phone number | Ask user — agent requests the code, user reads it from their phone |
| **Push 2FA** | User's device | Ask user — agent waits for user to tap approve |
| **Hardware key** | User's physical key | Ask user — cannot be automated |

## TOTP Authenticator (Planned)

### How TOTP Works

TOTP (Time-based One-Time Password, RFC 6238) generates 6-digit codes from two inputs:

1. **A shared secret** — Base32-encoded key, established during 2FA enrollment
2. **Current time** — divided into 30-second windows

The code is a pure function: `HMAC-SHA1(secret, floor(unix_time / 30))` truncated to 6 digits. Google Authenticator, Authy, and every TOTP app use this exact same math. A code generated by `pyotp` is **mathematically identical** to one from a phone app — services cannot tell the difference.

### Agent Enrollment Flow

When a service shows "Set up authenticator app":

```
Service shows 2FA setup page
    |
    v
Agent clicks "Can't scan? Enter manually" (preferred — most services have this)
    |                                |
    | (text fallback available)      | (no text fallback)
    v                                v
Extract Base32 secret as text    Screenshot QR code element
    |                                |
    |                                v
    |                            Decode QR with pyzbar + Pillow
    |                                |
    |                                v
    |                            Parse otpauth:// URI
    |                                |
    +--------------------------------+
    |
    v
pyotp.TOTP(secret).now() -> generate confirmation code
    |
    v
Enter code on page to complete enrollment
    |
    v
Store in vault: vault_set("totp_github", secret)
Store metadata: service name, account, enrolled_at
    |
    v
Store backup/recovery codes if provided
```

### Agent Login Flow

```
Agent enters username + password
    |
    v
Service prompts "Enter your authenticator code"
    |
    v
Retrieve secret from vault: vault_get("totp_github")
    |
    v
Generate code: pyotp.TOTP(secret).now()
    |
    v
Check TTL: if < 5 seconds remaining, wait for next 30-second window
    |
    v
Enter code -> login complete
```

### The QR Code / otpauth:// URI

When a service displays a QR code for authenticator setup, it encodes a URI:

```
otpauth://totp/GitHub:agent@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30
```

| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `secret` | Yes | — | Base32-encoded shared secret |
| `issuer` | Recommended | — | Service name (GitHub, AWS, etc.) |
| `algorithm` | No | SHA1 | Hash algorithm |
| `digits` | No | 6 | Code length |
| `period` | No | 30 | Code validity in seconds |

The `secret` is all the agent needs. `pyotp.parse_uri()` handles the full URI.

### Tools

| Tool | Permission | Purpose |
|------|-----------|---------|
| `totp_generate` | SAFE | Generate a TOTP code for a stored service |
| `totp_enroll` | MODERATE | Store a new TOTP secret from a 2FA setup page |
| `totp_list` | SAFE | List services with stored TOTP secrets (names only, never secrets) |
| `totp_delete` | MODERATE | Remove a stored TOTP secret |

#### `totp_generate` (`tools/totp/generate_tool.py`)

- **Permission:** SAFE (read-only, generates a code from an existing secret)
- **Params:** `service` (string — vault key, e.g. "github", "aws")
- **Returns:** `code` (string — 6-digit TOTP code), `seconds_remaining` (int — TTL before code expires), `service` (string)
- **Behavior:** Retrieves encrypted secret from vault, generates code via `pyotp.TOTP(secret).now()`. If < 5 seconds remaining in the current window, waits for the next code to avoid race conditions.

#### `totp_enroll` (`tools/totp/enroll_tool.py`)

- **Permission:** MODERATE
- **Params:** `service` (string — name for this entry, e.g. "github"), `secret` (string — Base32 TOTP secret), `account` (string, optional — the account/email associated), `backup_codes` (array of strings, optional — recovery codes)
- **Returns:** `service` (string), `account` (string), `enrolled` (bool)
- **Behavior:** Validates the secret is valid Base32, stores in vault as `totp_{service}`, optionally stores backup codes as `totp_{service}_backup`. Updates identity beliefs with the new 2FA enrollment.
- **Note:** The agent extracts the secret from the 2FA setup page (via browser tools) before calling this tool. This tool only handles storage.

#### `totp_list` (`tools/totp/list_tool.py`)

- **Permission:** SAFE
- **Params:** none
- **Returns:** `services` (array of `{service, account, enrolled_at}`) — names only, never secrets
- **Behavior:** Scans vault keys with `totp_` prefix, returns metadata.

#### `totp_delete` (`tools/totp/delete_tool.py`)

- **Permission:** MODERATE
- **Params:** `service` (string)
- **Returns:** `deleted` (bool)
- **Behavior:** Removes `totp_{service}` and `totp_{service}_backup` from vault.

### Security

- **Secrets never in LLM context** — TOTP secrets are vault-only. The `totp_generate` tool retrieves the secret at execution time and returns only the 6-digit code. The LLM never sees the Base32 secret.
- **Encrypted at rest** — Stored in the existing Fernet-encrypted vault alongside other credentials.
- **Credential isolation** — Same pattern as email API keys and crypto wallet keys. The `sensitive_params` mechanism ensures secrets are redacted from logs.
- **Backup codes stored separately** — Recovery codes stored as `totp_{service}_backup` in vault, listed but never displayed in full.
- **Services cannot detect programmatic TOTP** — The generated code is a pure math function. No fingerprint, no side-channel. Indistinguishable from Google Authenticator.

### Dependencies

```
pyotp          # TOTP code generation (RFC 6238). Lightweight, no transitive deps.
pyzbar         # QR code decoding (fallback when no text secret available)
Pillow         # Image handling for QR screenshots (already a dependency)
```

System dependency for pyzbar: `zbar` shared library (`brew install zbar` on macOS, `apt-get install libzbar0` on Linux).

### What TOTP Covers

Most services that offer "authenticator app" 2FA use standard TOTP. This includes:

GitHub, Google, AWS, Azure, Cloudflare, Slack, Discord, Twitter/X, Facebook, Dropbox, 1Password, Hetzner, DigitalOcean, Stripe, and hundreds more.

## SMS Verification

### Design Decision: User's Phone Number

The agent does **not** get its own phone number. Reasons:

1. **VoIP detection** — Services like Google, Meta, and banks actively block Twilio/Telnyx VoIP numbers for SMS verification. A Twilio number would fail on the services that matter most.
2. **Cost vs. value** — $1.15/month per number + per-SMS fees for a capability that's unreliable.
3. **Regulatory complexity** — Phone number provisioning has KYC requirements in many jurisdictions.
4. **Declining use** — SMS 2FA is being phased out in favor of TOTP and passkeys. Most services that still offer SMS also offer TOTP as an alternative.

Instead, when SMS verification is required, the agent **asks the user**:

```
Agent: "This service requires SMS verification. I'll enter your phone number
        on the signup form. What's your phone number?"
User:  "+1 555-123-4567"

Agent enters the number on the form, service sends SMS.

Agent: "I've entered your number. You should receive an SMS code shortly.
        What's the code?"
User:  "847291"

Agent enters the code, verification complete.
Agent stores the phone number in identity beliefs for future use.
```

### System Prompt Guidance

The agent is instructed to:

1. **Prefer TOTP over SMS** — When a service offers both, always choose authenticator app.
2. **Prefer email over SMS** — When a service offers email verification, use the agent's own inbox.
3. **Fall back to SMS only when it's the only option** — Ask the user conversationally for their number and codes.
4. **Remember the user's number** — Store in identity beliefs (with permission) so the agent doesn't ask repeatedly.
5. **Never store SMS codes** — Codes are ephemeral, entered immediately, not persisted.

### No SMS Tools

SMS verification does not need dedicated tools. The flow is:

1. Browser tools to navigate and fill forms
2. Agent asks user for the code via conversation (CLI, Telegram, etc.)
3. Browser tools to enter the code

The user's phone number, if provided, is stored in identity beliefs: `{"user_phone": "+15551234567"}`.

## Email Verification (Already Implemented)

The agent has its own email inbox and handles email verification autonomously:

```
Agent creates inbox (email_create_inbox)
    |
    v
Agent fills signup form with its own email address (browser tools)
    |
    v
Agent polls for verification email (email_list with retry / email_monitor)
    |
    v
Agent reads verification email (email_read)
    |
    v
Agent extracts verification link or code
    |
    v
Agent navigates to link (browser tools) or enters code
    |
    v
Verification complete
```

This is fully autonomous — no user involvement needed. See [18-EMAIL.md](18-EMAIL.md) for full details.

## Verification Priority

When a service offers multiple verification options, the agent should prefer them in this order:

1. **Email** — Agent's own inbox, fully autonomous, zero cost
2. **TOTP authenticator** — Agent's own authenticator, fully autonomous after enrollment
3. **SMS** — Requires user involvement, but works as fallback
4. **Push-based 2FA** — Requires user to tap approve on their device
5. **Hardware key** — Cannot be automated, requires user's physical key

The system prompt instructs the agent to always choose the highest-priority option available.

## What Cannot Be Automated

| Method | Why | Agent's response |
|--------|-----|-----------------|
| **Push 2FA** (Duo, Okta Verify) | Requires human tap on registered device | Ask user to approve on their phone |
| **Hardware keys** (YubiKey, FIDO2) | Requires physical presence/touch | Ask user to insert and touch their key |
| **CAPTCHA** | Designed to block automation | Use CAPTCHA-solving service or ask user |
| **Biometric** (Face ID, fingerprint) | Requires user's physical body | Ask user to authenticate |
| **Phone call verification** | Requires answering a call | Ask user to answer and relay the code |

For all of these, the agent explains what's needed and waits for the user to complete the step.

## Integration Points

| Component | What changes |
|-----------|-------------|
| `tools/totp/` | 4 new tool files (generate, enroll, list, delete) |
| `core/registry.py` | Register TOTP tools |
| `core/planner.py` | Add TOTP tools + verification priority to system prompt |
| `core/agent.py` | Inject vault into TOTP tools |
| `pyproject.toml` | Add `pyotp`, `pyzbar` dependencies |
| `config.yaml` | No changes — TOTP secrets live in vault, not config |

## Files

| File | Description |
|------|-------------|
| `tools/totp/__init__.py` | Package init |
| `tools/totp/generate_tool.py` | totp_generate — generate a TOTP code |
| `tools/totp/enroll_tool.py` | totp_enroll — store a new TOTP secret |
| `tools/totp/list_tool.py` | totp_list — list enrolled services |
| `tools/totp/delete_tool.py` | totp_delete — remove a TOTP secret |

## Phases

| Phase | Scope | Status |
|-------|-------|--------|
| **Phase 1** | 4 TOTP tools, vault storage, system prompt integration, verification priority | Done |
| **Phase 2** | SMS user-phone flow in system prompt, phone number in identity beliefs | Done (in system prompt) |
| **Phase 3** | Verification skill (best practices for signup flows combining email + TOTP + SMS) | Planned |
