Skip to content

SECURITY MODEL

Security

The public site is a separate, browser-first product. It has no automatic route into a private LAN; a visitor may explicitly configure and approve one exact HTTPS MCP address from their own browser.

Report a security issue

Send the affected URL or feature, the impact you believe is possible and minimal reproduction steps to sales@pui.ai. Do not include live credentials, access tokens, backup passphrases or private documents in the initial message.

Start a security report View security.txt

Trust boundary

The application shell is served as static and server-rendered web content. Chats are kept in the browser. Cloud AI calls travel directly from the browser to the provider the visitor selects; MCP calls travel directly to the exact reviewed HTTPS server. Public HTTPS is the default. A separate Trusted LAN scope accepts only an explicitly confirmed loopback, RFC 1918 or unique-local HTTPS address and still requires browser-trusted TLS, compatible CORS/Private Network Access headers, network/message grants and exact approval rules.

The optional private Automations runner is a separate owner-operated boundary, not an application-server database. Its host SQLite file is the sole authority for schedules, execution claims, run audit and Inbox results. The browser reaches it only through one exact Bearer-authenticated, current-device-tested custom MCP catalog at a reviewed public-HTTPS or Trusted-LAN endpoint; ambiguous or changed authority fails closed.

OpenRouter works without setup through an intentionally public client-side key with no paid balance. The public mode is fail-closed to openrouter/free and model IDs ending in :free: discovery, visible catalogs and the final request transport all enforce the restriction. Paid-model and web-search requests are rejected before any network call. An unlocked visitor-owned OpenRouter key always takes precedence and restores the ordinary full catalog.

Model routes are capped at five configured steps and a user-selected billed-attempt ceiling. Automatic failover is restricted to pre-output network, timeout, rate-limit and server failures. Authentication, invalid requests, response limits and every partial response stop the route immediately.

Browser credential boundary

Provider and MCP credentials are scoped to one immutable workspace and exact connection authority; changing a provider endpoint, MCP network scope or authentication header cannot reuse the former key. Remember in this browser (recommended) stores AES-256-GCM ciphertext and a separate non-extractable Web Crypto device key in IndexedDB, so the credential remains ready across reloads, new tabs and browser restarts in the same profile. This tab only uses session storage. Passphrase-protected vault (strict) never stores its passphrase and requires unlock after a browser restart. None of these records is sent to the pui.ai server or included in workspace, library, chat or plaintext exports. An explicit password-protected complete backup can include currently readable credentials inside its authenticated encrypted section.

Encryption protects persistent credential data at rest; it does not make a compromised browser safe. Non-extractable means raw device-key material cannot be exported through Web Crypto, not that same-origin code cannot ask the browser to decrypt with it. Application code running on this origin and privileged or malicious extensions may be able to use an automatically available or unlocked credential. The automatic browser-bound vault does not sync to another browser, profile or device. Use restricted, revocable keys and a trusted browser profile.

Local-model boundary

Ollama, LM Studio and oMLX are OpenAI-compatible development providers that default to explicit loopback addresses on the visitor's own computer. They work without a token when the local runtime permits it and can optionally use a browser-vault Bearer token. Requests go from the browser directly to that local process; the pui.ai server cannot see, proxy or operate it. Provider configuration remains restricted to loopback HTTP or reviewed HTTPS. The distinct Trusted LAN MCP mode does not broaden local-model destinations.

A local runtime must opt in to the https://pui.ai browser origin through its own CORS configuration, and the visitor must grant the site's browser-level Local Network Access permission. Loopback requests explicitly declare the browser's dedicated loopback address space so current Chromium versions can present that permission instead of silently treating HTTP loopback as mixed content. The application probes only after the visitor opens and tests that provider, imports only bounded model metadata, and applies the same capability checks and attachment limits used for cloud providers. A locally served model is not assumed to support Vision, tools or reasoning merely because its API is reachable.

Delivery protections

  • Content Security Policy and same-origin framing protection.
  • HTTPS-only production delivery with Strict Transport Security.
  • No-referrer, no-sniff and restrictive browser permissions; microphone access is same-origin and still requires explicit user consent.
  • No third-party analytics, embedded advertising or remote script tags.

Local document boundary

PDF and DOCX extraction runs on demand in the browser. File signatures, original size, PDF page count and extracted plain text are bounded. Macros, document HTML, embedded images and linked resources are not executed. The composer accepts at most five files under shared byte, text and chunk ceilings, rejects duplicate content, clears its chat-owned queue on navigation, and wraps content in numbered untrusted-document boundaries. Only attachment metadata persists after that request unless the user separately saves a canonical derivative in the Asset Vault. Agent-assigned and broader project/tag retrievals are deduplicated before one final token and passage ceiling is applied; deleting a source also removes agent references to it.

