6 min read

Onyx: Self-Hosted AI Chat and Search Over Your Team's Apps (GitHub, Scanned)

Self-hosted AI chat that searches your team's docs, tickets and chats across 50+ connected apps.

Onyx logo
✅
Scan: safe. Nothing malicious. Know that every deployment sends usage and health telemetry to Onyx by default, and that it is built to hold your company's data, so secure the server accordingly. Scanned Oct 26, 2026; the full report is below.

Onyx is a self-hosted AI assistant that knows what is in your company's tools. It connects to more than 50 sources such as Google Drive, Slack, Confluence, Jira, SharePoint and GitHub, indexes them along with their permissions, and answers questions with citations, so people only get answers drawn from documents they could open anyway. On top of search it adds deep research, custom agents, web search, MCP actions, a code sandbox and voice.

It runs as Docker containers or on Kubernetes, works with hosted models (OpenAI, Anthropic, Gemini) or self-hosted ones (Ollama, vLLM, LiteLLM), and can be fully air-gapped. A Lite mode under 1 GB of memory gives you the chat interface and agents without document indexing. People reach it through web and desktop apps, a Slack or Discord bot, an MCP server for coding agents, and a Chrome extension.

The Community Edition is MIT-licensed; folders named ee hold the Enterprise Edition features (SSO, RBAC, analytics and similar) under a commercial licence. About 33,000 stars and 210 contributors, at v5.0.0. The full stack is heavy, with a search index, background workers and model servers, so plan a proper server for it.

Who it is for

Teams that want a ChatGPT-style assistant grounded in their own documents while keeping the data on their own infrastructure, and IT staff weighing self-hosted alternatives to enterprise AI search products.

Getting started

1. Install the Onyx CLI, which then walks you through a Docker deployment

curl -fsSL https://onyx.app/install_onyx.sh | bash

2. Or deploy the lightweight Lite mode without prompts

curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh | bash -s -- --lite --no-prompt

The installer in the repository (deployment/docker_compose/install.sh) is only a bootstrap: it installs onyx-cli to ~/.local/bin (or /usr/local/bin as root), adds that folder to your PATH in your shell profile, and hands over to the CLI. Set DISABLE_TELEMETRY=true on the services if you do not want usage reporting.

Safety scan

We cloned onyx-dot-app/onyx at commit 616a4b1 on Oct 26, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.

  • The 40 secret hits are test fixtures (fake Slack tokens and private-key markers) and the same placeholder text, BEGIN PRIVATE KEY, in nine translation files, where it is the hint shown in an SSO key field. The 14 bare-IP URLs are SSRF tests checking that Onyx refuses them.
  • Fleet telemetry is on by default. Per the project's deployment/fleet-telemetry.md, each deployment reports to telemetry.onyx.app every few minutes under a random deployment key, including query counts and timings, connector settings and counts, and signup email domains. Setting DISABLE_TELEMETRY=true on all services stops it.
  • The six pattern hits are the curl | bash installer and comments about it. The script downloads onyx-cli from the project's GitHub releases, writes a PATH line to your shell profile, and only suggests sudo if it cannot write to the chosen folder.
  • 101 advisories, none critical: the Python backend's uv.lock (55, 21 high, in aiohttp, fsspec and similar), the Tauri desktop app's Cargo.lock, and three Go modules for the CLI and tooling.
  • 52 workflows. One uses pull_request_target, an ownership check for the ee folders, and it does not check out the pull request. All 272 third-party actions are pinned to a commit; Dependabot, CodeQL and a security policy are present.

What the scanner counted

