Design principles
LogLife follows a server-initiated contact model. Users must sign up through the website and verify their phone number before LogLife ever sends them a message. This is a deliberate architectural choice.Why “we message first”
| Concern | Open inbound (anyone texts us) | Server-initiated (we text first) |
|---|---|---|
| Identity | Unknown — anyone with the number can text | Known — tied to a Clerk account |
| API cost exposure | Unbounded — each message triggers LLM calls | Bounded — only verified users generate cost |
| Abuse surface | Wide — spam, prompt injection from strangers | Narrow — only authenticated users interact |
| Kill switch | Block phone numbers manually | Disable Clerk account, stop responding |
| Rate limiting | Hard — no user identity to rate-limit against | Easy — per-user, per-account limits |
Referrals instead of open access
Instead of letting anyone message LogLife directly, new users are onboarded through:- Direct signup at loglife.co/signup
- Referral links shared by existing users
Authentication
Website to plugin
All communication between the Next.js website (hosted on Vercel) and the OpenClaw plugin is authenticated with a Bearer token. The token is set asOPENCLAW_API_KEY on both sides.
- The website’s API routes (
/api/sessions,/api/verify) add the token to every request to the plugin. - The plugin validates the token using
crypto.timingSafeEqualto prevent timing attacks. - Requests without a valid token receive a
401 Unauthorizedresponse.
User authentication
User authentication is handled by Clerk. The website’s API routes verify that the caller has a valid Clerk session before proxying to the plugin. This means:- An unauthenticated browser request to
/api/verifyis rejected before it ever reaches the plugin. - The plugin itself does not need to know about individual users — it trusts the website’s Bearer token.
WhatsApp linking security model (current V2 flow)
Phone ownership is proven through a user-initiated WhatsApp message containing anLF-XXXX code.
- User enters phone on the dashboard.
- Backend registers the phone and generates a short-lived link code.
- User taps a prefilled WhatsApp button and sends the code from that phone.
- Plugin auto-verifies, marks link complete, and sends a welcome message.
Expiry and cleanup behavior
- TTL: Pending link codes expire after 5 minutes.
- Sweep interval: Expired pending links are cleaned every 30 seconds.
- Effective removal window: cleanup usually happens between
5:00and5:30after link creation (or earlier if message-count guardrails trigger). - Persistence across restarts: pending links are stored in
multi-user/pending-links.json, so a restart does not silently drop pending-link cleanup state. - Wrong-number rollback scope: only users created during the current register flow are auto-removed on expiry; pre-existing users are not auto-deleted.
Rate limits
Current limits
| Resource | Limit | Window |
|---|---|---|
| Verify/send code API (legacy flow) | 1 per phone | 60 seconds |
| Pending link validity (V2 flow) | 1 link | 5 minutes |
Planned limits
As LogLife scales, additional guardrails will be added:- Message rate limits — maximum messages per user per day
- Audio processing limits — maximum audio messages and duration per day
- Token budgets — per-user token consumption caps per billing period
- Usage dashboard — visible on the dashboard so users can monitor their own consumption
Stage-by-stage attacks and mitigations
| Stage | Possible attacks | Current mitigation |
|---|---|---|
| Dashboard register request | Unauthenticated caller attempts registration; forged client requests | Clerk session required by website API route; plugin route protected by Bearer API key; timing-safe token compare |
| Phone entry and linking | User enters wrong number; script repeatedly requests links | 5-minute TTL; 30-second sweeper; pending-link message-count cap; existing-user path is idempotent |
| Pending (not verified yet) | Unverified sender tries to trigger agent replies by messaging random text/code-like payloads | Pending links are tracked per phone; linking messages are intercepted and canceled before normal agent reply path |
| Code replay / brute-force | Try old codes, random LF-XXXX payloads, or repeated guesses | Exact-code match required per phone; pending link expires quickly; wrong attempts increase counters and can trigger cleanup |
| Verified messaging | Abuse via high message volume or expensive prompts | Allow-list DM policy; account-level controls and disable path; planned per-user message/token budgets |
| Infra / operational | Gateway restart during linking; stale in-memory state | Pending-link state is persisted to disk and reloaded; cleanup continues after restart |
Residual risk and intentional behavior
- Existing users are intentionally not auto-deleted when a fresh link attempt expires. This avoids deleting valid long-lived users during accidental re-link attempts.
- If you need stronger enforcement (for example, “must re-verify before any further messaging”), use a stricter reconnect mode that temporarily suspends allow-list access until verification completes.
Data flow (legacy verification API)
Infrastructure security
- No secrets in code: API keys, Clerk keys, and SSH keys are stored in GitHub Actions secrets and Vercel environment variables. Never committed to the repository.
- HTTPS everywhere: A Caddy reverse proxy terminates SSL in front of the gateway. The gateway port (18789) is closed to the public — only Caddy reaches it locally. See the Networking guide for setup.
- Layered auth on Control UI: The OpenClaw Control UI is protected by HTTP basic auth (Caddy) in addition to the gateway’s own authentication token. The API domain is open but protected by the Bearer token.
- SSH deployment: Plugin deployment uses SSH key authentication. No passwords are transmitted.
- Health checks: Every deployment verifies the plugin is responding correctly before marking the deploy as successful.
- Sessions persisted to disk: Session data survives gateway restarts. No data loss during deployments.
- In-memory verification codes: Codes are intentionally not persisted. A gateway restart clears all pending codes, which is acceptable given their 5-minute TTL.