Memos is a note app for the small stuff: a passing thought, a link, a quote, a to-do. Each memo can be a single sentence or a longer Markdown entry, and it stays filed under the day you wrote it, so your notes build up into a timeline you can scroll back through. Tags connect related memos across days. It feels closer to a private social feed than a document editor.
It runs on your own server, so your notes stay in one folder you control. You decide what stays private, what to share with family or friends and what to publish on the web, and shared spaces let several people write side by side. There is a web clipper for Chrome and Firefox, a Telegram bot, an API and an MCP server for connecting AI tools, plus optional speech-to-text that works with an OpenAI or Gemini key you add.
Memos is MIT-licensed and written in Go, with about 64,000 stars and 418 contributors since 2021. Version 26.11.0 came out on 1 November 2026.
- Repository: github.com/usememos/memos
- Licence: MIT (MIT License)
- Language: Go. Stars: 64.4K. Forks: 4,946. Last push: Nov 9, 2026.
- Scan: safe, Nov 10, 2026, commit e4f3fb2
Who it is for
People who run a home server or a small VPS and want a private place for quick notes, families who want a shared notebook, and anyone who likes the feel of a timeline but not handing their thoughts to a social network.
Getting started
1. Try the public demo first
https://demo.usememos.com2. Run it with Docker, keeping all data in ~/.memos
docker run -d --name memos -p 5230:5230 -v ~/.memos:/var/opt/memos neosmemo/memos:stable3. Open it and create the first account
http://localhost:5230Everything Memos keeps is in that one folder, so back it up. If you open it to the internet, put it behind HTTPS. There is also a curl | sh installer for a plain binary; it uses sudo, so read it first or stick with Docker.
Safety scan
We cloned usememos/memos at commit e4f3fb2 on Nov 10, 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.
- No secret hits and no committed binaries across 1,356 files and about 321,000 lines. Of the 9 pattern hits, 2 are usage comments in scripts/install.sh showing its curl | sh command, and 7 are long lines in machine-generated protobuf files under web/src/types/proto.
- scripts/install.sh is about 300 lines. It downloads a release from GitHub and uses sudo to install the binary and a system service. The Docker image is the documented route and needs none of that.
- 62 advisories, none critical. The Go server's lockfile has just 1 low and 4 unrated. All 20 high ones are in web/pnpm-lock.yaml, the web interface: 10 in undici, plus browserslist, nanoid, PostCSS, Vite and source-map-js, which are build tools. The 2 in React Router concern its server-rendering mode and route matching in the browser; Memos uses it as a plain single-page app.
- No analytics service among the hosts. The AI hosts, api.openai.com and generativelanguage.googleapis.com, are defaults for the optional speech-to-text, which stays off until an admin adds a provider and key.
- 8 workflows, none using pull_request_target. None of the 22 third-party actions is pinned to a commit. There is a security policy; no Dependabot or CodeQL. The README opens with a featured sponsor, CodeRabbit, an AI code review service, which has nothing to do with using Memos.
What the scanner counted
| Check | Result |
|---|---|
| Secrets | None found. |
| Suspicious code | 9 pattern hits found and read; every one is listed under the raw findings. |
| Install-time code | 1 installer script (one fetches and runs a remote script; one can call sudo) |
| Committed binaries | None. |
| CI workflows | 8 workflows. None use pull_request_target. 22 of 22 third-party actions pinned to a tag rather than a commit. |
| Network hosts | 40 distinct hosts referenced from source; most often github.com, s3.example.com, usememos.com, idp.example.com. 24 URLs to a bare IP address, listed under the raw findings. |
| Known vulnerabilities | 62 advisories across 458 pinned packages: 0 critical, 20 high, 28 moderate, 10 low, 4 unrated. go.mod: 144 packages, 5 advisories; web/pnpm-lock.yaml: 314 packages, 57 advisories. |
| Project hygiene | Has security policy, licence file. Missing automated dependency updates, 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.
Pattern hits (9)
| Where | Rule | Match |
|---|---|---|
| scripts/install.sh:27 | download-piped-to-shell | curl -fsSL https://raw.githubusercontent.com/usememos/memos/main/scripts/install.sh | sh |
| scripts/install.sh:28 | download-piped-to-shell | curl -fsSL https://raw.githubusercontent.com/usememos/memos/main/scripts/install.sh | sh -s -- --version 26.10.0 |
| web/src/types/proto/api/attachment_service_pb.ts:19 | very-long-line | 5564 chars |
| web/src/types/proto/api/idp_service_pb.ts:19 | very-long-line | 3233 chars |
| web/src/types/proto/api/instance_service_pb.ts:23 | very-long-line | 9035 chars |
| web/src/types/proto/api/memo_service_pb.ts:23 | very-long-line | 9926 chars |
| web/src/types/proto/api/space_service_pb.ts:19 | very-long-line | 7088 chars |
| web/src/types/proto/api/user_service_pb.ts:25 | very-long-line | 20524 chars |
| web/src/types/proto/google/api/client_pb.ts:31 | very-long-line | 5087 chars |
URLs to bare IP addresses (24)
| Where | Rule | Match |
|---|---|---|
| internal/linkmeta/html_meta_test.go:181 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/post") |
| internal/linkmeta/html_meta_test.go:186 | ip-literal-url | Image: "http://93.184.216.34/oembed.png", |
| internal/linkmeta/html_meta_test.go:221 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/post") |
| internal/linkmeta/html_meta_test.go:240 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/spa") |
| internal/linkmeta/html_meta_test.go:255 | ip-literal-url | _, err := fetcher.Get(context.Background(), "http://93.184.216.34/page") |
| internal/linkmeta/html_meta_test.go:267 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/original") |
| internal/linkmeta/html_meta_test.go:287 | ip-literal-url | _, err := fetcher.Get(context.Background(), "http://93.184.216.34/page") |
| internal/linkmeta/html_meta_test.go:302 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/page") |
| internal/linkmeta/html_meta_test.go:318 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/page") |
| internal/linkmeta/html_meta_test.go:321 | ip-literal-url | require.Equal(t, "http://93.184.216.34/early.png", meta.Image) |
| internal/linkmeta/html_meta_test.go:340 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/post") |
| internal/linkmeta/html_meta_test.go:343 | ip-literal-url | require.Equal(t, "http://93.184.216.34/late.png", meta.Image) |
| internal/linkmeta/html_meta_test.go:358 | ip-literal-url | _, err := fetcher.Get(ctx, "http://93.184.216.34/page") |
| internal/linkmeta/html_meta_test.go:375 | ip-literal-url | first, err := successFetcher.Get(context.Background(), "HTTP://93.184.216.34:80/page#one") |
| internal/linkmeta/html_meta_test.go:378 | ip-literal-url | second, err := successFetcher.Get(context.Background(), "http://93.184.216.34/page#two") |
| internal/linkmeta/html_meta_test.go:384 | ip-literal-url | _, err = successFetcher.Get(context.Background(), "http://93.184.216.34/page") |
| internal/linkmeta/html_meta_test.go:395 | ip-literal-url | _, err := failureFetcher.Get(context.Background(), "http://93.184.216.34/failure") |
| internal/linkmeta/html_meta_test.go:400 | ip-literal-url | _, err = failureFetcher.Get(context.Background(), "http://93.184.216.34/failure") |
| internal/linkmeta/html_meta_test.go:436 | ip-literal-url | meta, err := fetcher.Get(context.Background(), "http://93.184.216.34/shared") |
| internal/linkmeta/html_meta_test.go:476 | ip-literal-url | _, _ = fetcher.Get(context.Background(), "http://93.184.216.34/page?id="+string(rune('a'+index))) |
| internal/webhook/webhook_test.go:47 | ip-literal-url | require.Error(t, ValidateURL("http://100.100.100.200/hook")) |
| internal/webhook/webhook_test.go:49 | ip-literal-url | require.NoError(t, ValidateURL("http://100.100.100.200/hook")) |
| internal/webhook/webhook_test.go:50 | ip-literal-url | require.Error(t, ValidateURL("http://100.100.100.201/hook")) |
| internal/webhook/webhook_test.go:340 | ip-literal-url | require.Error(t, ValidateURL("http://100.100.100.200/hook")) |
Installer scripts (1)
- scripts/install.sh, 300 lines, uses sudo, fetches and runs a remote script; talks to api.github.com, github.com, raw.githubusercontent.com
Worst known vulnerabilities (24 of 62)
| Advisory | Severity | Package | Summary |
|---|---|---|---|
| GHSA-73wf-gq98-2v4g | high | browserslist@4.28.2 | Browserslist: Uncaught crash / prototype write via untrusted browserslist-stats.json custom stats (normalizeStats) |
| GHSA-c83g-rgw3-j3cx | high | browserslist@4.28.2 | Browserslist: Unbounded memory growth (no cache eviction) via distinct query results, leading to eventual OOM |
| GHSA-28wg-ghj8-5hjv | high | nanoid@3.3.11 | nanoid: non-secure generators can loop indefinitely with negative size |
| GHSA-2v37-7h3g-55p8 | high | nanoid@3.3.11 | nanoid: custom generators can loop indefinitely when size is zero |
| GHSA-xwg4-73v4-xw9w | high | nanoid@3.3.11 | nanoid: Integer Overflow or Wraparound |
| GHSA-r28c-9q8g-f849 | high | postcss@8.5.14 | PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure |
| GHSA-chx6-hx7r-mcp5 | high | react-router@7.15.0 | React Router: Unauthenticated Denial of Service via Inefficient Route Matching |
| GHSA-qwww-vcr4-c8h2 | high | react-router@7.15.0 | React Router: RSC Mode CSRF Bypass Allows Action Execution Before 400 Response |
| GHSA-68fv-2mgg-jv7q | high | source-map-js@1.2.1 | source-map-js allows event-loop denial of service through indexed source-map section offsets |
| GHSA-2m5v-pch6-74hx | high | undici@7.25.0 | undici vulnerable to TLS certificate validation bypass via dropped tls options in Agent |
| GHSA-4cwx-7wf7-3272 | high | undici@7.25.0 | undici vulnerable to cross-user information disclosure and parse-time crash via degenerate private cache directives |
| GHSA-4w7w-5xrc-9h26 | high | undici@7.25.0 | undici vulnerable to cache poisoning and cross-user disclosure via cross-origin redirects |
| GHSA-hm92-r4w5-c3mj | high | undici@7.25.0 | undici vulnerable to cross-origin request routing via SOCKS5 proxy pool reuse |
| GHSA-qrxm-fqhp-jwvm | high | undici@7.25.0 | undici vulnerable to Denial of Service via TLS 1.2 certificate-changing renegotiation |
| GHSA-rfgv-xxqx-mfg5 | high | undici@7.25.0 | undici vulnerable to Denial of Service via unrequested WebSocket subprotocol |
| GHSA-vmh5-mc38-953g | high | undici@7.25.0 | undici vulnerable to TLS certificate validation bypass via dropped requestTls in SOCKS5 ProxyAgent |
| GHSA-vxpw-j846-p89q | high | undici@7.25.0 | undici WebSocket client vulnerable to denial of service via fragment count bypass |
| GHSA-w293-vg96-wgc3 | high | undici@7.25.0 | undici vulnerable to TLS certificate validation bypass via dropped connect options in BalancedPool |
| GHSA-xjj6-f33q-929c | high | undici@7.25.0 | undici vulnerable to Denial of Service via unhandled error on excessive content-encodings |
| GHSA-587h-7wmj-h5pm | high | vite@8.0.11 | launch-editor: `fs.existsSync()` existence check can be bypassed on Windows |
| GHSA-82fw-gwwq-j7x9 | moderate | @vitest/mocker@4.1.5 | Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock |
| GHSA-qxg6-89mg-rqv8 | moderate | @vitest/mocker@4.1.5 | Vitest: Code injection via unauthenticated manual mock registration |
| GHSA-w5vr-8v7q-w6rv | moderate | baseline-browser-mapping@2.10.23 | baseline-browser-mapping process termination on invalid input causes denial of service |
| GHSA-fxqj-rqcc-2cmp | moderate | postcss@8.5.14 | PostCSS: incomplete fix of GHSA-6g55-p6wh-862q - attacker-controlled sourceMappingURL reads arbitrary .map files when `f… |
By the numbers
| Stars | 64.4K |
|---|---|
| Forks | 4,946 |
| Contributors | 418 |
| Commits | 4,935 |
| Open issues | 26 |
| Open pull requests | 36 |
| Releases | 103 |
| Latest release | 26.11.0 |
| Licence | MIT |
| Main language | Go |
| Project age | 4 years |
| Last push | Nov 9, 2026 |
| Tracked files | 1,356 |
| Lines of code | 320.9K |
| Checkout size | 14 MB |
Lines by language: Go 155.5K, TypeScript 79.8K, JSON 66.8K, YAML 7,166, Protobuf 4,953, Markdown 4,508.
Questions
Is Memos free?
Yes. Memos is free and open source under the MIT licence. You pay only for the server you run it on, if any.
Do I need a server to use Memos?
You need somewhere to run it, such as a home server, a NAS that supports Docker, a small cloud VPS or your own computer. The README covers self-hosting and offers a public demo to try first.
Can I use Memos on my phone?
Yes, through your phone's browser pointed at your server. The web clipper and Telegram bot are other ways to send notes in.
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.