CheckResult
Secrets40 candidates found and read; see the notes above.
Suspicious code6 pattern hits found and read; every one is listed under the raw findings.
Install-time code3 npm lifecycle scripts. 1 Cargo build script. 8 installer scripts (one fetches and runs a remote script; one edits your shell profile; one can call sudo)
Committed binariesNone.
CI workflows52 workflows. 1 uses pull_request_target, none check out the pull request head. 0 of 272 third-party actions pinned to a tag rather than a commit.
Network hosts40 distinct hosts referenced from source; most often github.com, onyx.example, www.googleapis.com, identity.example.com. 14 URLs to a bare IP address, listed under the raw findings.
Known vulnerabilities101 advisories across 1,273 pinned packages: 0 critical, 28 high, 30 moderate, 13 low, 30 unrated. cli/go.mod: 46 packages, 5 advisories; desktop/src-tauri/Cargo.lock: 569 packages, 25 advisories; terraform-provider-onyx/go.mod: 56 packages, 10 advisories; tools/ods/go.mod: 166 packages, 16 advisories; uv.lock: 459 packages, 55 advisories.
Project hygieneHas security policy, automated dependency updates, CodeQL, licence file, contributing guide.
OpenSSF ScorecardNot scored: the project is not in Scorecard's weekly index.

The raw findings

Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.

