Skip to content

Reference

Security & privacy

16 min read

OpenEng is local-first by architecture and placeholder-only by rule. This page states what the platform guarantees about your data, your secrets, and your machines, and where those guarantees stop today. Statements about OpenEng AI describe the design until its services are live; the engineering apps' statements describe the shipped engines.

It covers both halves of the platform: the five engineering apps (Terminal, Kubernetes, Data, Git and Chat), whose engines listen only on your machine's loopback address, and OpenEng AI, whose engine opens no port at all and dials out to the OpenEng Broker. How OpenEng works describes both designs in full.

Local by default

Engineering apps. The web app only renders. Its engine, a native binary on your machine, does the work: it runs your shells, talks to your clusters and databases, and reads your repositories. The one exception is Git's CI & CD view, which fetches runs from your forge (GitHub, GitLab, Bitbucket or Gitea) directly in the browser, with a token stored only in that browser (CI & CD). Credentials such as kubeconfigs, database passwords, SSH keys and cloud keys are read by the engine on your machine and never sent to an OpenEng service. On its own, an app engine contacts OpenEng at api.openeng.app, to exchange its pairing key, renew its sign-in and fetch the certificate for engine.openeng.app, and releases.openeng.app, to check for updates (the current macOS builds, which are older releases, still use the release service's former address; the next macOS builds move to releases.openeng.app), and, when an update exists, downloads it from openeng.app. The Terminal engine also downloads an on-device embedding model (BGE-small-en-v1.5, about 130 MB) once from huggingface.co into ~/.openeng/terminal/state/models, for its semantic command suggestions; it sends none of your command history (Suggestions). Everything else an app engine connects to is something you pointed it at. Each engine keeps its files in ~/.openeng/<app>/.

OpenEng AI. Conversations, thread memory and code maps live under ~/.openeng/ on your machine; the agents, configs and other items you author in the Console are stored by the OpenEng API. Chat and coding run on on-device models by default. Your prompts, your threads and the engine's output leave your machine along these paths:

  • The Broker, whenever you work from the Web Console. Your requests (with any attachments), the engine's replies, and the engine data the Web Console shows you (your thread list and history, learned memory, the running config, placeholder names, and loop, warm-up and device status) pass through it. The Broker can read everything except the streamed replies to a turn (when the request carries a usable browser key), and it keeps every envelope it routes for a retention window, 7 days by default (Transport security).
  • The Cloud Planner, if you pick it for the planner role: the planner's requests go through api.openeng.app to Gemini on Vertex AI and are paid from your planning credit (Plans & billing).
  • Destinations you wire, each an explicit entry you author: a hosted or external model, an external coding CLI, an MCP server, a cell, an integration, or an external plugin endpoint, which receives each call it guards (up to the full payload, per its payload policy; Plugins). The integrations catalog's entries point at OpenEng-hosted endpoints under connectors.openeng.app/fn/, so their tool arguments would go to OpenEng (Integrations).
  • Research, which fetches web pages from the sites it researches.
  • Human-in-the-Loop (Pro): each approval request goes to api.openeng.app for the Console queue. It carries the run, thread and config ids, the target and operation, and the rule's message, not your prompt or the engine's output (Human-in-the-Loop).

As of September 25, 2026, the OpenEng AI API and the Broker aren't live yet, so this describes the design (status).

Transport security

