mdenc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # mdenc
2
+
3
+ **Encrypt your Markdown. Keep your diffs.**
4
+
5
+ mdenc 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.
6
+
7
+ ## What it looks like
8
+
9
+ Say you have some private notes:
10
+
11
+ ```markdown
12
+ # Meeting Notes
13
+
14
+ Discussed the Q3 roadmap. We agreed to prioritize
15
+ the mobile app rewrite.
16
+
17
+ ## Action Items
18
+
19
+ Alice will draft the technical spec by Friday.
20
+ Bob is handling the database migration plan.
21
+
22
+ ## Budget
23
+
24
+ Total allocated: $450k for the quarter.
25
+ ```
26
+
27
+ `mdenc encrypt notes.md -o notes.mdenc` turns it into:
28
+
29
+ ```
30
+ mdenc:v1 salt_b64=tWpYJ1TX... file_id_b64=QtPSfysG... argon2=m=65536,t=3,p=1
31
+ hdrauth_b64=2Iox+FH0IuoSHittEzcxSI8Ew7VUJNIBAP+3RKs3TRg=
32
+ u+C4c6fq3ShPpe0nmAdVdB3gt+Jr45rPChOYd8W3W827Hw6ye1tO7eBh... <- # Meeting Notes
33
+ iFeHLHNGgHT3cBM20/BlPfaDjeY+WL3rZh1unY951Ha/wGHI5D8yYmMi... <- Discussed the Q3...
34
+ dm54GXdXI+MpbgeCpbUQj9x5HYOvJ/wIIymaQxcwgraQO2lwCYUqfUka... <- ## Action Items
35
+ 2W4gqkAK/b/UD9euXLVE4I27+LnxFHdPr7lQajtI5HxC7eED4YUYtoaG... <- Alice will draft...
36
+ JQgoywFO02b4OdkZEKhk5ZjpXyLzJCuIFAU6mi73ZazKhy+qw1Drz6k8... <- ## Budget
37
+ NNdpCjf++ncLe9yrRbotyPUWuib8Oe68xjkaTnEJVNO7snSFS0Z6cGwY... <- Total allocated...
38
+ seal_b64=K7mQ2xR9f4pVnBt5z8wJH3kY6LdWqA0oNc1iEgMvT+s=
39
+ ```
40
+
41
+ Each 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.
42
+
43
+ Now you edit the "Action Items" paragraph and re-encrypt. Here's what `git diff` shows:
44
+
45
+ ```diff
46
+ mdenc:v1 salt_b64=tWpYJ1TX... file_id_b64=QtPSfysG... argon2=m=65536,t=3,p=1
47
+ hdrauth_b64=2Iox+FH0IuoSHittEzcxSI8Ew7VUJNIBAP+3RKs3TRg=
48
+ u+C4c6fq3ShPpe0nmAdVdB3gt+Jr45rPChOYd8W3W827Hw6ye1tO7eBh...
49
+ iFeHLHNGgHT3cBM20/BlPfaDjeY+WL3rZh1unY951Ha/wGHI5D8yYmMi...
50
+ dm54GXdXI+MpbgeCpbUQj9x5HYOvJ/wIIymaQxcwgraQO2lwCYUqfUka...
51
+ -2W4gqkAK/b/UD9euXLVE4I27+LnxFHdPr7lQajtI5HxC7eED4YUYtoaG...
52
+ +29eDDzd58m8BtTV3PA3zyetTyuhL3Qqimlz7APvXDZsGL/rtZtld9R0u...
53
+ JQgoywFO02b4OdkZEKhk5ZjpXyLzJCuIFAU6mi73ZazKhy+qw1Drz6k8...
54
+ NNdpCjf++ncLe9yrRbotyPUWuib8Oe68xjkaTnEJVNO7snSFS0Z6cGwY...
55
+ -seal_b64=K7mQ2xR9f4pVnBt5z8wJH3kY6LdWqA0oNc1iEgMvT+s=
56
+ +seal_b64=Px8nVdR2aLw0tYj3Km5qFh9sBcW7e6Uo4gZi1DxAf+E=
57
+ ```
58
+
59
+ One 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.
60
+
61
+ ## Why
62
+
63
+ You 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.
64
+
65
+ mdenc encrypts at paragraph granularity. Unchanged paragraphs produce identical ciphertext, so git only tracks the paragraphs you actually touched.
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ npm install mdenc
71
+ ```
72
+
73
+ ## CLI
74
+
75
+ ```bash
76
+ # Encrypt
77
+ mdenc encrypt notes.md -o notes.mdenc
78
+
79
+ # Decrypt
80
+ mdenc decrypt notes.mdenc -o notes.md
81
+
82
+ # Re-encrypt after editing (unchanged paragraphs keep same ciphertext)
83
+ mdenc decrypt notes.mdenc -o notes.md
84
+ # ... edit notes.md ...
85
+ mdenc encrypt notes.md -o notes.mdenc
86
+
87
+ # Verify file integrity
88
+ mdenc verify notes.mdenc
89
+ ```
90
+
91
+ Password is read from `MDENC_PASSWORD` env var or prompted interactively (no echo).
92
+
93
+ ## Library
94
+
95
+ ```typescript
96
+ import { encrypt, decrypt, verifySeal } from 'mdenc';
97
+
98
+ // Encrypt (always includes integrity seal)
99
+ const encrypted = await encrypt(markdown, password);
100
+
101
+ // Decrypt (verifies seal automatically)
102
+ const plaintext = await decrypt(encrypted, password);
103
+
104
+ // Re-encrypt with diff optimization
105
+ const updated = await encrypt(editedMarkdown, password, {
106
+ previousFile: encrypted,
107
+ });
108
+
109
+ // Verify integrity without decrypting
110
+ const ok = await verifySeal(encrypted, password);
111
+ ```
112
+
113
+ ## How it works
114
+
115
+ 1. Your Markdown is split into chunks at paragraph boundaries (runs of 2+ newlines)
116
+ 2. Each chunk is encrypted with XChaCha20-Poly1305 using a deterministic nonce derived from the content
117
+ 3. The output is plain UTF-8 text -- one base64 line per chunk, plus a seal HMAC
118
+ 4. Same content + same keys = same ciphertext, so unchanged chunks produce identical output and minimal diffs
119
+ 5. The seal HMAC covers all lines, detecting reordering, truncation, and rollback on decrypt
120
+
121
+ The password is stretched with Argon2id (64 MiB, 3 iterations). Keys are derived via HKDF-SHA256 with separate keys for encryption, header authentication, and nonce derivation.
122
+
123
+ ## What leaks
124
+
125
+ mdenc is designed for diff-friendliness, not metadata hiding. An observer can see:
126
+
127
+ - How many paragraphs your document has
128
+ - Approximate size of each paragraph
129
+ - Which paragraphs changed between commits
130
+ - Identical paragraphs within a file (they produce identical ciphertext)
131
+
132
+ The *content* of your paragraphs stays confidential.
133
+
134
+ ## Docs
135
+
136
+ - [SECURITY.md](SECURITY.md) -- threat model, crypto details, accepted tradeoffs
137
+ - [SPECIFICATION.md](SPECIFICATION.md) -- wire format for implementers
138
+
139
+ ## License
140
+
141
+ ISC