{"record":{"id":"a977735d92c03173","repo":"jdx/mise","slug":"cannot-publish-encrypted-path-has-an-unencrypt","errorCode":null,"errorMessage":"cannot publish: encrypted path {} has an unencrypted or invalid version in commit {commit}; encrypting the latest version does not erase earlier plaintext. Review and explicitly rewrite or replace that history before connecting it to origin; mise will not rewrite it automatically","messagePattern":"cannot publish: encrypted path (.+?) has an unencrypted or invalid version in commit (.+?); encrypting the latest version does not erase earlier plaintext\\. Review and explicitly rewrite or replace that history before connecting it to origin; mise will not rewrite it automatically","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/system/history/sync/files.rs","lineNumber":131,"sourceCode":"            if !protected.iter().any(|path| {\n                entry.path == *path\n                    || entry\n                        .path\n                        .strip_prefix(path)\n                        .is_some_and(|rest| rest.starts_with('/'))\n            }) || !checked.insert((entry.path.clone(), entry.mode.clone(), entry.oid.clone()))\n            {\n                continue;\n            }\n            let object = (entry.mode, entry.oid);\n            let valid =\n                envelope(repo, &object, agecrypt::MAX_ENCRYPTED_BYTES)?.is_some_and(|outer| {\n                    outer.path == entry.path\n                        && matches!(outer.mode.as_str(), \"100644\" | \"100755\" | \"120000\")\n                        && outer.ciphertext.0.starts_with(b\"age-encryption.org/v1\\n\")\n                });\n            if !valid {\n                bail!(\n                    \"cannot publish: encrypted path {} has an unencrypted or invalid version in commit {commit}; encrypting the latest version does not erase earlier plaintext. Review and explicitly rewrite or replace that history before connecting it to origin; mise will not rewrite it automatically\",\n                    entry.path\n                );\n            }\n        }\n    }\n    if let Ok(mut cache) = AUDITS.lock() {\n        // One watcher normally uses one repository. Bound temporary onboarding\n        // probes without adding another durable history or state file.\n        if cache.len() >= 64 && !cache.contains_key(repo.dir()) {\n            cache.pop_first();\n        }\n        cache.insert(\n            repo.dir().to_path_buf(),\n            VerifiedAncestry {\n                head: head.into(),\n                protected,\n            },","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/sync/files.rs#L113-L149","documentation":"audit_history verifies that every commit touching an encrypted path contains a validly encrypted version of that path (age-envelope with a recognized mode and the age header). If any historical version of the path is unencrypted or malformed, publishing is refused: encrypting the latest version would not erase plaintext from earlier commits, and mise will not rewrite history automatically.","triggerScenarios":"Connecting a setup-history repo to origin where an encrypted path had a plaintext (or corrupt/non-age) blob in any commit in its history; encryption enabled after the path was already tracked unencrypted.","commonSituations":"User enables encryption on an existing history repo; a commit snuck in with a raw config before the encrypt step; the ciphertext blob is truncated or not age-formatted.","solutions":["Rewrite history to encrypt or purge the plaintext versions (e.g. `git filter-repo` or an interactive rebase re-committing the file encrypted), then re-run publish.","Alternatively start a fresh history: replace the repo/ref and push the newly encrypted baseline.","Inspect `git log --follow -- <path>` in the history repo to locate the offending commit and version before rewriting."],"exampleFix":"// before\n# plaintext config committed in history, encryption enabled later\n// after\ngit filter-repo --path secrets.env --invert-paths   # or re-commit encrypted across history\nmise pull  # audit now passes","handlingStrategy":"validation","validationCode":"# before publishing, check every historical version of the path is encrypted\ngit log --format=%H -- path/to/secrets.env |\n  while read c; do git show \"$c\":path/to/secrets.env |\n    head -c 22 | grep -q 'age-encryption.org/v1' || echo \"plaintext in $c\"; done","typeGuard":null,"tryCatchPattern":"if let Err(e) = publish() {\n    if e.to_string().starts_with(\"cannot publish: encrypted path\") {\n        // rewrite history (git filter-repo / rebase) before retrying\n    }\n}","preventionTips":["Enable encryption before first commit of sensitive paths","Never commit plaintext versions of paths later marked encrypted","Verify blobs start with the age header when importing history"],"tags":["encryption","git-history","security","publish"],"backgroundTag":"checksum-mismatch","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}