Joplin is a notes and to-do app that keeps everything as Markdown, organised into notebooks with tags and full-text search. It is offline first: every note lives on your device, and sync is something you add, through Joplin Cloud, Nextcloud, Dropbox, OneDrive, WebDAV or a folder, with optional end-to-end encryption so the sync target only ever sees ciphertext.
It is the open-source answer to Evernote, and imports Evernote exports with formatting, attachments and metadata intact. There are apps for Windows, macOS, Linux, Android and iOS, a terminal client, a web clipper for Firefox and Chrome, and a plugin system with hundreds of community plugins for things like kanban boards, outlines and templates.
About 56,900 stars and 827 contributors, at v3.7.21 when scanned. Most of the code is AGPL-3.0-or-later; some directories, such as the sync server, carry their own licence. The editor is more practical than pretty, and heavy users with thousands of notes sometimes find the first sync slow.
- Repository: github.com/laurent22/joplin
- Licence: custom (Other)
- Language: TypeScript. Stars: 56.9K. Forks: 6,370. Last push: Oct 25, 2026.
- Scan: safe, Oct 26, 2026, commit 0c9e457
Who it is for
People who want their notes in an open format on their own devices, Evernote refugees, and anyone who needs encrypted notes synced through a service they already use.
Getting started
1. macOS (Homebrew)
brew install --cask joplin2. Windows (winget)
winget install Joplin.Joplin3. Terminal app (any OS with Node.js)
npm install -g joplinPhones get it from the App Store or Google Play; installers for every desktop are on joplinapp.org. Turn on encryption under Synchronisation, Encryption before the first sync, and keep the master password somewhere safe: without it the notes cannot be recovered.
Safety scan
We cloned laurent22/joplin at commit 0c9e457 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.
- All 6 secret hits are test material: RSA keys in packages/lib/services/e2ee/ppk/ppkTestUtils.ts used to test the end-to-end encryption, and a commented-out test key in the mobile app's buildStartupTasks.ts. The 3 very long lines are the same test data and a debug database populator.
- The 3 binaries are JoplinPortable.exe, the launcher for the PortableApps build, and two standard Android Gradle wrapper jars. The two bare-IP URLs are test cases for classifying AI endpoints as local or remote.
- 701 known advisories across 34 lockfiles sounds alarming but is mostly test fixtures: each sample plugin under packages/app-cli/tests/support/plugins has its own old lockfile, and two TinyMCE asset builders add more. None of those ship in the apps. The scanner read only 22 packages from the root yarn.lock, so the apps' main dependency tree was largely not checked.
- The postinstall and prepare hooks only build the project when you install it from source (husky and gulp build). The CI script that uses sudo runs on build machines.
- 10 workflows; two use pull_request_target (PR title check and CLA), neither checks out the pull request. None of the 5 third-party actions is pinned to a commit. Security policy and Renovate present. OpenSSF Scorecard 4.6.
What the scanner counted
| Check | Result |
|---|---|
| Secrets | 6 candidates found and read; see the notes above. |
| Suspicious code | 3 pattern hits found and read; every one is listed under the raw findings. |
| Install-time code | 36 npm lifecycle scripts. 3 installer scripts (one can call sudo) |
| Committed binaries | 3 executable or compiled objects committed; listed under the raw findings. |
| CI workflows | 10 workflows. 2 use pull_request_target, none check out the pull request head. 5 of 5 third-party actions pinned to a tag rather than a commit. |
| Network hosts | 40 distinct hosts referenced from source; most often github.com, api.github.com, codemirror.net, joplinapp.org. 2 URLs to a bare IP address, listed under the raw findings. |
| Known vulnerabilities | 701 advisories across 2,657 pinned packages: 38 critical, 304 high, 264 moderate, 92 low, 3 unrated. Assets/TinyMCE/IconPack/package-lock.json: 462 packages, 69 advisories; Assets/TinyMCE/JoplinLists/yarn.lock: 831 packages, 187 advisories; packages/app-cli/tests/support/plugins/ai_chat/package-lock.json: 172 packages, 35 advisories; packages/app-cli/tests/support/plugins/ai_search/package-lock.json: 172 packages, 35 advisories; packages/app-cli/tests/support/plugins/clipboard/package-lock.json: 519 packages, 74 advisories; packages/app-cli/tests/support/plugins/codemirror5-and-codemirror6/package-lock.json: 854 packages, 188 advisories; packages/app-cli/tests/support/plugins/codemirror6/package-lock.json: 842 packages, 182 advisories; packages/app-cli/tests/support/plugins/codemirror_content_script/package-lock.json: 499 packages, 79 advisories; packages/app-cli/tests/support/plugins/content_script/package-lock.json: 503 packages, 79 advisories; packages/app-cli/tests/support/plugins/dialog/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/editor_context_menu/package-lock.json: 493 packages, 79 advisories; packages/app-cli/tests/support/plugins/events/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/external_assets/package-lock.json: 520 packages, 78 advisories; packages/app-cli/tests/support/plugins/imaging/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/jpl_test/package-lock.json: 490 packages, 79 advisories; packages/app-cli/tests/support/plugins/json_export/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/load_css/package-lock.json: 519 packages, 74 advisories; packages/app-cli/tests/support/plugins/menu/package-lock.json: 189 packages, 43 advisories; packages/app-cli/tests/support/plugins/multi_selection/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/nativeModule/package-lock.json: 520 packages, 78 advisories; packages/app-cli/tests/support/plugins/note_list_renderer/package-lock.json: 762 packages, 176 advisories; packages/app-cli/tests/support/plugins/post_messages/package-lock.json: 509 packages, 77 advisories; packages/app-cli/tests/support/plugins/register_command/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/selected_text/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/settings/package-lock.json: 519 packages, 80 advisories; packages/app-cli/tests/support/plugins/toast/package-lock.json: 491 packages, 79 advisories; packages/app-cli/tests/support/plugins/toc/package-lock.json: 501 packages, 85 advisories; packages/app-cli/tests/support/plugins/user_data/package-lock.json: 519 packages, 69 advisories; packages/app-cli/tests/support/plugins/withExternalModules/package-lock.json: 492 packages, 79 advisories; packages/app-cli/tests/support/plugins/worker/package-lock.json: 232 packages, 55 advisories; packages/app-clipper/popup/package-lock.json: 350 packages, 28 advisories; packages/onenote-converter/Cargo.lock: 109 packages, 8 advisories; packages/plugins/ToggleSidebars/package-lock.json: 520 packages, 78 advisories; yarn.lock: 22 packages, 0 advisories. |
| Project hygiene | Has security policy, automated dependency updates, licence file. Missing CodeQL, contributing guide. |
| OpenSSF Scorecard | 4.6 out of 10, as of Oct 19, 2026. |
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 (6, redacted)
| Where | Rule | Match |
|---|---|---|
| packages/app-mobile/utils/buildStartupTasks.ts:544 | private-key | -----B…--- (31 chars) |
| packages/lib/services/e2ee/ppk/ppkTestUtils.ts:129 | private-key | -----B…--- (31 chars) |
| packages/lib/services/e2ee/ppk/ppkTestUtils.ts:157 | private-key | -----B…--- (31 chars) |
| packages/lib/services/e2ee/ppk/ppkTestUtils.ts:167 | private-key | -----B…--- (31 chars) |
| packages/lib/services/e2ee/ppk/ppkTestUtils.ts:176 | private-key | -----B…--- (31 chars) |
| packages/lib/services/e2ee/ppk/ppkTestUtils.ts:185 | private-key | -----B…--- (31 chars) |
Pattern hits (3)
| Where | Rule | Match |
|---|---|---|
| packages/lib/services/debug/populateDatabase.ts:137 | very-long-line | 9541 chars |
| packages/lib/services/e2ee/ppk/ppkTestUtils.ts:137 | very-long-line | 3225 chars |
| packages/lib/services/e2ee/ppk/ppkTestUtils.ts:145 | very-long-line | 3225 chars |
URLs to bare IP addresses (2)
| Where | Rule | Match |
|---|---|---|
| packages/lib/services/ai/AiService.test.ts:52 | ip-literal-url | ['http://010.0.0.5:11434/v1', 'remote'], |
| packages/lib/services/ai/AiService.test.ts:53 | ip-literal-url | ['http://8.8.8.8:11434/v1', 'remote'], |
npm lifecycle scripts (36)
Assets/TinyMCE/IconPack/package.jsonpostinstall:node postinstall.jspackage.jsonpostinstall:husky && gulp buildpackages/app-cli/tests/support/plugins/ai_chat/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/ai_search/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/clipboard/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/codemirror5-and-codemirror6/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/codemirror6/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/codemirror_content_script/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/content_script/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/dialog/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/editor_context_menu/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/events/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/external_assets/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/imaging/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/jpl_test/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/json_export/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/load_css/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/menu/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/multi_selection/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/nativeModule/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/note_list_renderer/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/post_messages/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/register_command/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/selected_text/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/settings/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/toast/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/toc/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/user_data/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/withExternalModules/package.jsonprepare:npm run distpackages/app-cli/tests/support/plugins/worker/package.jsonprepare:npm run distpackages/app-clipper/popup/package.jsonpostinstall:node postinstall.js && npm run buildpackages/app-mobile/package.jsonpostinstall:jetifypackages/fork-htmlparser2/package.jsonprepare:npm run buildpackages/plugins/ToggleSidebars/package.jsonprepare:npm run distpackages/turndown-plugin-gfm/package.jsonprepare:npm run buildpackages/turndown/package.jsonprepare:npm run build
Installer scripts (3)
- .github/scripts/run_ci.sh, 384 lines, uses sudo; talks to github.com, stackoverflow.com, www.electron.build
- packages/app-desktop/integration-tests/run-ci.sh, 14 lines
- packages/app-desktop/runForTesting.sh, 206 lines; talks to api.joplincloud.local, joplinusercontent.local
Committed binaries (3)
packages/tools/PortableAppsLauncher/JoplinPortable.exe: PE (Windows executable), 165 KBpackages/react-native-saf-x/android/wrapper/gradle-wrapper.jar: JAR, 59 KBpackages/app-mobile/android/gradle/wrapper/gradle-wrapper.jar: JAR, 44 KB
Worst known vulnerabilities (24 of 701)
| Advisory | Severity | Package | Summary |
|---|---|---|---|
| GHSA-xvch-5gv4-984h | critical | minimist@1.2.5 | Prototype Pollution in minimist |
| GHSA-23hp-3jrh-7fpw | critical | tar@4.4.13 | node-tar: Decompression/parse DoS via unlimited input |
| GHSA-crh6-fp67-6883 | critical | xmldom@0.1.27 | xmldom allows multiple root nodes in a DOM |
| GHSA-67hx-6x53-jw92 | critical | babel-traverse@6.26.0 | Babel vulnerable to arbitrary code execution when compiling specifically crafted malicious code |
| GHSA-cpq7-6gpm-g9rc | critical | cipher-base@1.0.4 | cipher-base is missing type checks, leading to hash rewind and passing on crafted data |
| GHSA-vjh7-7g9h-fjfh | critical | elliptic@6.5.2 | Elliptic's private key extraction in ECDSA upon signing a malformed input (e.g. a string) |
| GHSA-6h5x-7c5m-7cr7 | critical | eventsource@1.0.7 | Exposure of Sensitive Information in eventsource |
| GHSA-fjxv-7rqg-78g4 | critical | form-data@2.3.3 | form-data uses unsafe random function in form-data for choosing boundary |
| GHSA-957j-59c2-j692 | critical | getobject@0.1.0 | Prototype pollution in getobject |
| GHSA-896r-f27r-55mw | critical | json-schema@0.2.3 | json-schema is vulnerable to Prototype Pollution |
| GHSA-76p3-8jx3-jpfq | critical | loader-utils@1.4.0 | Prototype pollution in webpack loader-utils |
| GHSA-xvch-5gv4-984h | critical | minimist@0.0.8 | Prototype Pollution in minimist |
| GHSA-h7cp-r72f-jxh6 | critical | pbkdf2@3.0.17 | pbkdf2 returns predictable uninitialized/zero-filled memory for non-normalized or unimplemented algos |
| GHSA-v62p-rq8g-8h59 | critical | pbkdf2@3.0.17 | pbkdf2 silently disregards Uint8Array input, returning static keys |
| GHSA-jqcg-44mw-7w3h | critical | proxy-addr@2.0.6 | proxy-addr vulnerable to IP spoofing via IPv4-mapped IPv6 trust subnet |
| GHSA-95m3-7q98-8xr5 | critical | sha.js@2.4.11 | sha.js is missing type checks leading to hash rewind and passing on crafted data |
| GHSA-hgjh-723h-mx2j | critical | url-parse@1.4.7 | Authorization Bypass Through User-Controlled Key in url-parse |
| GHSA-xv26-6w52-cph6 | critical | websocket-driver@0.7.3 | websocket-driver: Message corruption via abuse of protocol length headers |
| GHSA-23hp-3jrh-7fpw | critical | tar@6.2.1 | node-tar: Decompression/parse DoS via unlimited input |
| GHSA-vjh7-7g9h-fjfh | critical | elliptic@6.5.4 | Elliptic's private key extraction in ECDSA upon signing a malformed input (e.g. a string) |
| GHSA-76p3-8jx3-jpfq | critical | loader-utils@2.0.0 | Prototype pollution in webpack loader-utils |
| GHSA-h7cp-r72f-jxh6 | critical | pbkdf2@3.1.2 | pbkdf2 returns predictable uninitialized/zero-filled memory for non-normalized or unimplemented algos |
| GHSA-v62p-rq8g-8h59 | critical | pbkdf2@3.1.2 | pbkdf2 silently disregards Uint8Array input, returning static keys |
| GHSA-23hp-3jrh-7fpw | critical | tar@6.1.0 | node-tar: Decompression/parse DoS via unlimited input |
Workflows worth a look
- .github/workflows/check-pr-title.yml: pull_request_target
- .github/workflows/cla.yml: pull_request_target
By the numbers
| Stars | 56.9K |
|---|---|
| Forks | 6,370 |
| Contributors | 827 |
| Commits | 16K |
| Open issues | 640 |
| Open pull requests | 9 |
| Releases | 423 |
| Latest release | v3.7.21 |
| Licence | custom |
| Main language | TypeScript |
| Project age | 9 years |
| Last push | Oct 25, 2026 |
| Tracked files | 8,628 |
| Lines of code | 977.7K |
| Checkout size | 262 MB |
Lines by language: JSON 424K, TypeScript 411.2K, JavaScript 60.7K, Markdown 38.2K, Rust 18.7K, CSS 6,858.
Questions
Is Joplin free?
Yes. All the apps are free and open source, mostly under AGPL-3.0-or-later. Joplin Cloud is an optional paid sync service; you can sync free through Nextcloud, Dropbox, OneDrive, WebDAV or a local folder instead.
Is Joplin end-to-end encrypted?
It can be. End-to-end encryption is optional and works with every sync target. Once on, notes are encrypted on your device before upload, and only devices with your master password can read them.
Can I import my Evernote notes into Joplin?
Yes. Export your notebooks from Evernote as ENEX files and import them in Joplin. Formatting is converted to Markdown, and attachments, images and note metadata come across.
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.