Persistent Asset Vault boundary

The Asset Vault is a separate IndexedDB database physically named for one immutable workspace ID. It has no silent memory fallback. An atomic add operation accepts at most 100 files; the vault caps one workspace at 1,000 assets and 250 MB of verified content, with 10 MB source limits and a stricter 5 MB DOCX limit. Explicit user-uploaded documents are persisted only as normalized text/plain; images pass the same signature, dimension, decode and canvas re-encoding pipeline used by transient image input. Approved MCP output uses a narrower internal path: generated text is UTF-8-validated, control-cleaned and credential-redacted; generated PDF/DOCX preserves exact bytes only after allowlist, size, MIME, extension, signature and hash validation; generated images are still re-encoded. These bytes never enter the model transcript or workspace JSON.

Canonical payloads are deduplicated with SHA-256 and verified again for MIME type, size and hash before reuse. A selected batch is fully resolved and checked against composer limits before either pending queue changes. Workspace and chat JSON carry only bounded provenance metadata and stable local asset IDs—never Blobs, `data:` URLs or `blob:` URLs. Importing JSON as a different workspace detaches those local IDs. Linked or pending assets cannot be deleted through the vault UI; workspace reset and deletion clear only that workspace's namespace.

Deletion and recovery boundary

Ordinary chat, project and folder deletion is recoverable: a bounded soft-delete timestamp is committed while relationships remain intact. Deleted content is excluded from navigation, normal search, usage analysis, active model context and knowledge retrieval. Permanent purge is separately confirmed and commits the workspace graph before cleaning the independent knowledge database. Shared knowledge indexes are retained, and cleanup failures are reported instead of being hidden. Trash inventory exposes bounded metadata only; raw messages and passages are not searched by the recovery view. Resetting or deleting a complete workspace also clears its separate Knowledge, Asset Vault and credential namespaces; failures are reported or treated as best-effort cleanup after the registry tombstone prevents reopening the deleted workspace.

Conversation-search boundary

Conversation search runs locally over bounded title, active-branch message, tag, project and folder-hierarchy projections. Normal indexing excludes deleted message content, caps both per-chat and total scanned text, and derives snippets only from admitted content. Saved views contain bounded filters and are portable; recent queries are recorded only after an explicit result selection, reject credential-shaped text and are stripped from every workspace import and export.

Public knowledge-source boundary

Public URL import happens only after an explicit action. It rejects syntactically local, private, link-local and reserved hosts, credentials, sensitive query keys, fragments and non-default ports. The browser omits cookies and referrers, denies redirects, requires CORS and an unchanged public response URL, and bounds time, bytes, UTF-8 text type and extracted chunks. Returned HTML is converted with a string tokenizer and is never rendered; scripts, styles, SVG, frames and embedded objects are removed. HTTPS is recommended. Because a browser client cannot independently resolve and pin DNS, these checks cannot completely prevent DNS rebinding; do not use URL import as a privileged network proxy.

Web-search boundary

Web search v1 is an optional per-chat OpenRouter capability and starts disabled. It requires a visitor-owned OpenRouter key and is unavailable with the public free fallback because it can create additional charges. It uses the same direct browser-to-OpenRouter transport as an ordinary OpenRouter response; no pui.ai gateway, relay or server-side credential participates. The result limit is constrained to 1–10 with a default of 5.

Search output is treated as untrusted provider data. The persisted assistant evidence is versioned and bounded to the OpenRouter provider marker, search time, and citation URL, title and optional character-position indices. Raw snippets, fetched search content, the query as a separate search record, request headers and credentials are not part of that evidence. Citation URLs are rendered through the application's safe external-link boundary; search result pages are not fetched by a hidden pui.ai service.

Brave Search is deliberately unavailable in the public browser build. Its authenticated API lacks the required cross-origin browser contract and Brave advises against exposing its subscription token in client-side code. Because this deployment contains no operator or generic search relay, it does not accept a Brave token or attempt to bypass that provider boundary.

Always-on automation boundary

A model may create only an inactive scheduler draft after an explicit scheduling request. Scheduler list, read, edit, activate, pause, run-now, acknowledge and delete operations are UI-only and are never exposed through model tool projection, including when ordinary MCP tools are broadly approved. Activation requires a separate review of the prompt, local time and time zone, next occurrence, read-only research source, fixed execution profile, token/runtime limits, cost boundary, catch-up and delivery.

Unattended execution is closed to two modes: a reminder writes its reviewed message to the host Inbox without AI, and an AI job optionally performs bounded Brave News research followed by one fixed OpenAI-compatible summary. It cannot call Terminal, control a browser, mutate files or invoke arbitrary/destructive MCP tools. Scheduler credentials are root-provisioned host credentials and are never inherited from the browser vault, gateway bearer environment, chat model or browser approval rules.

