6 min read

Joplin: Private Markdown Notes With Encrypted Sync (GitHub, Scanned)

An offline-first Markdown notes app for desktop and mobile, with end-to-end encrypted sync.

Joplin logo
✅
Scan: safe. Nothing to warn about. The secret hits are test keys for its own encryption, and the many advisories sit in test fixtures and asset build folders. Scanned Oct 26, 2026; the full report is below.

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 joplin

2. Windows (winget)

winget install Joplin.Joplin

3. Terminal app (any OS with Node.js)

npm install -g joplin

Phones 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

CheckResult
Secrets6 candidates found and read; see the notes above.
Suspicious code3 pattern hits found and read; every one is listed under the raw findings.
Install-time code36 npm lifecycle scripts. 3 installer scripts (one can call sudo)
Committed binaries3 executable or compiled objects committed; listed under the raw findings.
CI workflows10 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 hosts40 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 vulnerabilities701 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 hygieneHas security policy, automated dependency updates, licence file. Missing CodeQL, contributing guide.
OpenSSF Scorecard4.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)
WhereRuleMatch
packages/app-mobile/utils/buildStartupTasks.ts:544private-key-----B…--- (31 chars)
packages/lib/services/e2ee/ppk/ppkTestUtils.ts:129private-key-----B…--- (31 chars)
packages/lib/services/e2ee/ppk/ppkTestUtils.ts:157private-key-----B…--- (31 chars)
packages/lib/services/e2ee/ppk/ppkTestUtils.ts:167private-key-----B…--- (31 chars)
packages/lib/services/e2ee/ppk/ppkTestUtils.ts:176private-key-----B…--- (31 chars)
packages/lib/services/e2ee/ppk/ppkTestUtils.ts:185private-key-----B…--- (31 chars)
Pattern hits (3)
WhereRuleMatch
packages/lib/services/debug/populateDatabase.ts:137very-long-line9541 chars
packages/lib/services/e2ee/ppk/ppkTestUtils.ts:137very-long-line3225 chars
packages/lib/services/e2ee/ppk/ppkTestUtils.ts:145very-long-line3225 chars
URLs to bare IP addresses (2)
WhereRuleMatch
packages/lib/services/ai/AiService.test.ts:52ip-literal-url['http://010.0.0.5:11434/v1', 'remote'],
packages/lib/services/ai/AiService.test.ts:53ip-literal-url['http://8.8.8.8:11434/v1', 'remote'],
npm lifecycle scripts (36)
  • Assets/TinyMCE/IconPack/package.json postinstall: node postinstall.js
  • package.json postinstall: husky && gulp build
  • packages/app-cli/tests/support/plugins/ai_chat/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/ai_search/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/clipboard/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/codemirror5-and-codemirror6/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/codemirror6/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/codemirror_content_script/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/content_script/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/dialog/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/editor_context_menu/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/events/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/external_assets/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/imaging/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/jpl_test/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/json_export/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/load_css/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/menu/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/multi_selection/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/nativeModule/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/note_list_renderer/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/post_messages/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/register_command/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/selected_text/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/settings/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/toast/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/toc/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/user_data/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/withExternalModules/package.json prepare: npm run dist
  • packages/app-cli/tests/support/plugins/worker/package.json prepare: npm run dist
  • packages/app-clipper/popup/package.json postinstall: node postinstall.js && npm run build
  • packages/app-mobile/package.json postinstall: jetify
  • packages/fork-htmlparser2/package.json prepare: npm run build
  • packages/plugins/ToggleSidebars/package.json prepare: npm run dist
  • packages/turndown-plugin-gfm/package.json prepare: npm run build
  • packages/turndown/package.json prepare: npm run build
Installer scripts (3)
Committed binaries (3)
  • packages/tools/PortableAppsLauncher/JoplinPortable.exe: PE (Windows executable), 165 KB
  • packages/react-native-saf-x/android/wrapper/gradle-wrapper.jar: JAR, 59 KB
  • packages/app-mobile/android/gradle/wrapper/gradle-wrapper.jar: JAR, 44 KB
