TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
SigNote is an end-to-end encrypted notes app with three security tiers.
Sign-in is passwordless — an emailed one-time code, Google OAuth, or Sign-In with Ethereum (SIWE), with passkeys planned — and higher-security notes are protected by a passphrase-derived key the server never sees. The goal is simple: let users pick how they identify themselves (privacy or convenience) while picking the right privacy level for each note.查看完整 README ↗SigNote is an end-to-end encrypted notes app with three security tiers.
Sign-in is passwordless — an emailed one-time code, Google OAuth, or Sign-In with Ethereum (SIWE), with passkeys planned — and higher-security notes are protected by a passphrase-derived key the server never sees. The goal is simple: let users pick how they identify themselves (privacy or convenience) while picking the right privacy level for each note.
SigNote organizes notes into three tiers:
title remains unencrypted for search and navigation.body is encrypted client-side before leaving the browser.title remains unencrypted.body is encrypted with a unique per-note key (NEK).For both Tier 2 and Tier 3, the server stores ciphertext only, and all encryption and decryption happens client-side using the Web Crypto API. The server never sees plaintext note bodies or any key material capable of decrypting them.
Encryption is unlocked with a user-chosen passphrase. The passphrase itself is never stored. Instead, during setup:
salt is generated client-side.deviceShare.serverShare is generated client-side and stored in Postgres (associated with the wallet address).deviceShare XOR serverShare. The MEK is never stored anywhere.After setup, deviceShare is kept in sessionStorage for the duration of the browser session. This means the MEK can be reconstructed silently on page reload without re-prompting for a passphrase — and is automatically discarded when the tab closes.
To unlock from a new tab or after sessionStorage is cleared, the user re-enters the passphrase. This re-derives deviceShare from the stored salt, fetches serverShare from the server, and reconstructs the MEK.
The MEK is split into two shares specifically to limit what either side alone can compromise:
serverShare but not deviceShare (which is derived from the passphrase and never stored server-side). Without the passphrase, they cannot reconstruct the MEK.sessionStorage gets deviceShare but not serverShare. Without the server, they cannot reconstruct the MEK. (Once sessionStorage is cleared — on tab close or explicit lock — there is nothing to steal.)The MEK itself only ever exists in memory, as a non-extractable CryptoKey object, for the duration of an unlocked session.
A keyCheck payload is stored with the encryption profile. It is an AES-GCM ciphertext of a known constant, encrypted with a key derived from the MEK using HKDF. On unlock, the app attempts to decrypt keyCheck — success means the passphrase was correct, failure means it was wrong. No oracle is needed on the server.
Once the MEK is in memory it is imported as an HKDF base key (non-extractable). All working keys are derived from it using HKDF (SHA-256) with distinct info strings to ensure domain separation:
| Derived key | HKDF info string | Used for |
|---|---|---|
secretBodyKey | signote-v1/secret-body | Encrypting and decrypting all Secrets bodies |
verifyKey | signote-v1/verify | Encrypting the keyCheck payload |
sealWrapKey(id) | signote-v1/seal-wrap/<noteId> | Wrapping the per-note NEK for each Seal |
Because the sealWrapKey derivation includes the note ID in the info string, each note gets a unique wrapping key even though they share the same MEK.
Each Secret body is encrypted with AES-GCM 256-bit using secretBodyKey. A fresh random 12-byte IV is generated per encryption operation. The ciphertext and IV are stored together in Postgres.
Each Seal uses a unique 32-byte Note Encryption Key (NEK):
sealWrapKey(noteId) — also AES-GCM, also with the note ID as AAD.To decrypt, the process is reversed: derive the seal wrapping key → unwrap the NEK → decrypt the body. If the note ID does not match the AAD used during encryption, decryption fails.
This design means that if a single seal's NEK were somehow recovered, it compromises only that note, not all Seals.
All cryptography uses the browser's built-in Web Crypto API (no third-party crypto libraries):
Keys are created as non-extractable CryptoKey objects where possible, preventing JavaScript from reading the raw key bytes.
SigNote supports two authentication methods:
SIWE is ideal for users who prioritize privacy and anonymity.
Google OAuth is ideal for users who prioritize convenience.
Users can link both SIWE and Google to the same account for flexibility. Either method can be used to sign in and access the same encrypted notes.
⚠️ Both methods validate against
NEXTAUTH_URL, which must exactly match the URL you use in the browser during local development and deployment.
| Tier | Searchable fields |
|---|---|
| Notes | Title + content |
| Secrets | Title only |
| Seals | Title only |
Titles are intentionally left unencrypted to enable full-text indexing while keeping bodies private.
bun install
cp .env.local.example .env.local
# Core configuration
NEXTAUTH_URL="http://localhost:5000"
NEXTAUTH_SECRET="replace-with-a-long-random-secret"
DATABASE_URL="postgres://<user>:<password>@<host>:5432/<database>"
# SIWE configuration
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=""
NEXT_PUBLIC_RPC_URL=""
# Google OAuth (optional)
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
# S3 file storage
AWS_S3_BUCKET=""
AWS_S3_REGION=""
AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
AWS_S3_ENDPOINT="" # optional: S3-compatible provider endpoint
| Variable | Description | Required |
|---|---|---|
NEXTAUTH_URL | Exact public app origin (used for both SIWE and OAuth) | Yes |
NEXTAUTH_SECRET | Secret used by NextAuth to sign sessions | Yes |
DATABASE_URL | Postgres connection string | Yes |
PG_POOL_SIZE | Max pooled connections (default 10) | No |
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID | WalletConnect project ID used by RainbowKit/Wagmi | Yes |
NEXT_PUBLIC_RPC_URL | Ethereum RPC endpoint used by the app (for SIWE) | Yes |
GOOGLE_CLIENT_ID | Google OAuth 2.0 Client ID (from Google Cloud Console) | No* |
GOOGLE_CLIENT_SECRET | Google OAuth 2.0 Client Secret (from Google Cloud Console) | No* |
AWS_S3_BUCKET | S3 bucket name for file storage | Yes† |
AWS_S3_REGION | AWS region | Yes† |
AWS_ACCESS_KEY_ID | AWS access key ID | Yes† |
AWS_SECRET_ACCESS_KEY | AWS secret access key | Yes† |
AWS_S3_ENDPOINT | Custom endpoint for S3-compatible providers (e.g. R2) | No |
*Only required if you want to enable Google OAuth sign-in. SIWE works without Google credentials.
†Required to enable file attachments.
The local dev script runs on port
5000, soNEXTAUTH_URLshould behttp://localhost:5000unless you change the port.
bun run dev
Then open http://localhost:5000.
src/
├── app/ # App Router pages and API route handlers
│ ├── api/
│ │ ├── encryption/ # Profile and key material endpoints
│ │ ├── files/ # File upload and retrieval
│ │ ├── notes/ # Tier 1 CRUD
│ │ ├── secrets/ # Tier 2 CRUD
│ │ └── seals/ # Tier 3 CRUD
│ ├── secrets/ # Tier 2 pages
│ └── seals/ # Tier 3 pages
├── components/ # UI and feature components
├── config/ # Auth, wallet, and server configuration
├── contexts/ # EncryptionContext — MEK lifecycle management
├── controllers/ # Application logic over the database layer
├── db/ # Drizzle schema, client, and the shared note-tier repo
├── hooks/ # Client data and mutation hooks
├── lib/
│ └── crypto.ts # All client-side cryptographic operations
└── providers/ # App-wide React providers
serverShare stored in Postgres is useless without the user's passphrase.sessionStorage (which holds deviceShare) is tab-scoped and cleared automatically when the tab closes.sessionStorage entry immediately.As with any cryptography-heavy app, treat this as evolving software and review the implementation carefully before using it for sensitive real-world data.
SigNote is being opened up as a reference project for client-side encryption, note security tiers, and passwordless authentication in a modern Next.js app.
Issues, ideas, and contributions are welcome.
bun install
bun run test:e2e:prepare
bun run test:e2e
The explicit prepare command downloads Playwright Chromium and pinned PostgreSQL
18 binaries for your platform. It disables package install scripts and restores
the PostgreSQL symlinks explicitly. Nothing downloads PostgreSQL during ordinary
bun install or a test run. Re-run prepare after upgrading Playwright or the
PostgreSQL version in tests/setup/postgresRuntime.ts.
Each run starts a fresh temporary PostgreSQL cluster, pushes the Drizzle schema,
and starts the app and mock services. All six local Playwright workers share that
cluster, using separate test accounts. Teardown removes the cluster and stops the
processes, including on setup failure. No Docker, PostgreSQL service, or database
URL is required; incoming database URLs are ignored. Keep the app's test settings
in .env.test as usual.
Supported binary targets: macOS arm64/x64, Linux arm64/x64, and Windows x64.
On Linux CI, bun run test:e2e:prepare --with-deps also installs Chromium's system
libraries. Run the tests as a non-root user, as required by PostgreSQL. Binaries
are cached under .cache/e2e/postgres/; deleting that directory requires running
prepare again. Docker Compose remains available for the development database.