{"record":{"id":"9c645d31e52026cb","repo":"jdx/mise","slug":"encrypt-an-external-dotfile-source-instead-of-conf","errorCode":null,"errorMessage":"encrypt an external dotfile source instead of configuration: {path}","messagePattern":"encrypt an external dotfile source instead of configuration: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/files.rs","lineNumber":342,"sourceCode":"        .map(|recipient| {\n            agecrypt::parse_recipient_mode(recipient, interactive)?\n                .ok_or_else(|| eyre::eyre!(\"invalid age recipient: {recipient}\"))\n        })\n        .collect::<Result<Vec<_>>>()?;\n    encrypt(repo, path, object, &scheme, &recipients)\n}\n\n/// Encrypt bytes before they enter Git. Callers may store only the returned\n/// envelope, never the input or the decrypted payload in repository objects.\npub(crate) fn encode(\n    path: &str,\n    mode: &str,\n    content: &[u8],\n    scheme: &str,\n    recipients: &[Box<dyn age::Recipient + Send>],\n) -> Result<Vec<u8>> {\n    if control_file(path) {\n        bail!(\"encrypt an external dotfile source instead of configuration: {path}\");\n    }\n    if !matches!(mode, \"100644\" | \"100755\" | \"120000\") {\n        bail!(\"unsupported encrypted file mode: {path}\");\n    }\n    let inner = Plaintext {\n        path: path.into(),\n        mode: mode.into(),\n        scheme: scheme.into(),\n        content: Bytes(content.to_vec()),\n    };\n    let bytes = rmp_serde::to_vec_named(&inner)?;\n    let outer = Envelope {\n        path: path.into(),\n        mode: mode.into(),\n        scheme: scheme.into(),\n        ciphertext: Bytes(agecrypt::encrypt_bytes(&bytes, recipients)?),\n    };\n    let mut encoded = MAGIC.to_vec();","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/sync/files.rs#L324-L360","documentation":"Thrown by `encode` when asked to encrypt a file that is itself one of the sync system's control/configuration files (`control_file(path)` is true). The sync configuration must stay plaintext so the tool can read it; encrypting it would make the setup store unreadable. Users should encrypt external dotfiles instead.","triggerScenarios":"Calling `encode` (via `encrypt`) on a path for which `control_file(path)` returns true — i.e. the setup configuration file itself is listed for encryption.","commonSituations":"Glob patterns like `~/.config/**` that include the sync config file; explicitly adding the setup store's configuration to the encrypted-files list.","solutions":["Remove the setup configuration file from the encryption list.","Refine the glob so only external dotfiles match.","Keep sync configuration in a dedicated directory excluded from encryption."],"exampleFix":"// before\nencrypt = [\"~/.config/*\"]\n// after\nencrypt = [\"~/.config/*\", \"!~/.config/setup-config.toml\"]","handlingStrategy":"validation","validationCode":"if (isControlFile(path)) {\n  throw new Error(`cannot encrypt setup configuration: ${path}`);\n}\nawait sync.encrypt(path, mode, content);","typeGuard":"function isExternalDotfile(path: string): boolean {\n  return !isControlFile(path);\n}","tryCatchPattern":"try {\n  await sync.encrypt(path, mode, content);\n} catch (e) {\n  if (/instead of configuration/.test(String(e))) {\n    log.warn(`excluded control file from encryption: ${path}`);\n  } else throw e;\n}","preventionTips":["Keep encryption include lists targeted at dotfiles only.","Review expanded glob matches before committing an encryption config.","Maintain an explicit deny-list for control files."],"tags":["encryption","configuration","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}