Skip to content

Commit 9706d8f

Browse files
committed
2.7.1 — remove SRI hashes (break file:// loading in Electron)
1 parent e0a11bd commit 9706d8f

5 files changed

Lines changed: 128 additions & 122 deletions

File tree

README.md

Lines changed: 115 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,70 @@
11
# NoteForge
22

3-
**Encrypted, offline note-taking.** A three-panel, OneNote-style app that keeps your data local and protected with AES-256-GCM encryption.
3+
**Encrypted, offline note-taking.** A OneNote-style app that keeps your data local and protected with AES-256-GCM encryption.
44

55
## Features
66

7-
- **3-panel layout** — Notebooks → Sections → Pages, just like OneNote.
8-
- **AES-256-GCM encryption** — Master password encrypts all data at rest, with scrypt key derivation (N=65536, r=8, p=1).
9-
- **Per-notebook locks** — Individual password for sensitive notebooks, on top of the master.
10-
- **Fully offline** — All fonts, scripts, and dependencies are bundled in `lib/`. The only network request is an optional update check against GitHub Releases on launch.
11-
- **Rich text editor** — Bold, italic, headings, lists, tables, code blocks, links, images, checklists.
12-
- **Find & Replace** — Safe text-node walking that won't break HTML.
13-
- **Auto-lock** — Configurable idle timeout (5 / 15 / 30 / 60 min) plus `Ctrl+L` manual lock.
14-
- **Encrypted backup** — Export and restore `.enc` backup files.
15-
- **Auto-update** — Checks GitHub Releases on launch and prompts to download and install when a new version is available. Can be disabled in settings.
16-
- **Dark & light themes** — Persisted across sessions.
17-
- **Export** — HTML and plain text, with warnings before writing unencrypted output.
18-
19-
## Screenshot
20-
21-
![NoteForge](docs/screenshots/main-window.png)
7+
- **3-panel layout** — Notebooks → Sections → Pages, just like OneNote
8+
- **AES-256-GCM encryption** — Master password encrypts all data at rest with scrypt key derivation (N=65536)
9+
- **Per-notebook locks** — Individual password for sensitive notebooks, with per-notebook brute-force rate limiting
10+
- **Fully offline** — All fonts, scripts, and dependencies bundled locally. The only network request is an optional update check against GitHub Releases on launch
11+
- **Rich text editor** — Bold, italic, headings, lists, tables, code blocks, links, images, checklists
12+
- **Image auto-downscale** — Pasted photos are automatically resized to 1600px and JPEG-compressed
13+
- **Find & Replace** — Safe text-node walking that won't break HTML
14+
- **Auto-lock** — Configurable idle timeout (5/15/30/60 min) + Ctrl+L manual lock + per-notebook "Re-lock Now"
15+
- **Encrypted backup** — Export/restore `.enc` backup files with password-verified-before-clobber restore
16+
- **Auto-update** — Checks GitHub Releases on launch, downloads and installs updates seamlessly
17+
- **Dark & light themes** — Persisted across sessions, applied to all dialogs
18+
- **Export** — HTML and plain text with unencrypted file warnings
19+
- **Keyboard shortcuts help** — Press F1 for a list of every shortcut
20+
- **Sandboxed renderer** — Chromium OS-level process sandbox enabled by default
21+
- **Content Security Policy**`connect-src 'none'`, `script-src 'self'` — no eval, no outbound connections
22+
- **DOMPurify + hardening hook** — All note content sanitized on load and paste; `<input>` types restricted to `checkbox` only to block in-note phishing
23+
24+
## What's new in 2.7.1
25+
26+
- **Fix blank window on launch** — 2.7.0 added Subresource Integrity (SRI) hashes to the bundled React/DOMPurify scripts. SRI requires CORS, which Electron's `file://` loader does not support, so all three scripts were blocked silently and the app loaded to a blank window. SRI has been removed; integrity of bundled files is guaranteed by the signed installer instead.
27+
28+
## What's new in 2.7.0
29+
30+
- **Sandbox** — Renderer now runs in Chromium's OS-level process sandbox by default. Can be disabled per-user in `noteforge-config.json` if it causes issues on a specific system.
31+
- **Phishing-input hardening** — DOMPurify hook strips `<input type="password">` (and every other non-checkbox input type) so malicious content can't impersonate a password prompt inside a note.
32+
- **Safe backup restore** — Restore now test-decrypts the backup with your password *before* touching current data. Failed restores leave everything intact. A rollback copy is kept on successful restores.
33+
- **Per-notebook rate limiting** — Wrong-password counter now tracks each notebook independently (keyed by the blob's salt+iv hash), so one mistyped password doesn't burn the lockout quota for every other notebook.
34+
- **Custom modal dialogs** — Native `alert()`/`confirm()`/`prompt()` replaced with themed modals. Password dialogs now honor light/dark theme.
35+
- **Paste any photo** — Images up to 5 MB auto-downscale to 1600px JPEG instead of being rejected.
36+
- **Idle timer improvements** — Now resets on `mousemove`/`wheel` too (throttled to 1 Hz). No more auto-locking mid-read.
37+
- **Re-lock Now** — Right-click a notebook you've unlocked this session to relock it without closing the app.
38+
- **Empty Trash** — Bulk-purge all soft-deleted pages from the trash view.
39+
- **Cursor-aware toolbar** — Heading and font-size selectors now reflect the format under your cursor.
40+
- **F1 shortcuts overlay** — Built-in keyboard shortcut cheat sheet.
41+
- **Schema version stamp** — Every saved file now carries a `version` field for safe future migrations.
42+
- **Graceful auto-updater failures** — If `electron-updater` fails to load (corrupt install, symlink weirdness), the app still starts. Update errors now log instead of silently vanishing.
43+
- **Automated test suite**`npm test` runs 89+ tests covering crypto round-trips, KDF-downgrade protection, XSS vector blocking, IPC channel coverage, and the input-type hook. CI-ready.
2244

2345
## Install
2446

2547
### Download (Windows)
2648

27-
Grab the latest installer from [Releases](../../releases):
49+
Download the latest installer from [Releases](../../releases):
2850

29-
- **`NoteForge Setup x.x.x.exe`** — Standard Windows installer (recommended).
30-
- **`NoteForge-x.x.x-portable.exe`** — Portable, no install needed.
51+
- **`NoteForge Setup x.x.x.exe`** — Standard Windows installer (recommended)
52+
- **`NoteForge-x.x.x-portable.exe`** — Portable version, no install needed
3153

32-
Releases are built automatically by GitHub Actions — no manual build step on my end.
54+
Releases are built automatically by GitHub Actions — no manual build steps required.
3355

34-
> **Note:** Windows will show a SmartScreen warning because the app isn't code-signed yet. Click **"More info"****"Run anyway"** to proceed. The source is fully open for inspection.
56+
> **Note:** Windows may show a SmartScreen warning because the app isn't code-signed yet. Click **"More info"****"Run anyway"** to proceed. The source code is fully open for inspection.
3557
36-
### Build from source
58+
### Build from Source
3759

38-
Requires [Node.js](https://nodejs.org/) 20 LTS or 22 LTS. Node 22 is recommended (Electron's `@electron/*` packages are moving to Node 22 as the new minimum).
60+
Requires [Node.js](https://nodejs.org/) 18+.
3961

4062
```bash
4163
git clone https://github.com/jamesccupps/NoteForge.git
4264
cd NoteForge
4365
npm install
4466
npm run build:jsx
67+
npm test # runs the full test suite
4568
npm start
4669
```
4770

@@ -51,28 +74,25 @@ To build the installer locally (or use `Build.bat` on Windows):
5174
npm run dist
5275
```
5376

54-
Output goes to `dist/`.
77+
Output goes to `dist/`. The `predist` hook runs tests before building — if any test fails, the build aborts.
5578

5679
## Security
5780

58-
This app exists specifically to keep notes private, so it's worth being specific about how.
59-
6081
### Encryption
6182

62-
| Layer | Algorithm | Key derivation |
83+
| Layer | Algorithm | Key Derivation |
6384
|---|---|---|
6485
| Master (file-level) | AES-256-GCM | scrypt N=65536, r=8, p=1 |
6586
| Notebook locks | AES-256-GCM | scrypt N=65536, r=8, p=1 |
6687

67-
- Master password is **never stored** — only the derived key (`Buffer`) lives in memory during the session.
68-
- Notebook passwords are **never held in the renderer** — after unlock, the renderer only has an opaque 128-bit handle to a session key that lives in the main process.
69-
- Session keys are zeroed with `Buffer.fill(0)` on lock, close, and idle timeout.
70-
- Locked notebook sections are **stripped from disk on every write** via `sanitizeForDiskSync()` in the renderer, with a second-layer `sanitizeDataJson()` safety net in the main process. Plaintext never reaches the data file even if the renderer is compromised.
71-
- **KDF downgrade protection** — decrypt rejects blobs with `N < 16384`, non-power-of-2 `N`, or malformed fields, so an attacker who can write the data file can't weaken the encryption header and then have you type your password into a weakened blob.
72-
- Rate limiting with exponential backoff on failed password attempts, persisted across restarts, with separate counters for master unlock and notebook unlock.
73-
- Password strength enforcement: minimum 10 characters, at least 3 of 4 character classes (upper / lower / digit / symbol), dictionary check against 160 common passwords, and a low-entropy check that rejects passwords with fewer than 5 unique characters.
88+
- Master password is **never stored** — only the derived key (Buffer) lives in memory during the session
89+
- Session key is zeroed (`Buffer.fill(0)`) on lock, close, and idle timeout
90+
- Locked notebook sections are **stripped from disk on every write** via `sanitizeForDiskSync()` — plaintext never reaches the data file
91+
- **Per-notebook** rate limiting with exponential backoff on failed password attempts (persisted across restarts)
92+
- KDF-downgrade protection: rejects any blob with weakened N/r/p parameters
93+
- Password strength enforcement: 10+ chars, 3/4 character classes, dictionary check against 160+ common passwords, low-entropy rejection
7494

75-
### Content Security Policy (renderer)
95+
### Content Security Policy (Renderer)
7696

7797
```
7898
default-src 'none';
@@ -83,113 +103,94 @@ img-src 'self' data:;
83103
connect-src 'none';
84104
```
85105

86-
All scripts and fonts load from the local `lib/` directory. Zero CDN dependencies at runtime. `connect-src 'none'` blocks any outbound fetch or XHR from the renderer, even if code is injected.
106+
All scripts and fonts loaded from local `lib/` directory. Zero CDN dependencies at runtime. `connect-src 'none'` blocks any outbound fetch/XHR from the renderer process, even if code is injected.
107+
108+
**Note:** The auto-updater runs in the main process (not governed by the renderer's CSP) and makes a single HTTPS request to GitHub Releases on launch to check for new versions. This can be disabled in File → Settings.
87109

88-
The auto-updater runs in the main process — not governed by the renderer's CSP — and makes a single HTTPS request to GitHub Releases on launch to check for new versions. Disable it in File → Settings if that matters to you.
110+
### Additional Hardening
89111

90-
### Additional hardening
112+
- **Sandboxed renderer** (`sandbox: true`) — Chromium OS-level sandbox, on by default
113+
- Navigation guards block all non-`file://` navigation
114+
- `contextIsolation: true`, `nodeIntegration: false`
115+
- All permissions denied (`setPermissionRequestHandler`)
116+
- DevTools disabled in production builds
117+
- **DOMPurify input-type hook** — blocks in-note phishing by forcing all non-checkbox `<input>` elements to lose their type attribute
118+
- Links inserted into notes get `target="_blank" rel="noopener noreferrer"` automatically
119+
- Export dialogs warn about unencrypted output
120+
- Print dialogs warn for password-protected notebooks
121+
- Config keys allowlisted — renderer can only write known settings
122+
- CI actions pinned to commit SHAs to prevent supply-chain attacks
91123

92-
- DOMPurify sanitizes all note content on load, paste, and export.
93-
- Navigation guards block all non-`file://` navigation. `will-navigate` and `will-redirect` are both handled, and `setWindowOpenHandler` denies every attempt to open a new window.
94-
- `contextIsolation: true`, `nodeIntegration: false`.
95-
- All runtime permission requests denied — `setPermissionRequestHandler` returns `false` for every permission.
96-
- DevTools menu item is removed from production builds.
97-
- Export dialogs distinguish between generic unencrypted export and export from a password-protected notebook.
98-
- Print dialogs show a warning when printing from a password-protected notebook.
99-
- Backup restore validates `v=2`, `kdf=scrypt`, and `N>=16384` before accepting anything.
100-
- Password hint inclusion in backups is opt-in via an explicit dialog. Default is exclude.
101-
- Config keys are allowlisted — the renderer can only write a tiny whitelist of known settings.
102-
- CI actions are pinned to commit SHAs so supply-chain attacks via tag-moves can't affect the build.
124+
### Disabling sandbox (fallback)
103125

104-
Security issues? See [SECURITY.md](SECURITY.md) for responsible disclosure.
126+
If `sandbox: true` causes issues on a specific system (rare), close NoteForge and edit `noteforge-config.json` in the data folder:
127+
128+
```json
129+
{ "autoUpdate": true, "sandbox": false }
130+
```
131+
132+
Then restart. No rebuild required.
105133

106134
## Development
107135

108-
### File structure
136+
### File Structure
109137

110138
```
111139
NoteForge/
112-
├── .github/
113-
│ ├── ISSUE_TEMPLATE/ # Bug report and feature request templates
114-
│ ├── pull_request_template.md
115-
│ └── workflows/
116-
│ └── build.yml # CI: auto-build and release on tag push
117-
├── docs/
118-
│ ├── CONTRIBUTING.md
119-
│ └── screenshots/
120-
├── app.jsx # React source (edit this)
121-
├── app.js # Compiled output (generated)
122-
├── main.js # Electron main process + crypto
123-
├── preload.js # IPC bridge (contextBridge)
124-
├── index.html # Shell with CSP
125-
├── styles.css # All styling + @font-face
126-
├── package.json # Scripts + electron-builder config
127-
├── package-lock.json # Pinned dependency graph (committed for reproducibility)
128-
├── lib/ # Bundled runtime dependencies
129-
│ ├── react.min.js
130-
│ ├── react-dom.min.js
131-
│ ├── purify.min.js
132-
│ └── *.woff2 # DM Sans + JetBrains Mono fonts
133-
├── assets/
134-
│ ├── icon.ico
135-
│ └── icon.png
136-
├── Build.bat # Windows build helper
137-
├── NoteForge.bat # Windows dev launcher
138-
├── CHANGELOG.md
139-
├── SECURITY.md
140+
├── .github/workflows/build.yml # CI: auto-build on tag push
141+
├── app.jsx # React source (edit this)
142+
├── app.js # Compiled output (generated)
143+
├── main.js # Electron main process + crypto
144+
├── preload.js # IPC bridge (contextBridge)
145+
├── index.html # Shell with CSP
146+
├── styles.css # All styling + @font-face
147+
├── package.json # Scripts + electron-builder config
148+
├── lib/ # Bundled dependencies (React, DOMPurify, fonts)
149+
├── assets/ # Icons
150+
├── test/ # Test harness — crypto, XSS, install, input hook
151+
├── Build.bat # Windows build helper
152+
├── NoteForge.bat # Windows dev launcher
140153
├── LICENSE
141154
└── README.md
142155
```
143156

144157
### Workflow
145158

146-
1. Edit `app.jsx` (React/JSX source).
147-
2. Compile: `npm run build:jsx`.
148-
3. Test: `npm start`.
149-
4. Build installer: `npm run dist`.
159+
1. Edit `app.jsx` (React/JSX source)
160+
2. Compile: `npm run build:jsx`
161+
3. Test: `npm test`
162+
4. Run: `npm start`
163+
5. Build installer: `npm run dist`
150164

151-
### Data location
165+
### Data Location
152166

153167
| OS | Path |
154168
|---|---|
155169
| Windows | `%APPDATA%\noteforge\` |
156170
| macOS | `~/Library/Application Support/noteforge/` |
157171
| Linux | `~/.config/noteforge/` |
158172

159-
Files written: `noteforge-data.json` (unencrypted) or `noteforge-data.enc` (encrypted), `noteforge-config.json`, `window-state.json`, `ratelimit.json`, and `noteforge-hint.txt` (optional password hint).
173+
Files: `noteforge-data.json` (unencrypted) or `noteforge-data.enc` (encrypted), `window-state.json`, `ratelimit.json`, `noteforge-hint.txt`, `noteforge-config.json`, `noteforge-data.enc.pre-restore.bak` (after a restore, for rollback)
174+
175+
## Keyboard Shortcuts
160176

161-
## Keyboard shortcuts
177+
Press **F1** in the app for an interactive cheat sheet.
162178

163179
| Shortcut | Action |
164180
|---|---|
165-
| `Ctrl+N` | New Page |
166-
| `Ctrl+Shift+N` | New Notebook |
167-
| `Ctrl+B` / `I` / `U` | Bold / Italic / Underline |
168-
| `Ctrl+D` | Duplicate Page |
169-
| `Ctrl+F` | Find & Replace |
170-
| `Ctrl+L` | Lock App |
171-
| `Ctrl+Z` / `Ctrl+Y` | Undo / Redo |
172-
| `Ctrl+=` / `Ctrl+-` | Zoom In / Out |
173-
| `Ctrl+\` | Toggle Sidebar |
174-
| `Ctrl+Shift+D` | Toggle Theme |
175-
| `Ctrl+P` | Print |
176-
| `Ctrl+Shift+E` | Export HTML |
177-
178-
## Contributing
179-
180-
See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md).
181-
182-
## Contact
183-
184-
Open an [issue](https://github.com/jamesccupps/NoteForge/issues) for bugs and feature requests. For security reports or general questions, email <jamesccupps@proton.me>.
185-
186-
## Changelog
187-
188-
See [CHANGELOG.md](CHANGELOG.md).
189-
190-
## Contact
191-
192-
Open an [issue](https://github.com/jamesccupps/NoteForge/issues) for bugs and feature requests. For security reports or general questions, email <jamesccupps@proton.me>.
181+
| Ctrl+N | New Page |
182+
| Ctrl+Shift+N | New Notebook |
183+
| Ctrl+B / I / U | Bold / Italic / Underline |
184+
| Ctrl+D | Duplicate Page |
185+
| Ctrl+F | Find & Replace |
186+
| Ctrl+L | Lock App |
187+
| Ctrl+Z / Ctrl+Y | Undo / Redo |
188+
| Ctrl+= / Ctrl+- | Zoom In / Out |
189+
| Ctrl+\\ | Toggle Sidebar |
190+
| Ctrl+Shift+D | Toggle Theme |
191+
| Ctrl+P | Print |
192+
| Ctrl+Shift+E | Export HTML |
193+
| F1 | Keyboard Shortcuts |
193194

194195
## License
195196

index.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'none';">
77
<title>NoteForge</title>
88
<link rel="stylesheet" href="styles.css">
9-
<script src="lib/react.min.js" integrity="sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z" crossorigin="anonymous"></script>
10-
<script src="lib/react-dom.min.js" integrity="sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1" crossorigin="anonymous"></script>
11-
<script src="lib/purify.min.js" integrity="sha384-pcBjnGbkyKeOXaoFkmJiuR9E08/6gkmus6/Strimnxtl3uk0Hx23v345pWyC/MMr" crossorigin="anonymous"></script>
9+
<!-- SRI is not used here because these scripts ship locally inside the app bundle.
10+
Electron's file:// loader enforces CORS when `crossorigin` is set, which blocks
11+
local scripts silently. Integrity of bundled files is enforced by the signed
12+
installer and electron-builder's ASAR integrity checks. -->
13+
<script src="lib/react.min.js"></script>
14+
<script src="lib/react-dom.min.js"></script>
15+
<script src="lib/purify.min.js"></script>
1216
</head>
1317
<body>
1418
<div id="root"></div>

main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ function createWindow() {
730730
{ label: "Keyboard Shortcuts", accelerator: "F1", click: send("show-shortcuts") },
731731
{ type: "separator" },
732732
{ label: "About NoteForge", click: () => dialog.showMessageBox(mainWindow, {
733-
type: "info", title: "About NoteForge", message: "NoteForge v2.7.0",
733+
type: "info", title: "About NoteForge", message: "NoteForge v2.7.1",
734734
detail: "Encrypted offline note-taking.\nAES-256-GCM · scrypt (N=65536)\nDerived key session · Auto-lock · Sandboxed renderer\n\nData: " + userDataPath,
735735
})},
736736
]},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "noteforge",
3-
"version": "2.7.0",
3+
"version": "2.7.1",
44
"description": "Encrypted offline note-taking — a OneNote alternative that keeps your data local and protected.",
55
"main": "main.js",
66
"author": {

0 commit comments

Comments
 (0)