Engineering apps: a sealed loopback channel

  • The engine binds 127.0.0.1 only (Terminal 50052, Git 50053, Kubernetes 50054, Chat 50056, Data 50057), so nothing outside your machine can connect to it.
  • The browser reaches it at https://engine.openeng.app:<port>. That public name resolves to 127.0.0.1, and at sign-in the engine fetches a publicly trusted certificate for it, so the connection is HTTPS with no manual trust step. Every signed-in engine receives the same certificate and private key, so TLS here proves the name, not which engine answers; what keeps the connection on your machine is that the name resolves to 127.0.0.1.
  • On top of TLS, each request and response frame of a session is sealed on its own, with a fresh ephemeral key per frame (X25519, HKDF-SHA256, ChaCha20-Poly1305), so it's encrypted on the loopback connection. The engine seals a session only when the client offered a key at CreateSession, a request frame that fails to unseal is passed on as it arrived, and the key exchange that opens a session isn't authenticated (A sealed transport).
  • A handshake token, minted when the engine starts, authorizes every call. The engine writes it to a file only you can read (0600), but it also hands it out at GET /openeng/handshake to its own app's web origin and to any local program that sends no Origin header. On a shared machine, a program run by another user can therefore obtain it and drive your engine.
  • In the current Linux builds of the Terminal, Kubernetes and Data engines, the engine accepts browser requests only from its own app's web origins (its openeng.app host, for example https://terminal.openeng.app, and the app's Firebase default domains <site>.web.app and <site>.firebaseapp.com), and only when they're addressed to its loopback names. That shuts out other web pages and DNS-rebinding tricks (Terminal: origin and Host lockdown). The macOS builds are older releases pending a rebuild and don't enforce this exact-origin allow-list yet, and in the Chat and Git engines (not published yet) the lockdown is still pending.

Older macOS engine builds answer any website

An engine build without the exact-origin allow-list answers every web origin, so any website you visit while it runs can fetch its handshake token and drive it: run your shell, your cluster commands or your queries. On macOS, don't browse untrusted sites while an app engine runs, or run the engine on a Linux machine until the macOS rebuild ships.

OpenEng AI: the engine dials the Broker

Status on September 25, 2026

This describes the design. The OpenEng AI routes of the OpenEng API and the Broker aren't live yet, so no engine can register or connect today (OpenEng AI status). The engineering apps' transport above works as described.

  • The openeng engine opens no network port (its model daemon talks to it over a private Unix socket). It registers with api.openeng.app using your OpenEng key, then keeps an outbound connection open to the Broker. The key itself is never sent to the Broker.
  • Browser to engine: your requests, with any attachments, travel encrypted to the Broker, which opens them and re-seals them to route them to your engine. On this leg the Broker is a trusted part of the path, not a blind relay.
  • Streamed replies: the engine's streamed replies to a turn are sealed end to end to your browser, so the Broker can't read them. That needs a usable browser key on the request; a run without one, such as a headless run, seals its replies to the Broker instead.
  • Engine data: the engine data the Web Console loads through the Broker (your thread list and history, learned memory, the running config, placeholder names, and loop, warm-up and device status) is sealed to the Broker, which opens it to deliver it.
  • Retention: the Broker keeps every envelope it routes (requests with any attachments, replies and control data) for a retention window, 7 days by default, then deletes it by age, whether or not it was delivered.
  • The browser and the engine learn each other's public keys through the Broker, so the end-to-end sealing of streamed replies also relies on the Broker passing those keys on faithfully.
  • Placeholder values you enter in the Web Console go to the OpenEng API over HTTPS and never pass through the Broker (below).

The platform APIs

  • Console ↔ platform API traffic runs over HTTPS.
  • engine ↔ platform traffic (registration, config resolution, plan checks and placeholder values) targets https://api.openeng.app. The AI engine's update check goes to releases.openeng.app and carries your OpenEng key too (Release integrity).
  • The Console refuses to publish a credential over plain http:// to a non-loopback host, and the engine skips such MCP servers and cells at runtime. The API doesn't repeat that check, and the engine doesn't apply it to a plugin's endpoint.
  • Like the rest of OpenEng AI, these API checks describe the design: the OpenEng AI routes of the OpenEng API aren't served as of September 25, 2026 (status).

Secrets: ${ENV} placeholders, end to end

