DotweaveDotweave
DocsGitHub
DotweaveDotweave
    • Introduction
    • Install Dotweave
    • Set up your first sync
    • Set up a second device
    • How syncing works
    • Directory and repository layout
    • Sync modes
    • Profiles
    • Platform-specific paths
    • Secrets and encryption
    • Track files and directories
    • Push, review, and commit day to day
    • Keep several devices in sync
    • Enable shell autocomplete
    • Install the agent skill
    • Upgrade config and repository formats
    • Troubleshoot a broken sync
    • dotweave init
    • dotweave track
    • dotweave push
    • dotweave pull
    • dotweave status
    • dotweave untrack
    • dotweave cd
    • dotweave profile
    • dotweave doctor
    • dotweave autocomplete
    • dotweave skill
    • manifest.jsonc
    • settings.jsonc
    • Environment variables and paths
    • Error messages
DocsGitHub
  1. Dotweave/
  2. Secrets and encryption

Secrets and encryption

How Dotweave encrypts secret artifacts with its bundled age implementation, and what happens if you lose keys.txt.

What a secret artifact is

A tracked entry in secret mode never reaches the repository in the clear. push encrypts the file content and stores it at profiles/<profile>/<repoPath>.dotweave.secret. The artifact is ASCII-armored, so it begins with the line -----BEGIN AGE ENCRYPTED FILE----- and remains plain text that git can transport, diff, and merge without binary handling.

Dotweave carries its own age implementation in packages/cli/lib/src/crypto/age/: the X25519 recipient stanza, ChaCha20-Poly1305 payload encryption, bech32 key strings, and the ASCII armor codec. Nothing shells out, and you do not install an age binary to use secret mode.

Encryption applies to the bytes of a regular file. A secret path must be a regular file, not a symlink, because a symlink holds a link target rather than content to encrypt.

Only the content is encrypted. File names, directory names, and the repository path stay readable to anyone who can read the repository, so .ssh/config.dotweave.secret still announces that you track an SSH configuration. When the path itself is sensitive, override repoPath so the stored name reveals nothing.

Identity and recipients

Two pieces of key material do the work, and they live in deliberately different places.

The identity is your private key. It sits at <dotweave-home>/keys.txt, one AGE-SECRET-KEY-... string per line, with blank lines and # comment lines allowed. That file is outside the sync directory, so git never sees it, and Dotweave refuses to track any path that contains it with TARGET_OVERLAPS_IDENTITY.

The recipients are public keys, stored in manifest.jsonc under age.recipients with at least one entry. dotweave init derives the recipient from the identity and records it there, so a fresh setup can already encrypt and decrypt.

The two sides are used asymmetrically. push encrypts to every recipient listed in the manifest. pull loads every identity in keys.txt and decrypts with whichever one matches, so a single usable key is enough. Because a file encrypted to several recipients can be opened by any of them independently, several machines can share one repository without sharing one private key.

push also decrypts the stored artifact and compares plaintext before writing. An unchanged secret produces no new ciphertext, which keeps the git history free of churn. The comparison ignores the recipient list, so editing age.recipients alone does not rewrite artifacts whose content did not change.

To give another machine its own key pair:

  1. On the new machine, run dotweave init with no repository argument. It generates keys.txt and records the derived age1... recipient in the local manifest.jsonc. Copy that recipient value, and keep a copy of keys.txt somewhere outside the dotweave home directory.

  2. On a machine that can already decrypt, add the new recipient to age.recipients in the shared repository's manifest.jsonc, delete the .dotweave.secret artifacts you want re-encrypted, then run dotweave push and commit the result with git.

  3. Back on the new machine, connect the shared repository and hand its own key back in.

    dotweave init <your-repo-url> --force --key-file ~/dotweave.agekey

Track a file as a secret

secret is a per-entry sync mode, so you choose it when you track the path:

dotweave track ~/.ssh/config --mode secret
dotweave push

Re-tracking the same target updates only the fields you pass, so dotweave track ~/.ssh/config --mode normal moves an entry back out of secret mode and keeps its repoPath, permission, and profiles. Sync modes covers how a nested entry inherits a mode from its parent directory.

On a second machine, the identity has to arrive before the secrets can be read. Pass it as a file:

dotweave init <your-repo-url> --key-file ~/dotweave.agekey
dotweave pull

In an interactive terminal you can instead run dotweave init <your-repo-url> and paste the key at the prompt. Without a key, connecting a repository that already lists recipients fails with INIT_AGE_IDENTITY_REQUIRED.

Back up keys.txt

Dotweave never syncs keys.txt. It is not stored in the repository and no command copies it between machines, so moving it is your job: a password manager, an encrypted volume, or scp over a channel you trust.

Losing every copy of the key is final. The artifacts stay in git, and nothing in Dotweave or age can open them again. Your only recovery is the plaintext still sitting on a machine that has a working identity, which is why the backup belongs somewhere Dotweave does not manage.

dotweave init --force deletes the existing identity file along with the sync directory and the settings file. Run it against a machine holding your only copy of keys.txt and the key is gone, together with access to every secret artifact encrypted only to it. Save the key elsewhere first, or pass it back with --key-file in the same command.

dotweave doctor reports whether an identity file exists at the resolved path, which catches a missing or misplaced keys.txt before a pull needs it.

When decryption fails

Four failures account for most secret-related errors, and the message names which one you hit.

Failed to decrypt a secret artifact. means the identities in keys.txt match none of the artifact's recipients, or the stored data is corrupt. Check that the key you transported is the one whose recipient is listed in age.recipients.

No age identities were found in the configured identity file. means keys.txt exists but holds only blank lines and # comments. Add a private key, or run dotweave init to generate one.

Secret sync path is stored as a plain artifact in the repository. means the manifest says secret while the stored file is not encrypted, usually because the mode changed after the artifact was written. The reverse, Plain sync path is stored as a secret artifact in the repository., means the manifest says normal while an encrypted artifact is still in place. Both clear up once you push from a machine that can read the entry, or remove the stale artifact.

Implementation and reference

The age v1 implementation lives in packages/cli/lib/src/crypto/age/, the encrypt and decrypt entry points in lib/src/lib/crypto.dart, and the identity path resolution in lib/src/config/identity_file.dart. Artifact comparison and writing happen in lib/src/services/repo_artifacts.dart.

Read manifest.jsonc for the age.recipients field beside the rest of the configuration, or dotweave init for what --key-file and --force do to an existing setup.

← PreviousConceptsPlatform-specific pathsGive one tracked entry a different local path, mode, or permission per operating system.→ NextGuidesTrack files and directoriesRegister targets under your home directory, override their repository path, adjust nested paths, and stop tracking.

On this page

  1. What a secret artifact is
  2. Identity and recipients
  3. Track a file as a secret
  4. Back up keys.txt
  5. When decryption fails
  6. Implementation and reference