Worst known vulnerabilities (24 of 701)
AdvisorySeverityPackageSummary
GHSA-xvch-5gv4-984hcriticalminimist@1.2.5Prototype Pollution in minimist
GHSA-23hp-3jrh-7fpwcriticaltar@4.4.13node-tar: Decompression/parse DoS via unlimited input
GHSA-crh6-fp67-6883criticalxmldom@0.1.27xmldom allows multiple root nodes in a DOM
GHSA-67hx-6x53-jw92criticalbabel-traverse@6.26.0Babel vulnerable to arbitrary code execution when compiling specifically crafted malicious code
GHSA-cpq7-6gpm-g9rccriticalcipher-base@1.0.4cipher-base is missing type checks, leading to hash rewind and passing on crafted data
GHSA-vjh7-7g9h-fjfhcriticalelliptic@6.5.2Elliptic's private key extraction in ECDSA upon signing a malformed input (e.g. a string)
GHSA-6h5x-7c5m-7cr7criticaleventsource@1.0.7Exposure of Sensitive Information in eventsource
GHSA-fjxv-7rqg-78g4criticalform-data@2.3.3form-data uses unsafe random function in form-data for choosing boundary
GHSA-957j-59c2-j692criticalgetobject@0.1.0Prototype pollution in getobject
GHSA-896r-f27r-55mwcriticaljson-schema@0.2.3json-schema is vulnerable to Prototype Pollution
GHSA-76p3-8jx3-jpfqcriticalloader-utils@1.4.0Prototype pollution in webpack loader-utils
GHSA-xvch-5gv4-984hcriticalminimist@0.0.8Prototype Pollution in minimist
GHSA-h7cp-r72f-jxh6criticalpbkdf2@3.0.17pbkdf2 returns predictable uninitialized/zero-filled memory for non-normalized or unimplemented algos
GHSA-v62p-rq8g-8h59criticalpbkdf2@3.0.17pbkdf2 silently disregards Uint8Array input, returning static keys
GHSA-jqcg-44mw-7w3hcriticalproxy-addr@2.0.6proxy-addr vulnerable to IP spoofing via IPv4-mapped IPv6 trust subnet
GHSA-95m3-7q98-8xr5criticalsha.js@2.4.11sha.js is missing type checks leading to hash rewind and passing on crafted data
GHSA-hgjh-723h-mx2jcriticalurl-parse@1.4.7Authorization Bypass Through User-Controlled Key in url-parse
GHSA-xv26-6w52-cph6criticalwebsocket-driver@0.7.3websocket-driver: Message corruption via abuse of protocol length headers
GHSA-23hp-3jrh-7fpwcriticaltar@6.2.1node-tar: Decompression/parse DoS via unlimited input
GHSA-vjh7-7g9h-fjfhcriticalelliptic@6.5.4Elliptic's private key extraction in ECDSA upon signing a malformed input (e.g. a string)
GHSA-76p3-8jx3-jpfqcriticalloader-utils@2.0.0Prototype pollution in webpack loader-utils
GHSA-h7cp-r72f-jxh6criticalpbkdf2@3.1.2pbkdf2 returns predictable uninitialized/zero-filled memory for non-normalized or unimplemented algos
GHSA-v62p-rq8g-8h59criticalpbkdf2@3.1.2pbkdf2 silently disregards Uint8Array input, returning static keys
GHSA-23hp-3jrh-7fpwcriticaltar@6.1.0node-tar: Decompression/parse DoS via unlimited input
Workflows worth a look

By the numbers

Stars56.9K
Forks6,370
Contributors827
Commits16K
Open issues640
Open pull requests9
Releases423
Latest releasev3.7.21
Licencecustom
Main languageTypeScript
Project age9 years
Last pushOct 25, 2026
Tracked files8,628
Lines of code977.7K
Checkout size262 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.