Skip to content

Security model

What the widget does

  • Mounts inside a shadow root so it can’t read host-page DOM outside its own subtree (shadow DOM closed-ish — actually open mode but with strict input/output isolation).
  • Communicates with the LeFlux API over TLS.
  • Stores conversation locally (localStorage) and on the server (Firestore).
  • Executes click/type/navigate on the live page — same as a visitor would.

What the widget does NOT do

  • Doesn’t run arbitrary code. Only executes pre-defined primitives (click, type, navigate, scroll, select, wait) on indexed elements. No eval, no dynamic <script> injection.
  • Doesn’t read DOM outside its own scope unless prompted. The dom-inspector samples interactive elements + visible text for context; it doesn’t exfiltrate hidden fields or cookies or localStorage outside what visitors see.
  • Doesn’t submit forms without an action emission. Every form submit goes through the action pipeline + post-action verification. No silent submits.
  • Doesn’t bypass CAPTCHA or auth. Filled forms stop at human-verification gates.
  • Doesn’t enter sensitive data. Credit card numbers, SSNs, passport numbers, bank accounts — the agent refuses to fill these even if the visitor asks. Hard rule in the system prompt + an action-executor guard list.

Allowed-host validation

Every session-init call is host-validated server-side against the site’s allowed-host list. A token pasted on an unauthorized origin gets a host_not_registered response and the widget no-ops silently. See Allowed hosts.

Data residency

Firestore data lives in your Firebase project’s chosen region. Conversations + crawled content are stored under sites/{siteId} — your tenant doc, not cross-tenant. Other tenants can’t read your data.

For zero-trust scenarios, self-host — you control the Firestore project and the LLM endpoint.

Visitor privacy

  • Anonymous by default: no PII collected unless the visitor provides it during a form-fill.
  • leflux:visitorId cookie is a random UUID for analytics aggregation, not tied to identity. Can be disabled via data-no-visitor-id.
  • User-data-store (name, email, phone from form-fills) lives in localStorage only, 30-day TTL, never sent to anyone except the LLM call that’s currently filling a form. Disable via data-no-user-memory.
  • Conversation messages persist in Firestore by default (for dashboard analytics). Toggle off in Settings → Privacy → Message log.

Prompt injection

A malicious page could try to inject instructions into the agent via visible text (“Ignore previous instructions and…”). The widget mitigates:

  • visibleText is truncated to 5000 chars per turn, with strict markers around it in the prompt so the LLM treats it as page content not commands.
  • indexedElements text fields are treated as element labels, not commands.
  • The system prompt explicitly tells the agent to ignore in-page instructions.

We don’t promise zero injection vulnerability — LLM prompt injection is an open research problem — but the layered design makes practical exploits hard. If you spot one, please email ishaquehassan@digitalhire.com.

Action restrictions

Site admins can disable specific action types (Settings → Restrictions):

  • Disable navigate — the agent can only operate within the current page
  • Disable type — the agent can navigate + click but never type
  • Disable submit-detected click — the agent can’t trigger form submits

Useful for high-stakes pages (account deletion, irreversible billing actions).

Confirmation gate

For actions the system prompt classifies as high-stakes (delete, purchase, send-to-all), the agent emits confirmation_required instead of executing immediately. Visitor must tap Confirm in the chat UI for the action to proceed.

CSP compatibility

The widget loads from https://leflux.xrlabs.app. To allow it in your Content Security Policy:

script-src 'self' https://leflux.xrlabs.app;
connect-src 'self' https://leflux.xrlabs.app wss://leflux.xrlabs.app;
img-src 'self' https://leflux.xrlabs.app data:;
style-src-elem 'self' 'unsafe-inline'; /* widget injects styles into shadow root */

If you have a strict CSP without 'unsafe-inline' for styles, the widget won’t render. We don’t yet support nonce-based style injection (on roadmap).

Subresource integrity (SRI)

The embed.js bundle is small + frequently updated, so we don’t ship a stable SRI hash. The main leflux-agent.js is also no-cache to ensure visitors always get the latest bundle. If you need SRI, self-host and pin a version.

Disclosure

Security issues: email ishaquehassan@digitalhire.com with subject [SECURITY]. Please don’t file public GitHub issues for vulnerabilities.