The single most important rule on the platform:

  • The Console stores only placeholder names. Anywhere a credential appears — model credentials, cell keys, MCP auth, storage-point tokens — the value is an environment-variable placeholder like ${PROD_CELL_KEY}. Editors refuse raw values client-side, and the API independently rejects credential-shaped fields holding anything but a placeholder. The Console and the API hold no value for a variable you keep only in the machine's environment. Anyone who controls your account can still run work on your engines, which can read that environment, so protect the account as you would the keys.
  • Values resolve on the node when a config is resolved — first from that machine's environment, then from any value you stored for the config in the Web Console (below); a stored value you change applies at the next config resolution, when a new session starts. Values are sent only to the endpoint the published config names for them (your cell's gateway, your model's API).
  • A missing variable disables the owning capability — the engine reports which variable to set (for example cell "prod-logs" disabled — set PROD_CELL_KEY to enable it); a blank or partial secret is never sent.
  • The engine doesn't upload environment values — it never sends them to the platform or includes them in telemetry, and floor:secret-egress blocks resolved values (8 characters or longer) from model, MCP, cell, search and connector calls. Shell commands your agents run inherit the machine's environment, except OPENENG_KEY, which the engine removes, so confine them with OPENENG_SANDBOX (Environment variables).
  • Values stored for a config are optional and write-only. The Web Console's Placeholder values screen lets you give a placeholder a value instead of exporting it on the machine. The value goes to the OpenEng API over HTTPS and is stored encrypted. A browser can set or clear a value and see which names are set, but can't read a value back; only a caller holding one of your API keys (normally your engine) can fetch it — treat every API key as able to read your stored values. The engine keeps its copy encrypted in ~/.openeng/env-vault.json. A variable set on the machine always wins.

The always-on gateway floor

Every invocation on your machines — model calls, worker actions, MCP/cell invocations, sandbox jobs, search — passes through a policy gateway whose floor is always on and cannot be edited or shadowed (details):

  • floor:secret-egress blocks resolved secret values from leaving toward external destinations; floor:secret-redact redacts resolved secret values (your placeholders' values, 8 characters or longer) from responses. Both match the values the engine resolved; they don't detect other secret-shaped text.
  • floor:authz authorizes every action; floor:envelope sanity-checks every payload; floor:meter meters token and size use on every call (it records, never blocks). Budgets are set with token limits and a config's token caps.

Your own guardrails (injection screens, redaction, groundedness) stack on top — the floor applies to every account on every plan.

Cells and connectors: your cloud, your IAM

A cell runs inside your cloud account under the function's own IAM role / service account. OpenEng never holds your cloud credentials — the engine calls the cell's HTTPS gateway with an API key that you issued in your cloud, resolved on your engine like any other placeholder. The engine sends that key as an Authorization: Bearer token, while the cell deploy guides set the gateway up to check an x-api-key header, so until the two are aligned a cell registered in the Console is likely to be refused with 401 or 403 (Cells). Read/write separation is enforced by type: a write cell is callable only in an execution (goal) context, never in read-only analysis (Cells).

The engineering apps' connectors follow the same model: pure-Python source you deploy in your own cloud behind an API-key gateway, running under the role you give it. A connector is read-only unless a request carries a valid write key, and the function stores only a salted hash of that key, so reading its environment doesn't grant writes (Read-only by default). The app engines keep a connector's API key, bearer token and write key on your machine. Terminal keeps them write-only. Kubernetes and Data return them to the app over the sealed loopback to pre-fill the edit and re-auth dialogs: Kubernetes the API key, bearer token and write key, and Data the API key and bearer token (and a direct connection's DSN).

Accounts, keys & sessions

One account (Google via auth.openeng.app) spans every surface: the five engineering apps, the Console, the Web Console and billing.

  • Engineering app engines start with a one-time pairing key that the signed-in app mints for you (openeng-terminal serve --oauth=<one-time key>). A key works once and expires after about five minutes. The OpenEng API mints at most five unused keys an hour per account, shared by all the apps (a key an engine has used no longer counts), and each app also counts the keys it mints in your browser. The engine exchanges the key with api.openeng.app for its own access token, valid for an hour and renewed with a rotating refresh token. Every new start of an engine needs a fresh key (Pairing).
  • The AI engine authenticates only with an OpenEng key, an API key you issue in the Console. There is no browser sign-in and no device code: the engine never prompts and never opens a browser. The API registers a key only for an account with an active Pro plan, and one key runs one engine at a time (The engine).
  • API keys are shown once at issue; only their SHA-256 hash is stored, and lists show metadata only. Revoking a key stops it from registering an engine or calling the API. An engine already connected keeps its Broker session until that session expires (up to 24 hours), so stop the engine as well. Headless and sandbox machines are API-key-only and plan-limited (Free 0 · Pro 1 · Enterprise unlimited, or a number set on the account; Plans & billing).
  • Per-user isolation. Items you create in the Console carry your user id in their id (<prefix>_usr_<uid>_<suffix>, see IDs). Once the OpenEng AI API is live, it checks on every read and write that an item is yours or shared with you, and filters lists to those items. Sharing is the deliberate exception to isolation: a config you share by email becomes readable by that account (Configs), and a template you make public can be read by everyone (Templates).

