Encrypted local state
Encrypted local state
Holaryn protects classified local records, artifacts, and recovery archives with authenticated
envelope encryption. A fresh install turns it on before opening its first protected store or
saving its first provider key. The onboarding Recover step then offers a recovery password
for an encrypted recovery backup. Installs that already opened plaintext stores keep working
and need an explicit migration. The desktop setup below manages that stopped-host operation;
the manual alternative is holaryn encryption enable with every owning process stopped.
If encryption was disabled or its key provider was unavailable when a store opened, saving a
provider key later does not enable encryption for only part of that running installation.
Settings displays the required stopped-host migration. Restarting alone does not encrypt
earlier plaintext. Keep the encryption-plaintext-state.json marker with the state directory;
it records this migration requirement and contains no private content.
Concurrent starts share one installation's keys and wait briefly for its encryption boundary.
If the owning process does not finish initialization within five seconds, retry after it finishes;
the waiting process refuses to write without a known boundary. Runtime and CLI stores use the
configured state directory for their keys, including custom or batch memory paths. Memory outside
that directory needs a separate data backup or explicit inclusion in an archive; its keys remain
in the installation manifest. Library callers must supply state_dir or an existing crypto
object to initialize encryption. A bare library store only discovers ancestor keys and warns
if it remains plaintext.
Standalone CLI memory previously defaulted to ~/.holaryn/memory.sqlite3; it now defaults to
memory.sqlite3 inside the selected installation state directory. An existing legacy file is
retained and triggers a warning. To keep using that history, explicitly set HOLARYN_MEMORY_PATH
to the old file and include it in stopped-host enable/migrate with --memory-path. The path
change does not import or delete the old database. Host --state-dir takes precedence over
HOLARYN_STATE_DIR, including after settings saves, profiles and background runtime creation.
Stopped-host enable/migrate also includes batch-memory/*.sqlite3 and the current SQLite
holaryn_memory_path from that installation's config (with environment overrides). For older
imports or other local memory databases, repeat --memory-path <database> on enable or
migrate. Stop every process using those databases, including another installation that shares
an external file. A database owned by a different encryption manifest is refused; keep both
manifests and migrate it with its owning installation.
Each additional database gets a consistent encrypted pre-migration snapshot under
encrypted-backups/memory_<target-id>.sxbak. The migration checkpoint maps those target ids to
local source paths and retains unfinished targets when configuration changes. Resume with the
same checkpoint and recovery password. A missing pending source or busy SQLite cleanup leaves
the migration incomplete. The snapshot includes committed WAL pages and is taken while holding
the database's write reservation; covered rows are then encrypted under the installation keys.
No plaintext snapshot file is created during this operation.
Keep both the first-enable archive and these additional rollback archives. To recover an
additional pre-migration database, restore its archive into a separate empty directory and
inspect memory.sqlite3 there. It is the original snapshot, so legacy plaintext rows in that
rollback remain plaintext after archive decryption. With all owners stopped, copy the inspected
database to the intended path in a separate recovery installation and run migration there before
starting the host. Restoring a rollback is an explicit operator operation; migration does not
overwrite a live external path during restore. Subsequent routine root backups still require
separate inclusion of external memory data.
What is protected
Encryption covers the write-only secret store; sensitive run-journal and local-memory fields;
durable execution specifications, commands, launch plans, errors and output frames;
chat artifacts, attachments, canvas and MCP payloads; sensitive device-invocation fields,
transfer chunks and artifacts; and encrypted recovery backups. Each domain has its own versioned
data key. Windows DPAPI or the operating-system credential vault normally wraps those keys; a
recovery-password provider is available for portable installations.
Owned Workshop artifacts and proposal/revision/event JSON payloads, plus immutable library
objects, index and last-good recovery, also use the artifact and journal domains. Existing
plaintext requires the explicit stopped-host holaryn encryption migrate upgrade, including
installations whose older migration checkpoint was complete. Workshop and Settings display this
pending upgrade without running it. See skill-state encryption
for limits, separate rollback backups, resumability and remaining plaintext metadata.
Some metadata remains readable so Holaryn can locate, expire, route, and repair records: ids,
timestamps, states, sizes, MIME types, digests and relationships. Configuration, logs,
operator/control-plane databases, explicit exports, workspaces, model files, and externally
hosted databases are not covered. The complete inventory and threat model are in
the encrypted-state ADR.
Search vectors of an encrypted memory are stored encrypted in the memory file: each vector is
encrypted on its own under a key derived from the memory key, the key that also encrypts the memory
text. To search them, Holaryn decrypts the vectors into an index it keeps only in RAM while it runs.
HOLARYN_MEMORY_VECTOR_INDEX_MAX_MB sets how much RAM a process may use for all of its search
indexes, 256 MB by default. An index that does not fit makes recall use keywords only for that
memory. A memory file from an earlier release still holds its search vectors unencrypted. Until you
run holaryn memory reindex (or the "Reindex memory" button on the Memory settings page, or the
"Rebuild memory index" repair on the Readiness panel), Holaryn searches such a file by keywords only
and stores no new search vectors. The same applies after you change the vector backend, and to a
memory file this version created while the installation was still at format 1 (see below). With the
local vector backend, the reindex stores the vectors encrypted and removes the old vector tables,
but their bytes stay in the file's unused space until a cleanup erases them (see below). In
Qdrant, vectors are never encrypted, and the reindex sends them
there unencrypted again. Anyone who can read the Qdrant collection can compare them with texts of
their choosing or approximate what was remembered. Holaryn records each Qdrant collection an
encrypted memory used and reports one that is no longer configured until you delete it with Qdrant's
tools and run holaryn memory reindex --forget-external-remnants. Holaryn never deletes a
collection.
To erase those old bytes, stop the Holaryn host (and every holaryn run session), then run holaryn
memory reindex. With no host holding the state directory, the reindex also runs a verified cleanup
of an encrypted memory file: it takes SQLite's exclusive lock on the file, rebuilds it with VACUUM
so that no unused page is left, checks its structure, scans it and the files beside it for the
encrypted samples of the old data that the rebuild kept, and only then marks the cleanup verified
and says so. holaryn memory status then shows old vector cleanup: verified (or old fingerprint
cleanup: verified when only fingerprints were converted). The cleanup needs free space of twice the
size of the file and its write-ahead log plus 64 MB on the file's disk, and the file's size in the
temporary folder. If another program has the file open, if the file or a file beside it has another
name on disk (a hard link), if a journal file that SQLite does not use is beside it, or if there is
not enough space, the cleanup itself changes nothing and says why. One change can still happen when
the cleanup opens the file: if an earlier program left a journal file from an unfinished change
beside the memory file, SQLite undoes that change, and the cleanup's message says so. The cleanup
does not reach copies made earlier: encrypted backups and rollback archives, file-system snapshots,
synced copies, backups made by other tools, exports, the operating system's page or hibernation
file, and Qdrant collections. Restoring an older backup brings the old data back, and Holaryn
reports it again. The cleanup also runs while search vectors are off (see below), and keeps them
off.
To use no search vectors at all for an encrypted memory, set
HOLARYN_MEMORY_KEYWORD_ONLY_WHEN_ENCRYPTED=true in the environment of the Holaryn host. It is not
a Settings field. The first time a Holaryn process with this setting opens the encrypted memory file
to work on it, that process turns search vectors off in the file itself. From then on every Holaryn
process that uses the file, including one already running without the setting, recalls by keywords
only. No process stores, searches or sends search vectors. Writing, recall and holaryn memory
reindex build no embedder, so a missing embedder extra such as fastembed does not stop them.
holaryn memory status, the Memory settings page and the readiness check still build the active
embedder to name it, so a missing embedder extra is reported there as before. Turning search vectors
off removes nothing by itself. holaryn memory reindex (or the "Reindex memory" button on the
Memory settings page, or the "Rebuild memory index" repair on the Readiness panel) removes the
search vectors still in the memory file and keeps them off. Vectors already sent to Qdrant stay
there unencrypted until you delete the collection with Qdrant's tools. To turn search vectors back
on, remove the setting from every Holaryn process that uses the memory and restart the host, then
run holaryn memory reindex --enable-vectors. It refuses while the setting is in its own
environment. It rebuilds the vectors in the vector backend now configured, encrypted in the memory
file or unencrypted in Qdrant. A process that still has the setting turns them off again the next
time it opens the file. The setting does not apply to an unencrypted memory or to a PostgreSQL or
MariaDB store. holaryn memory status says whether search vectors are off and what remains.
The memory file also holds fingerprints of each record and recall query. An encrypted memory makes
new fingerprints with a secret key. Older fingerprints stay SHA-256 digests made without a secret
key until holaryn memory reindex converts them; deleting a memory converts its own fingerprint.
Anyone who gets a copy of the file can use the fingerprints made without a secret key to confirm a
guess of a record's content or of a query, and anyone who gets a copy made before the reindex can
compare the old vectors with texts of their choosing. A copy made before the cleanup holds the old
vectors and fingerprints too. holaryn memory status says what applies to your store. Keep the
memory file and every copy of it, backups included, where only people you trust can read them, and
limit who can read the Qdrant server. A PostgreSQL or MariaDB memory store is not
encrypted by Holaryn at all.
Memory format 2 is the format in which new fingerprints are made with a secret key. An encrypted
installation is converted to it the first time Holaryn opens its encrypted state to work on it while
no other Holaryn process holds its state directory. That happens, for example, when holaryn serve
starts, or when holaryn run or holaryn memory reindex runs while no host is running. Each memory
file is then converted the first time this version opens it to work on it. Commands that only
report, such as holaryn memory status, holaryn status, holaryn encryption status and holaryn
doctor, convert nothing and write nothing in the encryption manifest, the memory file or its
write-ahead log. They say what they find. If a write-ahead log, shared-memory or journal file is
beside the memory file and could be incomplete, holaryn memory status and holaryn doctor do not
open the memory file, because reading it could change it. Running holaryn memory reindex completes
the file beside the memory file. If another Holaryn process holds the state directory while the
installation still needs converting, holaryn memory reindex stops and says so: the search vectors
cannot be stored encrypted in a memory file still at format 1. If no memory file exists yet, holaryn memory status says so and
does not create it; the Holaryn host or a holaryn run session creates it the first time it uses
memory.
Once an installation is converted, older Holaryn versions refuse to open it ("unsupported encryption
manifest version 2"). An older Holaryn process that was already running can still add, edit, search
and delete memories in a memory file until this version first opens that file to work on it. After
that it cannot add, edit or search memories in that file ("memory format 2 requires a newer
Holaryn"), but the fences do not stop everything it does: a reindex it runs still rewrites the
search vectors, unencrypted, and it can still delete a memory whose fingerprint this version already
made with a secret key. Stop every older Holaryn process and upgrade the desktop app and any
separately installed holaryn command to the same version.
Opening an artifact in an editor writes a plaintext copy under webui-files/exports/, because
the editor needs a readable file. Holaryn deletes those copies after a day (when you open another
artifact or restart the host), leaves them out of backups, and removes any existing copies when
you first enable encryption. Close an artifact in its editor before enabling encryption; a copy
the editor still holds open stops the migration, and you can run it again after closing it.
Encryption at rest does not protect data while an authorized running agent, connector, tool,
model provider, debugger, or operator is using it.
Set up encryption in the desktop app
- Open Settings → System → Encrypted local state in the current desktop app.
- Save or close temporary chats and finish or stop active work, including work waiting for a
decision. The desktop must own this installation's local host. - Choose Set up encryption, review the protection information, then choose Continue.
- Enter and confirm a recovery password of at least 12 characters. Keep it separately from
the computer and backup; Holaryn does not save this password. - Choose Choose backup folder and select a folder outside Holaryn's data directory.
- Choose Encrypt and restart. Keep Holaryn open while it stops its host, verifies a recovery
backup, encrypts existing records, and reconnects. - Confirm Protected and unlocked, retain the displayed recovery backup, and choose
Return to Advanced reasoning if you want to select that experimental reasoning system.
Installing the app or selecting Advanced does not migrate an existing plaintext profile by
itself. If setup is interrupted, keep the keys, backup, and password, then use Resume encryption
setup or the documented manual recovery path. A browser session, independently managed host,
or Windows service displays the reason guided setup is unavailable. The desktop does not stop
or take ownership of those hosts. Locked state needs its existing keys recovered first.
Before enabling manually
Older installations whose migration completed before execution-session coverage may now show
upgrade_required. Stop all owning processes and run holaryn encryption migrate with the
existing recovery material. It adds the missing execution step while preserving the rollback
archive. An interrupted transaction resumes safely; a busy cleanup remains incomplete until
the owning processes release their databases.
Settings → System → Encrypted local state provides health and setup guidance. The Web search and
inactive Advanced reasoning notices link directly to it. These links do not start encryption.
Saving new Web search credentials requires unlocked encryption covering secrets; choosing Advanced
reasoning without available encrypted state leaves Standard reasoning active.
- Stop every Holaryn host or CLI process using the state directory.
- Choose a strong recovery password and store it separately from the machine and its backups.
- Confirm you have enough free space for an encrypted pre-migration backup plus SQLite
checkpointing. - Put the password in a process-scoped environment variable. Do not put it in shell history,
a command argument, a project.envfile, or source control. - Optionally place representative unique plaintext canaries in environment variables so the
migration can prove they no longer occur in covered at-rest files.
PowerShell example:
$env:HOLARYN_ENCRYPTION_RECOVERY_PASSWORD = Read-Host "Recovery password" -MaskInput
$env:HOLARYN_TEST_CANARY = "unique-test-marker-already-present-in-covered-state"
holaryn encryption enable --provider os --canary-env HOLARYN_TEST_CANARY
Remove-Item Env:\HOLARYN_ENCRYPTION_RECOVERY_PASSWORD
Remove-Item Env:\HOLARYN_TEST_CANARY
On Windows, --provider os uses current-user DPAPI. On macOS and Linux it uses the configured
Keychain/libsecret-compatible keyring. Use --provider recovery only when you deliberately want
the recovery password to be the live wrapping provider.
Enabling creates and verifies encrypted-backups/first-enable-*.sxbak before it changes covered
state. The operation is checkpointed. If power is lost, stop the host again, restore the same
password environment variable, and run:
holaryn encryption migrate
Do not delete the encryption manifest, migration checkpoint, or pre-migration backup to “fix” a
locked or interrupted installation.
After enablement and verification, restart the host so it opens the encrypted stores. Revisit
Web search to save credentials or Experimental Features to confirm Advanced reasoning is active.
For a non-default installation, pass --state-dir to the encryption command before its subcommand
and use the same state directory as the host.
Inspect health
holaryn encryption status
holaryn encryption status --json
With no host running, holaryn status and holaryn encryption status read the memory file to
check its encryption, which can take a few seconds on a large memory store.
Settings → System → Encrypted local state shows the same non-secret provider, lock, data-key
version, migration, rotation and backup health. A locked status is fail-closed: Holaryn preserves
ciphertext rather than overwriting it.
For a recovery-password live provider, set HOLARYN_ENCRYPTION_PASSWORD or a protected
HOLARYN_ENCRYPTION_PASSWORD_FILE before status. Commands that need an archive password or a
new recovery wrapper read HOLARYN_ENCRYPTION_RECOVERY_PASSWORD unless --password-env selects
another variable. Rotation and rewrap to an OS provider can unlock an existing recovery provider
through the runtime password/file; OS-to-OS rewrap and OS-backed rotation need no password.
Enabling or migrating encryption still requires a password for the rollback archive.
Rotate or change the wrapping provider
Stop the host. Activate new keys for every domain:
holaryn encryption rotate
Or rotate one domain:
holaryn encryption rotate --data-class memory
Old key versions remain readable so mixed-version data is safe. New writes use the active
version. Rotation authenticates every retained key before changing the manifest; a wrong
password or unavailable wrapping provider leaves the current versions intact. For a recovery
provider, make its existing password available as described above before rotating. Rewrap
preserves the domain keys and data while changing their provider:
holaryn encryption rewrap --provider recovery
holaryn encryption rewrap --provider os
Changing providers is not a substitute for a tested backup.
On Windows, rewrap --provider os defaults to current-user DPAPI. Before moving interactive
state to the LocalSystem service, stop the host and, from the account that can unlock the keys, use
holaryn encryption rewrap --provider os --dpapi-scope local-machine against that same installation.
holaryn encryption status shows DPAPI scopes (JSON: dpapi_scopes). Local-machine wrapping
permits any account on that machine holding the wrapped bytes to unwrap them, so the private state
directory DACL is required. Current rotation preserves each active domain's recorded scope; older
builds could create a mixed-scope manifest during rotation, which needs the explicit rewrap before
service use. Follow the Windows service procedure
for account selection, private ACLs and password-file setup. OS-01 manual installation validation
remains pending.
Backup and recovery drill
Create a path-independent encrypted archive while the host is stopped:
holaryn encryption backup D:\offline\holaryn-2026-07-25.sxbak
holaryn encryption verify-backup D:\offline\holaryn-2026-07-25.sxbak
Copy the archive to another machine, install the same or a compatible Holaryn version, provide the
recovery password, and restore into a new empty directory:
holaryn encryption restore-backup `
D:\offline\holaryn-2026-07-25.sxbak `
D:\restore-drill\holaryn-state
$env:HOLARYN_ENCRYPTION_PASSWORD = $env:HOLARYN_ENCRYPTION_RECOVERY_PASSWORD
holaryn encryption --state-dir D:\restore-drill\holaryn-state status
Never restore over live or non-empty state. Validate the recovered installation separately
before any deployment replacement.
New encryption backups include copies of all retained encryption-key versions wrapped for the
backup password. After restore, use that password as the restored installation's recovery-provider
password; the original OS vault/account or live wrapping password is not required. You may then
rewrap the recovered installation to its new machine's OS provider. Creating a backup leaves the
original installation's provider and domain keys unchanged.
Services can instead set HOLARYN_ENCRYPTION_PASSWORD_FILE to a mounted password file.
That file takes precedence over HOLARYN_ENCRYPTION_PASSWORD; an explicit command password
takes precedence over both. Files accept UTF-8 with or without a byte-order mark. Trailing
line endings are removed, while spaces are preserved. Missing, unreadable, empty or invalid
recovery material leaves the installation
locked with an actionable message. The original manifest and encrypted data are retained.
Archive format 2 authenticates the complete index, creation time and wrapping metadata,
as well as each file. Removing an entry, changing metadata, or adding duplicate/unlisted ZIP
members fails verification before file restoration. Older format 1 archives remain readable,
but verification reports verified-entries-only with legacy-entry-only integrity. Their
creation time and complete file inventory cannot be proven from the archive alone. Check
them against a trusted inventory, restore with a compatible version, then create a new backup.
Authentication proves an archive was made by someone who knew its password, not that its
creator can be trusted, so restore also checks every entry's name before writing anything.
A name must be one relative path that means the same on Windows, macOS and Linux:
/-separated components, none of them empty, . or .., and none holding a backslash, a
colon (a drive such as C: or an NTFS stream), another character Windows reserves, a trailing
dot or space, or a device name such as CON or nul.txt. Each file is written only after its
target resolves inside the restore directory, and two entries that this file system treats as
one file (README and readme on Windows or macOS) stop the restore instead of overwriting
each other. Backups are created under the same rule, so a state file with such a name makes
backup fail with the name rather than produce an archive that cannot be restored.
The onboarding recovery backup contains saved provider credentials, their registry and the key
manifest. It does not include chat history, memory or artifacts. Use the full encryption backup
command for those stores. Archives created before the SA-401 fix can still need their original
wrapping provider in addition to the archive password; create and test a new recovery backup.
Troubleshooting
- DPAPI wrap failure with
ModuleNotFoundErrororImportErrorin a Windows package: install
a corrected build containing thewin32cryptruntime. Source-Python encryption working does not
prove the frozen host has this module. Builds now exercise DPAPI migration and reopening with
disposable state before staging succeeds. Diagnostics retain the exception category and native
numeric error code, without key material or raw exception text. A native error with the runtime
present may instead indicate an OS-account or credential-vault problem. - “key provider is locked” or DPAPI/keyring unavailable: use the same OS account and unlock
its credential vault. For a recovery-password provider, supply the correct password variable. - Wrong password or modified archive: authentication fails before restore publication.
Retest a known-good independent copy; do not retry by deleting metadata. - Migration interrupted: run
migrateagain with the same state directory and recovery
material. Completed steps are not duplicated. - Plaintext canary found: the migration remains in
validation_failed. Keep the backup and
checkpoint, inspect the reported relative path, classify it against the inventory, and do not
declare cleanup complete. - Corrupt/truncated ciphertext: preserve the state directory, stop writes, verify a recovery
archive, and restore into a separate directory. Holaryn intentionally will not reset the record. - Lost OS key and recovery material: the protected content may be unrecoverable. Preserve
evidence and seek specialist help; recreating a manifest cannot recover the old data.