AFFiNE is a knowledge workspace that merges a document editor with an endless whiteboard. Any page can switch between a writing view and an edgeless canvas, where rich text, sticky notes, embedded web pages, multi-view databases, shapes and slides sit side by side. That makes it a single place for notes, wikis, planning boards and presentations, the job that otherwise takes Notion plus Miro.
It earns its place by being local-first. Your workspace lives on your disk and works offline, with real-time sync and collaboration added on top through AFFiNE Cloud or your own server, built on the Yjs CRDT and the team's own BlockSuite editor and OctoBase database. AFFiNE AI can draft, summarize, turn outlines into slides or mind maps and generate prototypes on the canvas.
AFFiNE is made by Toeverything, a Singapore company, and has about 73,000 stars. The licensing is split: the client and most of the code are MIT, while the server code in packages/backend carries a separate licence under which the Community Edition parts are MPL-2.0 and enterprise features need a subscription. Self-hosting the Community Edition is free.
- Repository: github.com/toeverything/AFFiNE
- Licence: custom (Other)
- Language: TypeScript. Stars: 73.2K. Forks: 5,327. Last push: Sep 30, 2026.
- Scan: safe, Sep 29, 2026, commit b71bb7c
Who it is for
Notion users who want their notes on their own disk, visual thinkers who plan on whiteboards, and small teams that want one self-hostable tool for docs and diagrams.
Getting started
1. Desktop app on macOS with Homebrew (Windows and Linux builds are on the releases page)
brew install --cask affine2. Self-host: download the Compose file from the latest release
mkdir affine && cd affine && wget -O docker-compose.yml https://github.com/toeverything/affine/releases/latest/download/docker-compose.yml3. Start the server with its Postgres and Redis, then open it on port 3010
docker compose up -d && open http://localhost:3010The self-hosting guide at docs.affine.pro covers storage paths, configuration and upgrades. AI features need a model provider: on AFFiNE Cloud they are a paid plan, and on a self-hosted server you configure your own provider key. The apps send usage analytics and Sentry error reports by default; turn telemetry off in the app's settings if you prefer.
Safety scan
We cloned toeverything/AFFiNE at commit b71bb7c on Sep 29, 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.
- One secret hit: packages/backend/native/src/entitlement.rs line 378, a P-256 private key named TEST_PRIVATE_KEY inside the file's #[cfg(test)] module, used to sign test licence payloads. It is compiled only into tests and is not a production key. The repository is large: about 1.24 million lines in 10,273 files.
- The 13 pattern hits are long lines: base64 images in AI end-to-end test specs, inline SVG icons and tooltip illustrations, and a test fixture; none decodes and runs code. The binaries are six Yjs document snapshots used as test fixtures and the Android Gradle wrapper. The npm hooks (workspace init, Husky, prisma generate, a template build) run for developers, not users.
- Telemetry: enableTelemetry defaults to true in packages/common/infra. When it is on, the client opts into the usage tracker and enables Sentry; turning it off in settings opts out of both.
- 57 known advisories, 1 critical (22 high). The critical one is piscina 5.2.0, a dependency of the self-hosted server: a prototype-pollution gadget that needs a separate prototype-pollution bug to become code execution. The desktop app's Electron 39.8.10 has four high sandbox-related advisories, the server's nodemailer and @fastify/busboy have several more, and the Rust side has quick-xml denial-of-service issues.
- 11 workflows. One uses pull_request_target, auto-labeler, which checks out the base branch and runs actions/labeler; none of the 33 third-party actions is pinned to a commit. Security policy, Renovate, CodeQL and licence present; the contributing guide lives in docs/ rather than the root.
What the scanner counted
| Check | Result |
|---|---|
| Secrets | 1 candidate found and read; see the notes above. |
| Suspicious code | 13 pattern hits found and read; every one is listed under the raw findings. |
| Install-time code | 3 npm lifecycle scripts. 5 Cargo build scripts. 3 installer scripts |
| Committed binaries | 7 executable or compiled objects committed; listed under the raw findings. |
| CI workflows | 11 workflows. 1 uses pull_request_target, none check out the pull request head. 33 of 33 third-party actions pinned to a tag rather than a commit. |
| Network hosts | 40 distinct hosts referenced from source; most often github.com, affine.pro, app.affine.pro, cdn.affine.pro. No URLs to bare IP addresses. |
| Known vulnerabilities | 57 advisories across 4,195 pinned packages: 1 critical, 22 high, 13 moderate, 4 low, 17 unrated. Cargo.lock: 1,100 packages, 25 advisories; yarn.lock: 3,095 packages, 32 advisories. |
| Project hygiene | Has security policy, automated dependency updates, CodeQL, licence file. Missing 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 |
|---|---|---|
| packages/backend/native/src/entitlement.rs:378 | private-key | -----B…--- (27 chars) |
Pattern hits (13)
npm lifecycle scripts (3)
package.jsonpostinstall:yarn affine init && yarn huskypackages/backend/server/package.jsonpostinstall:prisma generatepackages/frontend/templates/package.jsonpostinstall:yarn build
Installer scripts (3)
- .devcontainer/setup-user.sh, 10 lines
- .render/start.sh, 13 lines
- packages/frontend/apps/ios/setup.sh, 61 lines
Committed binaries (7)
packages/common/reader/__tests__/__fixtures__/test-doc-with-ai-editable.snapshot.bin: .bin, 62 KBpackages/frontend/apps/android/App/gradle/wrapper/gradle-wrapper.jar: JAR, 44 KBpackages/backend/server/src/__tests__/__fixtures__/test-doc.snapshot.bin: .bin, 36 KBpackages/common/reader/__tests__/__fixtures__/test-doc.snapshot.bin: .bin, 36 KBpackages/backend/server/src/__tests__/__fixtures__/test-doc-with-blob.snapshot.bin: .bin, 2 KBpackages/backend/server/src/__tests__/__fixtures__/test-root-doc.snapshot.bin: .bin, 1 KBpackages/common/reader/__tests__/__fixtures__/test-root-doc.snapshot.bin: .bin, 1 KB
Worst known vulnerabilities (24 of 57)
| Advisory | Severity | Package | Summary |
|---|---|---|---|
| GHSA-67c8-pqhq-4rmx | critical | piscina@5.2.0 | piscina: Prototype-pollution gadget in ThreadPool.options allows RCE via execArgv / loadBalancer / env |
| RUSTSEC-2026-0194 | high | quick-xml@0.37.5 | Quadratic run time when checking a start tag for duplicate attribute names |
| RUSTSEC-2026-0195 | high | quick-xml@0.37.5 | Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service |
| RUSTSEC-2026-0194 | high | quick-xml@0.38.4 | Quadratic run time when checking a start tag for duplicate attribute names |
| RUSTSEC-2026-0195 | high | quick-xml@0.38.4 | Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service |
| RUSTSEC-2026-0194 | high | quick-xml@0.40.1 | Quadratic run time when checking a start tag for duplicate attribute names |
| RUSTSEC-2026-0195 | high | quick-xml@0.40.1 | Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service |
| GHSA-x8mw-p69m-v3mx | high | @fastify/busboy@3.1.1 | @fastify/busboy vulnerable to Denial of Service via prototype-named multipart part header |
| GHSA-xjh9-v7x6-24jw | high | @fastify/busboy@3.1.1 | @fastify/busboy vulnerable to Denial of Service via oversized multipart boundary |
| GHSA-m9gg-hp2v-232j | high | @grpc/grpc-js@1.14.4 | @grpc/grpc-js: In certain configurations, getAuthContext can return unauthorized certificates as though they were author… |
| GHSA-vfj7-8cjw-p6xm | high | braces@3.0.3 | braces vulnerable to stack-exhaustion denial of service through deeply nested patterns |
| GHSA-9qh4-3jw8-366w | high | electron@39.8.10 | Electron: <webview> can enable Node.js integration in Web Workers despite embedder restrictions |
| GHSA-gr2m-v5gq-v685 | high | electron@39.8.10 | Electron: Windows opened from a sandboxed top-level document do not inherit its sandbox restrictions |
| GHSA-hq2x-r82h-9wj4 | high | electron@39.8.10 | Electron drops inherited HTML sandbox restrictions for popups opened through OpenURLFromTab |
| GHSA-j84w-jfhq-vhvj | high | electron@39.8.10 | Electron: File and HTTP protocol handlers allow cross-origin reads without corsEnabled |
| GHSA-2gc4-cqfq-p2gv | high | engine.io@6.6.9 | Socket.IO: Engine.IO Protocol Revision Mismatch DoS |
| GHSA-7pqw-9j4j-h8q3 | high | extract-zip@2.0.1 | extract-zip allows arbitrary file writes through symlink archive entries |
| GHSA-jmr9-qjv8-65gv | high | extract-zip@2.0.1 | extract-zip unvalidated symlink path traversal |
| GHSA-ch52-4w7c-c8xp | high | http-cache-semantics@4.2.0 | http-cache-semantics max-stale handling can disclose cross-user cached responses |
| GHSA-w3rx-r6r6-pgpr | high | image-size@0.7.5 | image-size: ICNS parser allows denial of service through an infinite loop |
| GHSA-prgh-xp8r-p3m5 | high | nodemailer@9.1.1 | Nodemailer addressparser: O(n^2) on comment-joined addresses enables a remote DoS (reachable via mailparser) |
| GHSA-v53p-9fqp-m79j | high | nodemailer@9.1.1 | Nodemailer: Quadratic backtracking in the addressparser free-text fallback allows remote denial of service |
| GHSA-rfgv-xxqx-mfg5 | high | undici@6.28.0 | undici vulnerable to Denial of Service via unrequested WebSocket subprotocol |
| GHSA-4grx-2x9w-596c | moderate | rsa@0.9.10 | Marvin Attack: potential key recovery through timing sidechannels |
Workflows worth a look
- .github/workflows/auto-labeler.yml: pull_request_target
By the numbers
| Stars | 73.2K |
|---|---|
| Forks | 5,327 |
| Contributors | 269 |
| Commits | 11.5K |
| Open issues | 649 |
| Open pull requests | 115 |
| Releases | 587 |
| Latest release | v0.27.4 |
| Licence | custom |
| Main language | TypeScript |
| Project age | 4 years |
| Last push | Sep 30, 2026 |
| Tracked files | 10,273 |
| Lines of code | 1.2M |
| Checkout size | 124 MB |
Lines by language: TypeScript 860.1K, JSON 188.4K, Rust 101.5K, Swift 42.4K, Markdown 17.1K, Kotlin 9,227.
Questions
Is AFFiNE free?
The desktop app and the self-hosted Community Edition are free. The client code is MIT; the server code has its own licence, with Community Edition parts under MPL-2.0 and an Enterprise Edition, adding features such as SSO and audit, sold by subscription. AFFiNE Cloud has a free tier and paid plans, and AFFiNE AI on the cloud is a paid add-on.
Does AFFiNE work offline?
Yes. It is local-first: workspaces are stored on your device and work without a connection. Sync and real-time collaboration happen when you connect a workspace to AFFiNE Cloud or to your own self-hosted server.
Can AFFiNE import from Notion?
AFFiNE can import Markdown and HTML, which is how Notion exports a workspace, along with other formats; the import options are in the app's import dialog. Complex Notion databases may need tidying after the move.
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.