Secret candidates (40, redacted)
WhereRuleMatch
backend/tests/external_dependency_unit/auth/test_sso_admin_api.py:307private-key-----B…--- (27 chars)
backend/tests/external_dependency_unit/auth/test_sso_admin_api.py:325private-key-----B…--- (27 chars)
backend/tests/external_dependency_unit/craft/test_credential_encryption.py:71slack-tokenxoxb-s…ret (17 chars)
backend/tests/external_dependency_unit/slack_bot/test_slack_bot_crud.py:58slack-tokenxoxb-u…new (15 chars)
backend/tests/external_dependency_unit/slack_bot/test_slack_bot_crud.py:60slack-tokenxoxp-u…new (15 chars)
backend/tests/external_dependency_unit/slack_bot/test_slack_bot_federated_search.py:286slack-tokenxoxb-t…ken (15 chars)
backend/tests/external_dependency_unit/slack_bot/test_slack_bot_federated_search.py:288slack-tokenxoxp-t…ken (20 chars)
backend/tests/external_dependency_unit/slack_bot/test_slack_bot_federated_search.py:730slack-tokenxoxp-t…ken (15 chars)
backend/tests/external_dependency_unit/slack_bot/test_slack_bot_federated_search.py:821slack-tokenxoxp-t…ken (15 chars)
backend/tests/integration/tests/connector/test_credential_family_sharing.py:22private-key-----B…--- (27 chars)
backend/tests/integration/tests/craft/k8s/test_approval_gate.py:162slack-tokenxoxb-f…ken (20 chars)
backend/tests/integration/tests/craft/k8s/test_approval_gate.py:628slack-tokenxoxb-f…ken (20 chars)
backend/tests/integration/tests/craft/k8s/test_approval_gate.py:778slack-tokenxoxb-f…ken (20 chars)
backend/tests/unit/federated_connector/slack/test_slack_federated_connnector.py:17slack-tokenxoxp-1…TAx (19 chars)
backend/tests/unit/onyx/connectors/google_utils/test_google_credential_storage.py:76private-key-----B…--- (27 chars)
backend/tests/unit/onyx/connectors/slack/test_slack_capability_checks.py:92slack-tokenxoxb-t…ken (15 chars)
backend/tests/unit/onyx/connectors/slack/test_slack_check_row_text.py:61slack-tokenxoxb-t…ken (15 chars)
backend/tests/unit/onyx/llm/test_llm_utils_scrub.py:21private-key-----B…--- (27 chars)
backend/tests/unit/onyx/server/documents/test_google_service_account_key_value.py:14private-key-----B…--- (27 chars)
backend/tests/unit/onyx/server/features/craft/external_apps/test_api_user_response.py:89slack-tokenxoxp-r…ken (27 chars)
backend/tests/unit/onyx/server/features/mcp/test_per_user_listing_invariants.py:77slack-tokenxoxp-r…ken (25 chars)
backend/tests/unit/onyx/server/features/mcp/test_per_user_listing_invariants.py:88slack-tokenxoxp-r…ken (25 chars)
backend/tests/unit/onyx/server/features/mcp/test_per_user_listing_invariants.py:185slack-tokenxoxp-r…ken (25 chars)
backend/tests/unit/onyx/server/oauth/test_oauth_binding.py:259slack-tokenxoxp-u…ret (16 chars)
and 16 more
Pattern hits (6)
WhereRuleMatch
cli/cmd/deploy_install.go:80download-piped-to-shell// newInstallOnyxCmd is the documented curl|bash entrypoint: a top-level alias
cli/internal/deploy/install/install.go:1026download-piped-to-shell// "./install.sh --shutdown", which doesn't exist after curl|bash). Interactive
deployment/docker_compose/install.ps1:748download-piped-to-shell$bashCmd = "curl -fsSL '$installUrl' | bash -s -- $($bashArgs -join ' ')"
deployment/docker_compose/install.sh:8download-piped-to-shell# curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh | bash
deployment/docker_compose/install.sh:9download-piped-to-shell# curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh | bash -s -- --lite --no-prompt
deployment/docker_compose/install.sh:385download-piped-to-shell# `curl ... | bash` leaves stdin on the pipe, and the CLI only prompts when
URLs to bare IP addresses (14)
WhereRuleMatch
backend/tests/integration/tests/craft/docker_e2e/networking/test_docker_networking.py:266ip-literal-url"http://1.1.1.1/",
backend/tests/integration/tests/craft/docker_e2e/test_sandbox_network_posture_docker.py:180ip-literal-url"https://1.1.1.1",
backend/tests/integration/tests/craft/k8s/test_kubernetes_sandbox.py:280ip-literal-url"-w '%{http_code}' https://1.1.1.1 || echo BLOCKED:$?"
backend/tests/unit/onyx/server/features/mcp/test_mcp_oauth_discovery.py:234ip-literal-urloauth_discovery.discover_oauth_capabilities("https://8.8.8.8/mcp")
backend/tests/unit/onyx/server/features/mcp/test_mcp_oauth_discovery.py:238ip-literal-urlassert requests == ["https://8.8.8.8/mcp"]
backend/tests/unit/onyx/server/features/mcp/test_mcp_ssrf.py:36ip-literal-url"http://8.8.8.8/mcp",
backend/tests/unit/onyx/server/features/mcp/test_mcp_ssrf.py:37ip-literal-url"https://1.1.1.1/mcp",
backend/tests/unit/onyx/server/features/mcp/test_mcp_ssrf.py:234ip-literal-url"http://8.8.8.8/mcp", "server_url", require_https=False
backend/tests/unit/onyx/utils/test_outbound_targets.py:211ip-literal-urlresult = playwright_fetch.fetch_rendered_html("https://93.184.216.34")
backend/tests/unit/onyx/utils/test_outbound_targets.py:322ip-literal-urlsource = "https://93.184.216.34/document"
backend/tests/unit/onyx/utils/test_url_ssrf.py:277ip-literal-urlassert call_args[0][1] == "https://93.184.216.34/path"
backend/tests/unit/onyx/utils/test_url_ssrf.py:736ip-literal-urlassert prepared.url == "https://93.184.216.34/metadata"
web/src/lib/oauth-provider/api.test.ts:56ip-literal-url"http://126.255.255.255/callback",
web/src/lib/oauth-provider/api.test.ts:57ip-literal-url"http://128.0.0.1/callback",
npm lifecycle scripts (3)
  • mobile/package.json preinstall: cd ../web/lib/shared && bun install && bun run build
  • web/lib/opal/package.json prepare: bun run build
  • web/lib/shared/package.json prepare: bun run build
