neurons.me

Identity-Bound Secrets

Status: implemented, and adversarially tested. The design below was originally published as a proposal; the .me technical document now records it as implemented (v4 derivation, root lifecycle, migration, redacted snapshots, real Monad process integration), with file/line citations against the code. A subsequent adversarial security battery — isolation, scopes/noise, information leakage, cryptographic/format tampering, root lifecycle, replay/restart, migration, and generative/scale testing, plus real-process persistence testing on the Monad side — ran against that implementation and is summarized in “Adversarial security battery” below. This page still does not certify a cryptographic audit; see that section’s own caveats. Updated September 8, 2026.

License: CC0 1.0 Universal.

What we are building

A tree with public branches and protected scopes. Each protected scope requires private material from its identity and the secrets applicable to that branch. Copying paths, secrets, and noise values to another identity must not allow it to open the data.

The private encryption root is randomly generated and stored encrypted under a password-derived key. The password unlocks the root; it does not regenerate it. Changing the password rewraps the same root and preserves branch ciphertext.

Identity's private encryption root
  + secrets applicable to the scope
  + noise and path context
  → branch encryption key

The public identity identifier is no substitute for the private root. Password key derivation and derivation from a random root serve different purposes; their algorithms and parameters must be documented in the implementation.

Persistence and session locking

Session secrets must not be persisted. Readable copies of protected content must not remain in snapshots, journals, or persistent indexes either.

The kernel must retain enough topology to recognize a protected branch internally after a restart. Without secrets, it cannot open the branch or accidentally make it public. The public surface preserves stealth: it does not reveal a “protected, locked” state. This does not promise to hide sizes, access patterns, or all on-disk metadata.

A redacted public view and a recoverable backup are formats with different purposes. Replacing content with *** is insufficient when that content is needed to reconstruct state.

Three distinct operations

Operation What it does What it requires
Re-derive Recomputes a branch’s keys without modifying its data. The root, applicable secrets, and context.
Rotate Creates another root and migrates affected data. Access to the previous data; migration may remain pending per scope.
Recover Restores the same root from a configured backup. An available recovery mechanism.

A random root lost without a backup cannot later be reconstructed from the public identifier. The initial scope includes basic backup/restore and password changes; advanced rotation between roots and an optional vault for remembering secrets are deferred to a later phase.

Why multiple packages are involved

Package Expected responsibility
.me v4 derivation and encryption, scopes, noise, caches, snapshots, and migration. Most tests belong here.
Monad Startup integration, actual disk persistence, and recovery without overwriting data after a failure.
Cleaker Identity and credential integration, only if the chosen solution changes this layer. Reviewing it does not require changing it.

The final location of root management must be justified against the code. Do not introduce a reverse dependency from the kernel to Cleaker or include unprotected private material in a public claim.

The 11 acceptance criteria

These are 11 behavioral requirements, not 11 tests per repository or a final allocation of test files. A requirement may need several unit and integration tests. The coverage column indicates where verification is expected; it does not report execution.

# Criterion Expected coverage
1 Another identity cannot open v4 blobs with identical paths, secrets, and noise, even after copying the storage. .me
2 The correct identity without the applicable secret cannot open the branch either. .me
3 Noise cuts previous inheritance while preserving identity isolation. .me
4 Public navigation, nested scopes, and inheritance work. .me
5 Saving and restarting leaves scopes locked; supplying the correct context recovers their data without reclassifying them as public. .me + Monad integration
6 Changing the password preserves the root and ciphertext; the previous password fails against the updated envelope. The root-management layer; Cleaker if involved
7 Locking or changing identity invalidates previous access and caches. .me + identity integration
8 Snapshots and generated files contain no plaintext test secrets, noise, or protected content. .me + Monad integration
9 Tampering with ciphertext or context fails without falling back to older formats or losing data. .me; Monad persistence where applicable
10 An interrupted migration resumes, preserves pending items, and covers both branch and value blobs. .me + Monad persistence
11 A backup restores the same root without automatically unlocking branch secrets. The backup/restore layer + integration

Execution results. The .me package’s tests/identity-bound-secrets.test.ts runs all 11 criteria above (npm run test:identity-bound-secrets), all 11 passing as of the implementation closing. A separate adversarial battery (tests/Security/*.test.ts in .me, plus tests/Security/*.process.test.ts in Monad) then tested each criterion’s edge cases and failure modes beyond the original acceptance shape — see “Adversarial security battery” below for what that found, fixed, and left open. Do not mark a criterion as passed solely because a test exists; the battery’s own README documents its requirement-to-test matrix explicitly for this reason.

Compatibility and migration

v3 derivation is preserved to open existing data. The v4 format introduces the private root as a mandatory input; a v4 authentication failure must not trigger an attempt to read the blob as v3.

Migration from v3 to v4 is explicit and resumable for both branch and value blobs. Without the required secrets/noise, a scope remains pending. Do not remove the original before verifying and durably committing the new content.

Importing old snapshots that contain secrets is an explicit migration path, not a silent unlock during ordinary startup. Old backups retain their previous exposure: migrating current data does not retroactively protect them.

Adversarial security battery

A structured adversarial pass ran against the implementation described above, organized around five distinct attacker/state classes: a storage thief with no credentials, a sibling identity that knows the same path/secret/noise strings but has a different private root, the owner unlocked but missing a branch secret, manipulated or stale storage, and a process interrupted mid-write. It is explicit about what it does not claim: no protection against an attacker who already controls an unlocked process, no metadata/traffic-analysis hiding (scope-path keys and chunk sizes stay visible by design), and no independent cryptographic audit of the underlying primitives — see the battery’s own README for the full boundary.

Three real bugs were found and fixed, each with a minimal reproduction and a regression test:

The battery also documents, without changing, two real characteristics that could otherwise look like bugs: noise (~()) reflects the current session’s state rather than a per-write snapshot of it, so declaring noise after a write to the same scope makes that earlier write unreadable until the original context is restored (fails closed, fully recoverable, inherited unchanged from the pre-existing v3 derivation this feature does not modify); and hydrate()’s redacted noise placeholder behaves as an active (permanently mismatching) value rather than “no noise,” so a caller must explicitly re-declare the real value after restoring a snapshot.

Known gaps, left open rather than silently assumed away: there is no rollback/freshness detection for branch ciphertext (an attacker with write access to the encrypted branch store could replace a scope’s current chunk with an older, still validly-authenticated one from the same scope); concurrent writers to the same Monad state directory are not supported and are documented as a limit, not a feature; and this remains adversarial testing by one engineer, not an independent cryptographic audit.

Full detail — attack model, exact commands, the requirement-to-test matrix, and each fix’s reproduction — is in the battery’s own README: me/Typescript/tests/Security/README.md (the .me package), with the process-level persistence tests in modules/monad/Typescript/tests/Security/ (Monad).

Sources and maintenance

This page is the architecture and cross-package coverage guide. The .me document maintains details close to the code. Contract changes must update both references; implementation status should only be updated with code and test evidence. The root’s location, its APIs, and concrete cryptographic decisions still await implementation verification.