{"id":46189977,"url":"https://github.com/yogh-io/mdenc","last_synced_at":"2026-03-07T04:01:11.762Z","repository":{"id":341295755,"uuid":"1169550862","full_name":"yogh-io/mdenc","owner":"yogh-io","description":"Diff-friendly Markdown encryption for git. Paragraph-granular XChaCha20-Poly1305.","archived":false,"fork":false,"pushed_at":"2026-03-03T12:47:50.000Z","size":239,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-06T05:04:07.229Z","etag":null,"topics":["cli","encryption","git","markdown","nodejs","scrypt","xchacha20-poly1305"],"latest_commit_sha":null,"homepage":"https://yogh-io.github.io/mdenc/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yogh-io.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-28T21:19:36.000Z","updated_at":"2026-03-06T03:29:16.000Z","dependencies_parsed_at":"2026-03-05T02:00:30.455Z","dependency_job_id":null,"html_url":"https://github.com/yogh-io/mdenc","commit_stats":null,"previous_names":["yogh-io/mdenc-v1","yogh-io/mdenc"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/yogh-io/mdenc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yogh-io%2Fmdenc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yogh-io%2Fmdenc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yogh-io%2Fmdenc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yogh-io%2Fmdenc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yogh-io","download_url":"https://codeload.github.com/yogh-io/mdenc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yogh-io%2Fmdenc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30207389,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T03:24:23.086Z","status":"ssl_error","status_checked_at":"2026-03-07T03:23:11.444Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","encryption","git","markdown","nodejs","scrypt","xchacha20-poly1305"],"created_at":"2026-03-03T00:11:58.639Z","updated_at":"2026-03-07T04:01:11.704Z","avatar_url":"https://github.com/yogh-io.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mdenc\n\n**Encrypt your Markdown. Keep your diffs.**\n\n[**Live Demo**](https://yogh-io.github.io/mdenc/) | [npm](https://www.npmjs.com/package/mdenc) | [Specification](SPECIFICATION.md) | [Security](SECURITY.md)\n\nmdenc lets you store encrypted Markdown in git without losing the ability to see *what changed*. Edit one paragraph, and only that paragraph changes in the encrypted output. Your `git log` stays useful. Your pull request reviews stay sane.\n\n## What it looks like\n\nSay you have some private notes:\n\n```markdown\n# Meeting Notes\n\nDiscussed the Q3 roadmap. We agreed to prioritize\nthe mobile app rewrite.\n\n## Action Items\n\nAlice will draft the technical spec by Friday.\nBob is handling the database migration plan.\n\n## Budget\n\nTotal allocated: $450k for the quarter.\n```\n\n`mdenc encrypt notes.md -o notes.mdenc` turns it into:\n\n```\nmdenc:v1 salt_b64=1z94qTI8... file_id_b64=eLP+c5cP... scrypt=N=16384,r=8,p=1\nhdrauth_b64=griicznFYhBTVCeq1lpvB+J73wsJJbheGghxNOIJlu0=\nQnp4sPf/aN1z/VSkZ8yjGWwk0ZLqwpFAJBzbLOcoKyafqUbMp4Y7WMqF...    \u003c- # Meeting Notes\nnD1KIHOMX5VhlSU4USUxWHTrl2Qi6cev/b6J5YJR9C78XHqwnNHVxHgW...    \u003c- Discussed the Q3...\nHes/oW+FeONHytgUa7c9ZzdF4d/w7Ei0tnGiJmqPX0DniJaiV0g0yMhc...    \u003c- ## Action Items\nyT7vkHbaXHR390bWz1d/qcK6yVeF3p5/quvW7BOx4hfoU0F2P0/oNAkR...    \u003c- Alice will draft...\ndkM7awElU/pfUYs1goxQFlgcyUq8FNHcnZrU76tPaygh7bdgYjdrC7Wx...    \u003c- ## Budget\nZBRV9kdXm7gNiF4BvI9eklrtTjhkI9tLHu001eQUevoZbeKQ8Y70basB...    \u003c- Total allocated...\nseal_b64=29ylXnDTWQ09nzZjvoYtYUpfyr4X4NLONxpT/HOC9TU=\n```\n\nEach paragraph becomes one line of base64. A seal HMAC at the end protects the file's integrity. The file is plain UTF-8 text that git tracks normally.\n\nNow you edit the \"Action Items\" paragraph and re-encrypt. Here's what `git diff` shows:\n\n```diff\n mdenc:v1 salt_b64=1z94qTI8... file_id_b64=eLP+c5cP... scrypt=N=16384,r=8,p=1\n hdrauth_b64=griicznFYhBTVCeq1lpvB+J73wsJJbheGghxNOIJlu0=\n Qnp4sPf/aN1z/VSkZ8yjGWwk0ZLqwpFAJBzbLOcoKyafqUbMp4Y7WMqF...\n nD1KIHOMX5VhlSU4USUxWHTrl2Qi6cev/b6J5YJR9C78XHqwnNHVxHgW...\n Hes/oW+FeONHytgUa7c9ZzdF4d/w7Ei0tnGiJmqPX0DniJaiV0g0yMhc...\n-yT7vkHbaXHR390bWz1d/qcK6yVeF3p5/quvW7BOx4hfoU0F2P0/oNAkR...\n+1RgyC3rXcjykvoL0GgsQsHBmxy5axdD/tqMnicJGjit66+o5bjP1vSbG...\n dkM7awElU/pfUYs1goxQFlgcyUq8FNHcnZrU76tPaygh7bdgYjdrC7Wx...\n ZBRV9kdXm7gNiF4BvI9eklrtTjhkI9tLHu001eQUevoZbeKQ8Y70basB...\n-seal_b64=29ylXnDTWQ09nzZjvoYtYUpfyr4X4NLONxpT/HOC9TU=\n+seal_b64=iNhYjNp69tyv4tkzgDJK5Fh2h1WLgIs3Y1IPRKcpQsE=\n```\n\nOne paragraph changed, one line in the diff (plus the seal updates). Even inserting a new paragraph between existing ones only adds one line -- surrounding chunks stay unchanged. Compare that to GPG, where the entire file would show as changed.\n\n## Why\n\nYou want to keep private notes, journals, or sensitive docs in a git repo. GPG-encrypting the whole file works, but every tiny edit produces a completely different blob. The entire file shows as changed in every commit.\n\nmdenc encrypts at paragraph granularity. Unchanged paragraphs produce identical ciphertext, so git only tracks the paragraphs you actually touched.\n\n## Install\n\n```bash\nnpm install mdenc\n```\n\n## CLI\n\n```bash\n# Encrypt\nmdenc encrypt notes.md -o notes.mdenc\n\n# Decrypt\nmdenc decrypt notes.mdenc -o notes.md\n\n# Re-encrypt after editing (unchanged paragraphs keep same ciphertext)\nmdenc decrypt notes.mdenc -o notes.md\n# ... edit notes.md ...\nmdenc encrypt notes.md -o notes.mdenc\n\n# Verify file integrity\nmdenc verify notes.mdenc\n```\n\nPassword is read from `MDENC_PASSWORD` env var or prompted interactively (no echo).\n\n## Git Integration\n\nmdenc uses git's native **smudge/clean filter** to transparently encrypt and decrypt `.md` files. You edit plaintext locally; git stores ciphertext in the repository.\n\n```bash\n# Set up git smudge/clean filter and textconv diff\nmdenc init\n\n# Generate a random password into .mdenc-password\nmdenc genpass [--force]\n\n# Mark a directory -- .md files inside will be filtered\nmdenc mark docs/private\n\n# See which files are configured for encryption\nmdenc status\n\n# Remove git filter configuration\nmdenc remove-filter\n```\n\nAfter `mdenc init` and `mdenc mark`, the workflow is transparent: the **clean filter** encrypts `.md` files when they're staged (`git add`), and the **smudge filter** decrypts them on checkout. You always see plaintext in your working directory. The custom diff driver shows plaintext diffs of encrypted content.\n\n## Library\n\n```typescript\nimport { encrypt, decrypt, verifySeal } from 'mdenc';\n\n// Encrypt (always includes integrity seal)\nconst encrypted = await encrypt(markdown, password);\n\n// Decrypt (verifies seal automatically)\nconst plaintext = await decrypt(encrypted, password);\n\n// Re-encrypt with diff optimization\nconst updated = await encrypt(editedMarkdown, password, {\n  previousFile: encrypted,\n});\n\n// Verify integrity without decrypting\nconst ok = await verifySeal(encrypted, password);\n```\n\n## How it works\n\n1. Your Markdown is split into chunks at paragraph boundaries (runs of 2+ newlines)\n2. Each chunk is encrypted with XChaCha20-Poly1305 using a deterministic nonce derived from the content\n3. The output is plain UTF-8 text -- one base64 line per chunk, plus a seal HMAC\n4. Same content + same keys = same ciphertext, so unchanged chunks produce identical output and minimal diffs\n5. The seal HMAC covers all lines, detecting reordering, truncation, and rollback on decrypt\n\nThe password is stretched with scrypt (N=16384, r=8, p=1). Keys are derived via HKDF-SHA256 with separate keys for encryption, header authentication, and nonce derivation.\n\n## What leaks\n\nmdenc is designed for diff-friendliness, not metadata hiding. An observer can see:\n\n- How many paragraphs your document has\n- Approximate size of each paragraph\n- Which paragraphs changed between commits\n- Identical paragraphs within a file (they produce identical ciphertext)\n\nThe *content* of your paragraphs stays confidential.\n\n## Docs\n\n- [SECURITY.md](SECURITY.md) -- threat model, crypto details, accepted tradeoffs\n- [SPECIFICATION.md](SPECIFICATION.md) -- wire format for implementers\n\n## License\n\nISC\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyogh-io%2Fmdenc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyogh-io%2Fmdenc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyogh-io%2Fmdenc/lists"}