Installer scripts (8)
Worst known vulnerabilities (24 of 101)
AdvisorySeverityPackageSummary
RUSTSEC-2026-0194highquick-xml@0.38.4Quadratic run time when checking a start tag for duplicate attribute names
RUSTSEC-2026-0195highquick-xml@0.38.4Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service
GHSA-3g6h-r5qx-f6hqhighquinn-proto@0.11.16Peer minimum ACK delay can panic quinn-proto when ACK_FREQUENCY transmission is enabled
GHSA-qfwj-vfxf-92j2highquinn-proto@0.11.16Stream reassembly memory-exhaustion guard in quinn-proto can be bypassed
GHSA-rg2x-37c3-w2rhhighgithub.com/docker/docker@28.5.2+incompatibleDocker: Race condition in docker cp allows bind mount redirection to host path
GHSA-x86f-5xw2-fm2rhighgithub.com/docker/docker@28.5.2+incompatibleDocker: `PUT /containers/{id}/archive` executes container binary on the host
GHSA-259r-337f-4rfwhighgithub.com/klauspost/compress@1.18.6Compress/s2: Integer Overflow or Wraparound and Out-of-bounds Write in compress
GHSA-2g87-pp6c-29x5highaiohttp@3.14.3aiohttp: Multiple instances of excessive or unbounded memory/CPU use
GHSA-5wr8-gcqf-gqf3highaiohttp@3.14.3aiohttp server lacks an initial request/header timeout
GHSA-9xw5-w5jj-pvcghighaiohttp@3.14.3AIOHTTP: File descriptor exhaustion in multipart requests
GHSA-27vj-qcqg-25rchighfsspec@2025.10.0fsspec: Server-Side Template Injection in ReferenceFileSystem leads to Remote Code Execution
GHSA-r77w-qph3-7vf8highgraphql-core@3.2.8GraphQL-core: Denial of service via comment tokens bypassing the parser's max_tokens limit
GHSA-vj8h-fx38-h3vchighgraphql-core@3.2.8GraphQL-core: Denial of service via quadratic complexity in the OverlappingFieldsCanBeMerged validation rule
GHSA-6c7f-9265-g8fwhighpypdf@6.19.0pypdf: Possible long runtimes for repeated references to the same missing object
GHSA-gmh7-3g9r-mjh5highpypdf@6.19.0pypdf: Possible long runtimes for large xref table header limits
GHSA-jr96-gvgh-j3r4highpypdf@6.19.0pypdf: Possible long runtimes for repeated malformed cross-reference entries (Follow-up)
CVE-2026-4538hightorch@2.9.1
GHSA-63cw-57p8-fm3phightorch@2.9.1
GHSA-394h-qwmc-48pfhighvirtualenv@20.36.1virtualenv: Xonsh activation literals depend on the script decoding codec
GHSA-5vjq-rrrf-7h2qhighvirtualenv@20.36.1virtualenv activate runs commands from the environment name in zsh with PROMPT_SUBST
GHSA-6w3r-xr62-4xcphighvirtualenv@20.36.1virtualenv: Bash prompt values can change meaning under older compatibility modes
GHSA-8rjx-v5ww-45pphighvirtualenv@20.36.1virtualenv activation scripts run code from a path that contains a placeholder name
GHSA-94p9-xgh2-xp45highvirtualenv@20.36.1virtualenv: Downloaded seed wheels (pip/setuptools) are not integrity-checked before use
GHSA-c947-3pg5-gm8qhighvirtualenv@20.36.1virtualenv activate.fish runs commands from a path or prompt with a backslash before a quote
Workflows worth a look

By the numbers

Stars33K
Forks4,646
Contributors210
Commits11.2K
Open issues181
Open pull requests387
Releases392
Latest releasev5.0.0
Licencecustom
Main languagePython
Project age3 years
Last pushOct 26, 2026
Tracked files9,508
Lines of code2M
Checkout size93 MB

Lines by language: Python 987.1K, TypeScript 449.3K, JSON 266.5K, Go 104.3K, Markdown 76.6K, YAML 50.2K.

Questions

Is Onyx free?

The Community Edition is free and MIT-licensed, and covers search, chat, agents and actions. The Enterprise Edition adds features such as SSO, role-based access and analytics under a paid licence, and Onyx Cloud is a hosted version if you would rather not run it yourself.

Can Onyx run with a local model?

Yes. It supports self-hosted models through Ollama, vLLM and LiteLLM as well as the big hosted providers, and the embedding model can run locally too, which is what makes a fully air-gapped deployment possible.

What does Onyx need to run?

Docker or Kubernetes. The standard deployment runs several containers, including a search index, background workers and model servers, so give it a real server. Lite mode needs under 1 GB of memory but skips document indexing, so it is a chat interface rather than company search.


This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.