Sign out from each surface's menu; signing out ends that surface's session only. It doesn't stop a running engine: stop the engine to end its session, and revoke API keys from the Console.

Sandbox fleet boundaries

  • By design, sandbox nodes boot API-key-only, with no interactive auth in that mode; as of September 25, 2026 no node can register yet. Keys are stored hashed (shown once at issue), bound 1:1 to a node, and revocable. Revoking a key stops it from registering an engine or calling the API; an engine already connected keeps its Broker session until that session expires (up to 24 hours), so stop the engine as well. Scopes are recorded but not enforced yet; every key has full account authority (API keys & sandboxes).
  • In the current engine build, openeng headless checks its key, prints the driver it resolved and exits. It doesn't register a node or run fleet jobs (The engine).

Release integrity

OpenEng publishes a SHA-256 checksum for every engine binary, and the installers and updaters check it.

  • Installers check each binary against the SHA-256 checksum published next to it (the same URL plus .sha256) when they can fetch that checksum and a hash tool (sha256sum or shasum) is installed; they print checksum verified when they did, and delete a download that doesn't match and stop. If the checksum can't be fetched or no hash tool is found, the installer installs the binary without checking it and doesn't warn you. The checksum confirms the download matches the file openeng.app serves, which catches a corrupted or truncated download. It isn't a signature: it comes from the same place as the binary, and the beta engines aren't code-signed. If you don't see checksum verified, check it yourself (Install).
  • App engines (Terminal, Kubernetes, Data) check for an update at every start and every 30 minutes while they run. A release without a valid sha256: checksum is never installed, a download whose checksum doesn't match is rejected, and the new binary must report the exact version the release names before it's swapped in. These updates can't be turned off; if a check fails, the engine keeps running the version it has (Updates).
  • The AI engine checks for an update at start and every 6 hours, and each check sends your OpenEng key to releases.openeng.app. It checks the checksum when it downloads an update and again before it applies it at the next start, and refuses anything that isn't a native executable for your system. Set OPENENG_SELF_UPDATE=0 to turn its updates off, which also stops the checks that carry the key (The engine). As of September 25, 2026 the release feed has no engine release, so there is nothing for it to download yet; run the installer again to update.
  • Release records on releases.openeng.app hold only pointers and checksums; the binaries are served from openeng.app. The release pipeline checks the newest record for each engine against the binary it serves and withholds that engine's feed on a mismatch; older records aren't re-checked (The release feed).
  • The behavioral-roles pack that the AI engine downloads from releases.openeng.app is signed offline with Ed25519 (minisign), and the engine verifies that signature against a public key built into the binary before it trusts the pack's checksum.

Payments

Billing runs on Razorpay's hosted checkout and invoices. The platform stores subscription state and payment metadata only — never card details (Plans & billing). Razorpay's notifications about your subscription are signature-checked (HMAC-SHA256, compared in constant time) before they change your plan.

Reporting a vulnerability

Found a security issue in any part of OpenEng: an app, an engine, the Console, the Web Console, a catalog or one of the websites? Email hello@openeng.app rather than filing a public issue or posting it publicly. Include:

  • what's affected, with the version (openeng-terminal --version prints an app engine's version; the apps show it in the account menu);
  • the steps to reproduce it;
  • what an attacker could do with it.

Please give us the chance to fix it before you disclose it.

What we'd like you to do

  • Treat OPENENG_KEY like a password: one key per machine-job, revoke on retirement.
  • Keep write-capable cells in separate deployments from read cells.
  • Give a connector a write key only where writes are needed, and connect database connectors with a read-only database role (Connectors).
  • Set a token limit — the best guardrail for unattended runs.
  • Use https:// everywhere the platform asks for a URL. The Console checks credentialed endpoints and plugin endpoints, but the API doesn't repeat those checks.
  • Keep your engines current: when an app offers Restart engine for a downloaded update, take it.