Every mutation uses an idempotent request ID and entity-revision CAS. Occurrences are unique by job and scheduled wall-clock slot; catch-up admits at most the latest recent miss. If the service restarts after an external call may have begun, the run becomes uncertain, future execution requires attention and the occurrence is never replayed automatically. The canonical Inbox survives soft job deletion. The SQLite database is protected by a root-only online-backup timer using SQLite's backup API, integrity checking, fsync and atomic publication.

Portable knowledge boundary

Knowledge packages are strict, bounded plaintext JSON. They include selected indexed passage text, but no credentials, executable content or authorization. URL provenance is removed, project references are non-authoritative hints, and tags are not restored. Import validates the complete package before preview, rejects unknown or credential-like content, requires an explicit existing project and confirmation, then allocates fresh source and chunk IDs. Store these files as carefully as the original documents.

Local image boundary

Image input accepts signature-verified PNG, JPEG and WebP only. Dimensions and pixel count are checked before decode; a local canvas redraw strips EXIF/GPS and other source metadata, then limits dimensions and encoded bytes. Ordinary composer data URLs remain in transient React state and never enter IndexedDB or exports. If the user explicitly saves an image to the Asset Vault, only the re-encoded metadata-stripped Blob enters its workspace-scoped database; a transient data URL is recreated just in time when the saved image is attached. Images are sent only to compatible Vision models. Incompatible route members, Response Comparison lanes and Council candidates fail before dispatch.

Model metadata boundary

Custom and discovered model metadata keeps provider and request IDs immutable. Names, descriptions, token ceilings and USD prices are bounded, and credential-shaped values are rejected. Declared capabilities are stored separately from what the browser runtime can actually use. Editing a discovered entry creates a protected local override; registry-only search and ranking fields are removed before IndexedDB persistence.

Protected chat hand-off

Portable chat packages use PBKDF2-SHA-256 and authenticated AES-256-GCM encryption in the browser. Strict size, message and text limits are enforced before and after decryption. Only complete visible user/assistant text is selected before encryption; credentials, provider/model/usage data, IDs, instructions and attachments are omitted. Imports reject unknown fields, wrong passphrases and expired packages, preview locally, and create fresh IDs only after explicit confirmation.

Portable agent and prompt boundary

Portable library packages reject unknown fields and credential-like keys or values before preview. They cannot carry plugin/MCP assignments, grants, local source references, broad knowledge scope or workspace tags. Foreign-agent workflow routes and unavailable models are detached, every entity receives a fresh ID, and bundled skills start disabled.

Canvas boundary

Edit in Canvas accepts only a non-empty terminal assistant response on the active valid branch, preserves its exact Markdown source and records its chat/message provenance under the shared artifact limits. Creation is idempotent and committed before the editor opens; later saves append immutable versions. The action makes no provider or tool request. Preview uses the same safe Markdown renderer as the transcript, so raw scripts are not executed and remote images are not fetched.

Approved agent-tool boundary

Only assigned, enabled, fully granted and durably saved public-HTTPS plugin commands or reviewed MCP tools with explicit message-reading authority can be proposed by a compatible direct remote model. MCP authority may use public HTTPS or a separately confirmed Trusted LAN HTTPS address; plugin authority remains public-only. Files, Knowledge and skill resources additionally require attachment authority before a tool definition is exposed; raw files are never sent automatically. Link-local/metadata and reserved destinations remain blocked, while a private MCP address must match its saved LAN scope exactly. Calls are strictly sequential and capped at 50 rounds and 50 calls. New unmatched proposals pause in a keyboard-contained approval alert showing the complete endpoint and bounded input; matching Always allow or deny rules are applied automatically. Immediately before networking, the live assignment, data scope, endpoint, permissions and input must still match; MCP additionally reconnects and verifies the live tool schema. Denial, pending authority, blocked saving or any mismatch contacts no external tool endpoint. Stored audit evidence contains the action name, method, bare origin, permissions, safe status and timing—but no arguments, results, headers, credentials, query strings or endpoint paths.

Deliberately unavailable

Private API, site-owned inbound MCP, terminal, browser automation and system-information paths are blocked at the public edge. Explicit outbound hosted-MCP connections remain browser-direct user actions. The public source contains no LAN address, OAuth session, private server-owner provider/MCP credential, Brave Search credential or server-side search relay. It deliberately contains the disclosed OpenRouter free-access key; that account has no paid balance and this app's runtime rejects non-free models and search before networking.

Visitor responsibility

Anyone who chooses a cloud provider or hosted MCP server should use a restricted personal key, review that service’s data policy and set spending limits where available. Use tab-only storage on shared devices, the strict passphrase vault when automatic reuse is inappropriate, and revoke credentials promptly if the browser profile, device or origin may have been compromised.