This project is a local development platform. It has not had an independent security audit. Only the latest release receives fixes.
Don't put credentials, exploit details or private data in a public issue. Use Security → Advisories → Report a vulnerability on the repository. If that form isn't available, open an issue that only asks for a private channel. Include the affected version, a minimal reproduction with synthetic data, the expected authorization boundary and the impact you observed.
- Portal administration requires the Keycloak
iceberg-admin/platform-adminrole. Team roles never grant it. - User portal: users are linked by exact Keycloak issuer and subject to a Polaris principal, and Polaris validates their tokens itself. The portal's platform identity and RustFS administration credentials are used only for directory lookups and for database and share actions. Before each of those actions, the portal re-checks the caller's current Administrator role, owning team and environment against Polaris. Catalog browsing, previews and notebooks use the user's own token.
- Previews and governed queries run fixed code in resource-limited child processes that hold only the user's token and can read only the tables they bind. Governed queries (
query_semantic_model,list_dimension_values) build their SQL from the semantic model's expressions, which must pass an allow-list of scalar and aggregate functions; filter values and search text are parameters. Access is checked again before results are returned. - MCP endpoints accept only RS256 Keycloak tokens issued to the public
iceberg-mcpclient. The signature, issuer, audience, expiry and authorized party are verified, and the user is mapped to their Polaris principal on every call. The admin endpoint also requiresplatform-adminon every call. Destructive tools are annotated, anddelete_database,delete_shareanddelete_semantic_modelrequire the exact name.create_userreturns a one-time password, and an externalcreate_shareandrotate_share_credentialreturn a client secret, in the agent's transcript, so treat that transcript as a secret. Theiceberg-mcpredirects allow anylocalhostor127.0.0.1port and path, but no other host. - Tools on the user's computer sign in through the public
iceberg-cliclient with the device authorization grant and call Polaris and RustFS directly. Only Polaris grants apply on that path; the portal's issuer and subject check does not. Access tokens last one hour. The offline refresh token stays on the user's machine for up to 30 days of inactivity. - Data shares hold one read grant per selected table, view or semantic model and nothing else: no listing, no writes, no other database. Vended storage credentials are read-only and limited to the shared table's location. Only a current Administrator of the owning team can manage a share, and portal administrators can only revoke. The secret is shown once and never stored, except in the transcript of an agent that created or rotated it. A shared view is not a row or column filter, because the recipient reads its underlying tables in full. Revocation and expiry end Polaris tokens at once, but storage credentials that were already vended remain valid until they expire. The user portal enforces expiry every 30 seconds and whenever shares are listed. Team shares grant recipients read access only, never administration.
- Extensions (see extensions) are trusted, separately released services such as Conversational BI. The Bridge accepts only Keycloak tokens for the
iceberg-bridgeaudience, issued to a registered extension's own clients. Polaris refuses those tokens, so a user token held by an extension cannot touch data. A user call is re-checked like a portal session (exact issuer and subject link, current team role). Only a team Administrator can enable an extension for one environment; its automation principal then has the team's writer access to that environment's databases and a second, read-only role. Team shares that the team received in that environment reach the read-only role too, so an extension the recipient team enabled can read what every member of that team can read, and must narrow access per user with/me. The extension never receives the principal's secret or storage keys, only Polaris tokens valid for one hour and vended storage credentials. Platform and team administrators can revoke an automation principal at once; tokens already issued stay valid until they expire. - Conversational BI (
extensions/conversationalbi) only reads. It narrows the recipient team's automation principal to each person with/meon every question and every result it serves, and a shared model counts only when the share includes the model and every table it reads. The LLM never writes SQL: queries are compiled from the semantic model, whose expressions pass an allowlist, and run in a disposable DuckDB process that has one read token, no local files and a locked configuration. The configured LLM provider receives the conversation, model names and descriptions, the compiled SQL and at mostBI_LLM_ROWS(default 20) rows per result; to keep data on your hardware, pointOPENAI_BASE_URLat a local OpenAI-compatible server. Model owners' guidance reaches the LLM as quoted data. The CopilotKit runtime runs on an internal network without credentials and gets conversations only with a short-lived run ticket that the gateway mints per request. - Docker socket: the user portal and the internal monitoring collector mount it, which gives them host-level capability, even with a read-only mount. Run them on a dedicated, trusted development host. Container isolation here is not a boundary against hostile tenants.
- Notebooks receive only their user's data credentials and their owner's current access token, never refresh tokens. They run without root or Linux capabilities, on individual networks with outbound internet access. Team members share writable notebook files, and a notebook runs with the credentials of whoever runs it, so members must trust each other's code.
- Sessions stay in server memory, so each portal runs as one replica. Issued tokens and vended storage credentials stay valid until they expire. See deployment boundaries.
- The active team and environment only filter the UI. A user's credentials stay valid for all of their teams.
Default ports bind to localhost. For anything beyond a trusted local network:
- Add HTTPS and secure cookies.
- Make reverse proxies preserve the Host header and support WebSockets.
- Set
FORWARDED_ALLOW_IPSto the proxy's address so sign-in limits apply per client. Never use*on a directly reachable portal. - Never expose the Docker socket, the Polaris management API or the storage administration endpoints.
- For external data shares or tools on other computers, put TLS in front of Polaris
/api/catalogand the RustFS S3 API only. Then setPOLARIS_PUBLIC_URLandS3_ENDPOINT. The quick-share skill's gateway does exactly this: it passes only/api/catalogto Polaris and only AWS-signed requests outside/rustfsand/minioto RustFS, so its administration API stays internal.
Keep .env, data volumes and team notebooks private. Database deletion is irreversible, so routine upgrades and tests must preserve volumes.