# Security Policy
Scrib Desktop is an offline, encrypted text editor. We take reports about the
encryption, file handling, and data integrity seriously.
## Supported versions
| Version | Supported |
|---------|-----------|
| 1.11.x | ✅ |
| 1.10.x | ⚠️ Critical fixes only |
| < 1.10 | ❌ |
## Reporting a vulnerability
**Please do not open a public GitHub issue for security vulnerabilities.**
Instead, use GitHub's private vulnerability reporting:
1. Go to the **Security** tab of this repository.
2. Click **Report a vulnerability**.
3. Describe the issue, the affected version, and steps to reproduce.
If private reporting is unavailable, contact the maintainer through the contact
details on .
We aim to acknowledge a report within 7 days and to provide a remediation plan
or fix timeline within 30 days.
## Scope and threat model
The user-facing list is the [Threat model](README.md#threat-model) section of
the README. This section names the adversaries so a reporter can tell which
side of the line a finding falls on.
**Defended against:**
- **Someone with read access to your `.scrb` files but not the password.**
AES-256-CBC with HMAC-SHA256 (Encrypt-then-MAC), PBKDF2-SHA256 key
derivation, and authenticated, self-describing KDF parameters (v3 format).
- **Someone who tampers with a `.scrb`.** The HMAC covers the version byte,
the KDF parameters, the IV, the salt, and the ciphertext, and it is verified
with a constant-time compare before any decryption, so tampered ciphertext
never reaches the unpadder.
- **A crafted `.scrb` header.** The per-file PBKDF2 iteration count is
range-checked before the KDF runs, capped at 2,000,000, so a hostile file
cannot stall the app with an arbitrary work factor.
- **A crash or power loss during a save.** Writes stage to a sibling file and
land through an atomic rename, with `.scrib-bak` recovery on the next read.
**Not defended against.** These are limitations of the current design, not
bugs, though concrete improvements to any of them are welcome:
- **Whoever supplied the note file you open.** A `.scrb`, `.rtf` or `.txt`
from someone else is untrusted input, parsed with no sandbox. Since 1.10.0
reads are capped at 64 MB and `.rtf`, table and image payloads are bounded,
so a crafted note can no longer hang the app or exhaust memory, but the
parsing itself is still unsandboxed and has no time budget. Scrib does
enforce an http/https/mailto scheme allowlist on
RTF import and again at link launch, restricts image embeds to `data:` URIs,
and has no network stack at all. It does **not** apply that allowlist to
rich text pasted from another application, or to links written back out into
an exported `.rtf`, so an exported document can carry a link the note
received from elsewhere.
- **Whoever can write to the folder holding your notes** (a shared drive, a
synced folder, removable media, a second local account). Scrib assumes that
directory is yours: it stages writes at predictable sibling paths
(`.scrib-tmp` and `.scrib-bak`), and because a save creates a new
file and renames it into place, the note ends up with the permissions
inherited from the folder rather than any per-file permissions you set.
- **Whoever controls the download.** Release binaries are not code-signed, and
the published SHA-256 is generated by the same workflow job that builds and
uploads the zip, so it detects a corrupted download but not a compromised
release. Build from source if you need stronger assurance.
- **Windows hibernation and paging.** Passwords and decrypted note content are
Dart `String`s, which are immutable and cannot be zeroed. Hibernating or
paging out writes that memory to `hiberfil.sys` / `pagefile.sys`, on the same
disk as the notes and readable by the same attacker the first bullet above
defends against. Full-disk encryption is the only real mitigation; auto-lock
shortens the window. Locking a tab drops the references but cannot erase the
strings. Locking also clears the clipboard when it still holds a slice of the
note being locked, but Windows Clipboard History and Cloud Clipboard keep
their own copies, which Scrib cannot reach.
- **A compromised machine** (key-logger, RAM dump, attached debugger, a
malicious build already running) and **brute-forcing a weak password**. Since
1.10.0 the strength meter accounts for common passwords, keyboard runs and
repeated characters as well as length and character classes, so it no longer
rates obvious dictionary passwords as strong. It is still a hint, not a
check: PBKDF2 at 100,000 iterations raises the cost of an offline guess but
does not make one infeasible.
- **Recovery of a plaintext file you later encrypted.** Converting an existing
`.txt` or `.rtf` writes the `.scrb`, then overwrites the original's bytes
before removing it, and does the same to any `.scrib-bak` / `.scrib-tmp`
sibling holding a copy (1.10.0 and later; earlier versions deleted without
overwriting). Overwriting cannot guarantee the bytes are gone: on an SSD, a
snapshotted volume, a backup, or a synced folder the pre-encryption copy may
survive. For notes
that must never exist in plaintext, create the note encrypted rather than
converting one.
- **Filename and path disclosure.** Recent files, the session snapshot, window
geometry and preferences live in an unencrypted Hive database under
`%APPDATA%`. Note contents are encrypted; their names and paths are not, and
Hive appends rather than rewrites, so clearing recent files or the session
does not immediately erase the earlier entries from that file. Versions
before 1.10.0 kept it in `%APPDATA%\com.beeswaxpat\scrib_desktop`; Scrib
copies that forward once and never deletes the old folder.
## Fixed security issues
- **1.9.x: a save could mark a note clean against content that was never
written.** Every save path recorded the tab's *live* content when the write
finished, not the content the write had started with. An encrypted save
takes well over 100ms (isolate spawn, PBKDF2, two flushed writes), so
anything typed during that window was marked saved while only the older
bytes were on disk: no dirty marker, no prompt on quit, and no `.scrib-bak`
to recover from. Each write now snapshots the content before it starts and
marks the tab clean against that snapshot, so an edit made mid-write leaves
the tab dirty and is persisted by the next save. The same snapshot is used
for every routing decision, so tab state changing mid-write can no longer
send ciphertext to a `.txt` or a rich-text envelope to a `.rtf`, and a tab
locked while its save was in flight is no longer re-seeded with the
decrypted note.
- **1.9.x: an extension swap could replace an unrelated file with no prompt.**
Toggling encryption (`Ctrl+E`) or switching editor mode makes the manual save
write a path the user never picked: `notes.txt` becomes `notes.scrb`, and the
atomic rename replaces whatever was already at that destination without
leaving a `.scrib-bak`. Each swap branch then deleted the source, so one
`Ctrl+S` could destroy two files. Every swap now asks before replacing a
different file, and it fails closed: with no confirmation available the save
is refused rather than performed. Save As re-asks when it retargets the
chosen path to `.scrb`, because the file picker's own replace prompt applied
to a path that is no longer the destination.
- **1.9.0: background saves could write plaintext over an encrypted `.scrb`.**
After toggling decryption on an open note (Ctrl+E), the auto-save,
close-tab-save, and quit-save paths wrote the decrypted content back to the
`.scrb` path, leaving the note unencrypted on disk. 1.9.0 makes every
background save path refuse an encryption/extension mismatch; only the
manual save performs the explicit extension swap. If you used Ctrl+E to
decrypt a note in an earlier version and kept working with auto-save on,
verify the file on disk is what you expect and re-encrypt it if needed.
A report that restates one of the "not defended against" bullets above (for
example, "the password is recoverable from a memory dump") is an acknowledged
limitation rather than a vulnerability. That list is deliberately specific, so
anything it does not name is in scope: in particular, a concrete exploit
against a hostile note file, a shared notes directory, or the release pipeline
is worth reporting even though those adversaries are listed as undefended,
because the bullets describe the current posture, not an intent to leave it
there.
## What to include in a report
- The Scrib Desktop version and your OS version.
- A minimal proof of concept or reproduction steps.
- The impact you believe the issue has.
- Whether the issue affects existing `.scrb` files on disk.