Encryption model
How RedScribe encrypts engagement data at rest, what is and isn't covered, and what happens if the root key is lost.
RedScribe encrypts the sensitive parts of your engagements at rest, using AES-256-GCM, so that a stolen database backup or an exposed disk image can’t be read without a separate key that lives elsewhere entirely. This page explains how that protection actually works, exactly what it does and doesn’t cover, and what happens if you lose the key.
Two keys, not one
Section titled “Two keys, not one”Root key (one per instance) wrapsEngagement key (one per engagement, created automatically) encryptsThe sensitive content that engagement containsOne key belongs to your whole instance: the root key, generated once during setup. It never touches your actual finding content directly. Instead, the first time an engagement needs one, RedScribe generates a fresh, random key just for that engagement and encrypts it with the root key. That per-engagement key is what actually encrypts and decrypts the sensitive content inside it.
The practical effect: compromising one engagement’s key (which never happens on its own, since it’s never stored anywhere unencrypted) still wouldn’t expose any other engagement’s data, and the root key itself never directly touches the data it’s ultimately protecting.
Where the root key lives
Section titled “Where the root key lives”You supply the root key one of two ways:
- A Docker secret file (the default, and the one to prefer if you can).
- An environment variable, for deployments that aren’t using Docker Compose, or for CI.
If both are present, the file wins. That’s a deliberate choice, not an arbitrary one: an environment variable can be read by anyone who can inspect the running container or its process list, while a secret file’s visibility is limited to whoever can actually read that mounted file.
What’s actually encrypted
Section titled “What’s actually encrypted”Encryption applies to specific, sensitive content, not to your entire database. As of this writing, that’s:
- Each finding’s written content: description, technical details, business impact, and whatever other sections your instance uses.
- Notes attached to a retest record.
- Comment threads on findings, both the specific text a comment points at and each comment’s own body.
- Checklist item results and comments.
- Images pasted into rich text, such as evidence screenshots.
Everything else, a finding’s title, severity, status, CVSS score, classification tags, an engagement’s client name and scope, checklist item titles and categories, stays unencrypted. That’s what makes listing, filtering, search, and reporting work without needing an engagement’s key for every single query. The line is drawn around the actual narrative and evidence content, not the whole schema.
Uploaded images
Section titled “Uploaded images”An image pasted into rich text, a screenshot of evidence for example, is checked against the actual bytes of the file, not just the label the upload claims to be, so a renamed file can’t sneak past the image-only restriction. It’s capped at 10 MB, then stored encrypted the same way as everything else above, and only ever served back out, decrypted, to someone who already has access to that engagement.
Ciphertext can’t be swapped between records
Section titled “Ciphertext can’t be swapped between records”Every piece of encrypted content is cryptographically tied to exactly which record it belongs to, not just encrypted in isolation. If someone with direct database access tried to copy one finding’s encrypted content into a different finding’s row, it wouldn’t quietly succeed: decryption would fail loudly the next time anyone tried to read it, rather than silently showing the wrong finding’s content. The same protection applies to the root key wrapping each engagement’s key in the first place.
Each encryption also uses a fresh, unpredictable value every time, so the same content never produces the same ciphertext twice. That’s a basic requirement for this kind of encryption to hold up at all, and it’s handled automatically, not something you need to think about.
What this protects against, and what it doesn’t
Section titled “What this protects against, and what it doesn’t”- Protects against: a stolen database backup, dump, or disk image being readable without the separate root key. A compromised read-only database credential, say a misconfigured reporting connection, exposing narrative finding content.
- Doesn’t protect against: a compromise of the running application itself. While the app is actively serving a request, it necessarily holds that engagement’s key in memory for the moment it needs it, since this protects data at rest, not data while it’s actively being used. It also doesn’t protect against a compromise of wherever you store your root key backup, so guard that backup the same way you’d guard the key itself.
See backup and restore for how encrypted data is actually backed up and restored. The encrypted content travels with the database backup, but the root key is a separate prerequisite you need to already have on hand to make a restored backup usable again.