narrarium-astro-reader 0.1.30 → 0.1.32

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.
@@ -2,6 +2,12 @@
2
2
  export declare function getBuildSalt(): Buffer;
3
3
  /** Base64-encoded build salt, ready to embed in an HTML attribute. */
4
4
  export declare function getBuildSaltBase64(): string;
5
+ /**
6
+ * Known plaintext embedded (encrypted) in the built HTML so the browser can
7
+ * verify a password by attempting decryption rather than comparing a fast hash.
8
+ * This forces brute-force attempts to pay the full PBKDF2 cost every time.
9
+ */
10
+ export declare const CANARY_PLAINTEXT = "narrarium-ok";
5
11
  export interface EncryptedChunk {
6
12
  /** Base64-encoded 12-byte random IV. */
7
13
  iv: string;
@@ -16,4 +22,12 @@ export interface EncryptedChunk {
16
22
  * verify integrity without any additional framing.
17
23
  */
18
24
  export declare function encryptString(plaintext: string, password: string): EncryptedChunk;
25
+ /**
26
+ * Encrypt the canary plaintext with the same PBKDF2-derived build key.
27
+ *
28
+ * Embed `iv` and `ct` in the built HTML as `data-canary-iv` / `data-canary-ct`.
29
+ * The browser verifies the password by decrypting the canary and checking that
30
+ * the result equals `CANARY_PLAINTEXT` — no fast-hash oracle in the HTML.
31
+ */
32
+ export declare function encryptCanary(password: string): EncryptedChunk;
19
33
  //# sourceMappingURL=content-crypto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content-crypto.d.ts","sourceRoot":"","sources":["../../src/lib/content-crypto.ts"],"names":[],"mappings":"AAcA,mFAAmF;AACnF,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAMD,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,CAWjF"}
1
+ {"version":3,"file":"content-crypto.d.ts","sourceRoot":"","sources":["../../src/lib/content-crypto.ts"],"names":[],"mappings":"AAcA,mFAAmF;AACnF,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAMD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,CAWjF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAE9D"}
@@ -24,6 +24,12 @@ export function getBuildSaltBase64() {
24
24
  function deriveKey(password, salt) {
25
25
  return pbkdf2Sync(password, salt, 100_000, 32, "sha256");
26
26
  }
27
+ /**
28
+ * Known plaintext embedded (encrypted) in the built HTML so the browser can
29
+ * verify a password by attempting decryption rather than comparing a fast hash.
30
+ * This forces brute-force attempts to pay the full PBKDF2 cost every time.
31
+ */
32
+ export const CANARY_PLAINTEXT = "narrarium-ok";
27
33
  /**
28
34
  * Encrypt a UTF-8 string with AES-256-GCM using PBKDF2-derived key.
29
35
  *
@@ -43,4 +49,14 @@ export function encryptString(plaintext, password) {
43
49
  ct: Buffer.concat([body, tag]).toString("base64"),
44
50
  };
45
51
  }
52
+ /**
53
+ * Encrypt the canary plaintext with the same PBKDF2-derived build key.
54
+ *
55
+ * Embed `iv` and `ct` in the built HTML as `data-canary-iv` / `data-canary-ct`.
56
+ * The browser verifies the password by decrypting the canary and checking that
57
+ * the result equals `CANARY_PLAINTEXT` — no fast-hash oracle in the HTML.
58
+ */
59
+ export function encryptCanary(password) {
60
+ return encryptString(CANARY_PLAINTEXT, password);
61
+ }
46
62
  //# sourceMappingURL=content-crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"content-crypto.js","sourceRoot":"","sources":["../../src/lib/content-crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;;GAQG;AAEH,IAAI,UAAU,GAAkB,IAAI,CAAC;AAErC,mFAAmF;AACnF,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,UAAU,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,8DAA8D,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,kBAAkB;IAChC,OAAO,YAAY,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,IAAY;IAC/C,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC;AASD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,QAAgB;IAC/D,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,8BAA8B;IAC/D,OAAO;QACL,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACzB,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAClD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"content-crypto.js","sourceRoot":"","sources":["../../src/lib/content-crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;;GAQG;AAEH,IAAI,UAAU,GAAkB,IAAI,CAAC;AAErC,mFAAmF;AACnF,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,UAAU,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,8DAA8D,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,kBAAkB;IAChC,OAAO,YAAY,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,IAAY;IAC/C,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAS/C;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,QAAgB;IAC/D,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,8BAA8B;IAC/D,OAAO;QACL,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACzB,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAClD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,aAAa,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC"}
@@ -5,11 +5,4 @@ export declare function isFullCanonMode(): boolean;
5
5
  * content encryption. Never embedded in the built HTML.
6
6
  */
7
7
  export declare function getReaderPassword(): string | null;
8
- /**
9
- * Returns a SHA-256 hex hash of the NARRARIUM_READER_PASSWORD env var,
10
- * or null when the variable is not set. The hash is embedded in the built
11
- * HTML and compared against the user's input at runtime via SubtleCrypto
12
- * as a fast pre-filter before the more expensive PBKDF2 key derivation.
13
- */
14
- export declare function getReaderPasswordHash(): string | null;
15
8
  //# sourceMappingURL=reader-mode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reader-mode.d.ts","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,IAAI,OAAO,CAMzC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CAIrD"}
1
+ {"version":3,"file":"reader-mode.d.ts","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,IAAI,OAAO,CAMzC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD"}
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import { readReaderEnv } from "./env.js";
3
2
  export function isFullCanonMode() {
4
3
  const raw = String(readReaderEnv(["NARRARIUM_READER_CANON_MODE", "NARRARIUM_READER_ALLOW_FULL_CANON"]) ?? "")
@@ -14,16 +13,4 @@ export function isFullCanonMode() {
14
13
  export function getReaderPassword() {
15
14
  return readReaderEnv(["NARRARIUM_READER_PASSWORD"]) ?? null;
16
15
  }
17
- /**
18
- * Returns a SHA-256 hex hash of the NARRARIUM_READER_PASSWORD env var,
19
- * or null when the variable is not set. The hash is embedded in the built
20
- * HTML and compared against the user's input at runtime via SubtleCrypto
21
- * as a fast pre-filter before the more expensive PBKDF2 key derivation.
22
- */
23
- export function getReaderPasswordHash() {
24
- const raw = readReaderEnv(["NARRARIUM_READER_PASSWORD"]);
25
- if (!raw)
26
- return null;
27
- return createHash("sha256").update(raw).digest("hex");
28
- }
29
16
  //# sourceMappingURL=reader-mode.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reader-mode.js","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,6BAA6B,EAAE,mCAAmC,CAAC,CAAC,IAAI,EAAE,CAAC;SAC1G,IAAI,EAAE;SACN,WAAW,EAAE,CAAC;IAEjB,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,UAAU,CAAC;AACnG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,aAAa,CAAC,CAAC,2BAA2B,CAAC,CAAC,IAAI,IAAI,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,GAAG,GAAG,aAAa,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACzD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC"}
1
+ {"version":3,"file":"reader-mode.js","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,6BAA6B,EAAE,mCAAmC,CAAC,CAAC,IAAI,EAAE,CAAC;SAC1G,IAAI,EAAE;SACN,WAAW,EAAE,CAAC;IAEjB,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,UAAU,CAAC;AACnG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,aAAa,CAAC,CAAC,2BAA2B,CAAC,CAAC,IAAI,IAAI,CAAC;AAC9D,CAAC"}
@@ -85,6 +85,7 @@ The scaffold creates a local \`.env\` with the book root already filled in. Adju
85
85
  \`\`\`bash
86
86
  NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}
87
87
  # NARRARIUM_READER_CANON_MODE=full
88
+ # NARRARIUM_READER_PASSWORD=your-secret-password
88
89
  # EPUBCHECK_CMD=epubcheck
89
90
  # EPUBCHECK_JAR=/absolute/path/to/epubcheck.jar
90
91
  \`\`\`
@@ -101,6 +102,29 @@ It also watches the linked book repository, regenerates the EPUB when canon file
101
102
 
102
103
  By default the reader uses a spoiler-safe public mode. If you want a private full-canon deployment, enable \`NARRARIUM_READER_CANON_MODE=full\` before running dev or build.
103
104
 
105
+ ## Password protection
106
+
107
+ Set \`NARRARIUM_READER_PASSWORD\` to encrypt all prose content at build time using AES-256-GCM.
108
+ Visitors must enter the correct password before any text is revealed.
109
+
110
+ For local development, add the variable to your \`.env\` file:
111
+
112
+ \`\`\`bash
113
+ NARRARIUM_READER_PASSWORD=your-secret-password
114
+ \`\`\`
115
+
116
+ For GitHub Pages deployment, add it to the \`Build site\` step in \`.github/workflows/deploy-pages.yml\`:
117
+
118
+ \`\`\`yaml
119
+ - name: Build site
120
+ env:
121
+ SITE_BASE: /\${{ github.event.repository.name }}/
122
+ NARRARIUM_READER_PASSWORD: \${{ secrets.NARRARIUM_READER_PASSWORD }}
123
+ run: npm run build
124
+ \`\`\`
125
+
126
+ Store the actual password as a repository secret: **Settings → Secrets and variables → Actions → New repository secret**.
127
+
104
128
  ## Doctor
105
129
 
106
130
  \`\`\`bash
@@ -132,6 +156,7 @@ function buildReaderEnvFile(bookRoot) {
132
156
  return [
133
157
  `NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}`,
134
158
  "# NARRARIUM_READER_CANON_MODE=full",
159
+ "# NARRARIUM_READER_PASSWORD=your-secret-password",
135
160
  "# EPUBCHECK_CMD=epubcheck",
136
161
  "# EPUBCHECK_JAR=/absolute/path/to/epubcheck.jar",
137
162
  "",
@@ -205,6 +230,7 @@ jobs:
205
230
  - name: Build site
206
231
  env:
207
232
  ${envBlock}
233
+ # NARRARIUM_READER_PASSWORD: \${{ secrets.NARRARIUM_READER_PASSWORD }}
208
234
  run: npm run build
209
235
 
210
236
  - name: Upload artifact
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,6BAA6B,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AAStH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAiB,EAAE,UAA2B,EAAE;IACvF,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACxE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,MAAM,wBAAwB,CAAC,WAAW,CAAC,CAAC;IAC7F,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IAE7D,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxE,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7E,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChF,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/E,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAC/F,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACzF,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,eAAe,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;QACvG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC5F,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAChH,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QACxG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAClG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC1G,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KACvG,CAAC,CAAC;IAEH,MAAM,SAAS,CACb,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EACrC,IAAI,CAAC,SAAS,CACZ;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,aAAa,EAAE,gCAAgC;YAC/C,MAAM,EAAE,2BAA2B;YACnC,GAAG,EAAE,wBAAwB;YAC7B,KAAK,EAAE,oCAAoC;YAC3C,OAAO,EAAE,eAAe;SACzB;QACD,YAAY,EAAE;YACZ,WAAW,EAAE,cAAc;YAC3B,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,QAAQ;YAClB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,SAAS;SAClB;QACD,eAAe,EAAE;YACf,aAAa,EAAE,SAAS;YACxB,UAAU,EAAE,QAAQ;SACrB;KACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,EACR,MAAM,CACP,CAAC;IAEF,MAAM,SAAS,CACb,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,CAAC,EACrD,kCAAkC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EACxE,MAAM,CACP,CAAC;IAEF,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,iBAAiB,CAAC,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9G,MAAM,SAAS,CACb,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,CAAC,EACjE,kBAAkB,CAAC,WAAW,CAAC,EAC/B,MAAM,CACP,CAAC;IAEF,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,GAAG,WAAW,IAAI,EAAE,MAAM,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,SAAS,CACb,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EACrC,kBAAkB,CAAC,QAAQ,CAAC,EAC5B,MAAM,CACP,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,kBAAkB,CAAC,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IACtE,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;QAC5B,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,0DAA0D,EAAE,MAAM,CAAC,CAAC;IACzH,MAAM,SAAS,CACb,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,EAClC,iBAAiB,CAAC,QAAQ,CAAC,EAC3B,MAAM,CACP,CAAC;IAEF,OAAO;QACL,UAAU;QACV,WAAW;QACX,cAAc;QACd,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACzC,OAAO;;;;;;;;;sBASa,OAAO,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgEtC,CAAC;AACF,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAgB;IAC7C,OAAO,kCAAkC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC;AAClF,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAgB;IAC1C,OAAO;QACL,uBAAuB,OAAO,CAAC,QAAQ,CAAC,EAAE;QAC1C,oCAAoC;QACpC,kDAAkD;QAClD,2BAA2B;QAC3B,iDAAiD;QACjD,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,kBAAkB,CAAC,eAA8B,EAAE,QAAgB,EAAE,UAAkB;IAC9F,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAClE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,GAAG,IAAI,CAAC;QACf,MAAM,YAAY,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACvD,IAAI,YAAY,IAAI,CAAC,6BAA6B,CAAC,YAAY,CAAC,IAAI,8BAA8B,CAAC,YAAY,EAAE,UAAU,CAAC,EAAE,CAAC;YAC7H,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,WAAW,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,KAAK,eAAe,EAAE,CAAC;AACzE,CAAC;AAED,SAAS,kBAAkB,CAAC,WAAoB;IAC9C,MAAM,QAAQ,GAAG,WAAW;QAC1B,CAAC,CAAC,CAAC,wBAAwB,EAAE,+BAA+B,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACrF,CAAC,CAAC,4DAA4D,CAAC;IAEjE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuCP,QAAQ;;;;;;;;;;;;;;;;;;;CAmBT,CAAC;AACF,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;SACnC,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,uBAAuB,CAAC;IACtD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,wBAAwB,CAAC,WAAmB;IACzD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA8C,CAAC;IAC5E,OAAO,MAAM,CAAC,YAAY,EAAE,CAAC,WAAW,CAAC,IAAI,QAAQ,CAAC;AACxD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "narrarium-astro-reader",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "description": "Astro reader and scaffolding CLI for Narrarium book repositories.",
6
6
  "license": "MIT",
@@ -5,8 +5,8 @@ import SiteSearch from "../components/SiteSearch.astro";
5
5
  import { listChapters } from "narrarium";
6
6
  import { getBookRoot } from "../lib/book.js";
7
7
  import { loadCanonGlossary } from "../lib/glossary.js";
8
- import { isFullCanonMode, getReaderPasswordHash, getReaderPassword } from "../lib/reader-mode.js";
9
- import { getBuildSaltBase64 } from "../lib/content-crypto.js";
8
+ import { isFullCanonMode, getReaderPassword } from "../lib/reader-mode.js";
9
+ import { getBuildSaltBase64, encryptCanary } from "../lib/content-crypto.js";
10
10
  import { loadSearchIndex } from "../lib/search.js";
11
11
 
12
12
  interface Props {
@@ -30,9 +30,10 @@ const canonGlossary = await loadCanonGlossary(currentChapterNumber);
30
30
  const searchIndex = await loadSearchIndex(currentChapterNumber);
31
31
  const readerChapters = await listChapters(getBookRoot()).catch(() => []);
32
32
  const fullCanonMode = isFullCanonMode();
33
- const passwordHash = getReaderPasswordHash();
34
- const cryptoSalt = getReaderPassword() ? getBuildSaltBase64() : null;
35
- if (!cryptoSalt) {
33
+ const password = getReaderPassword();
34
+ const cryptoSalt = password ? getBuildSaltBase64() : null;
35
+ const canary = password ? encryptCanary(password) : null;
36
+ if (!password) {
36
37
  console.info("[narrarium-reader] NARRARIUM_READER_PASSWORD not set — building without encryption.");
37
38
  }
38
39
  const isChapterPage = Number.isFinite(currentChapterNumber);
@@ -65,7 +66,7 @@ const isChapterPage = Number.isFinite(currentChapterNumber);
65
66
  })();
66
67
  </script>
67
68
  </head>
68
- <body class:list={[isChapterPage && "page-chapter"]} data-reader-chapter-number={currentChapterNumber} data-full-canon={fullCanonMode ? "true" : "false"} data-reader-password-hash={passwordHash ?? ""} data-crypto-salt={cryptoSalt ?? undefined}>
69
+ <body class:list={[isChapterPage && "page-chapter"]} data-reader-chapter-number={currentChapterNumber} data-full-canon={fullCanonMode ? "true" : "false"} data-crypto-salt={cryptoSalt ?? undefined} data-canary-iv={canary?.iv ?? undefined} data-canary-ct={canary?.ct ?? undefined}>
69
70
  <script is:inline>
70
71
  (() => {
71
72
  try {
@@ -85,6 +86,8 @@ const isChapterPage = Number.isFinite(currentChapterNumber);
85
86
  "decrypted" – encrypted, auto-decrypted from stored AES key
86
87
  ──────────────────────────────────────────────────────────────────── */
87
88
  var salt = document.body.dataset.cryptoSalt;
89
+ var canaryIv = document.body.dataset.canaryIv;
90
+ var canaryCt = document.body.dataset.canaryCt;
88
91
 
89
92
  if (!salt) {
90
93
  // No encryption configured — content is always available.
@@ -175,7 +178,14 @@ const isChapterPage = Number.isFinite(currentChapterNumber);
175
178
  { name: "AES-GCM", length: 256 },
176
179
  false, ["decrypt"]
177
180
  )
178
- .then(function (key) { return window._narrariumDecryptPage(key, true); })
181
+ .then(function (key) {
182
+ // Verify canary — AES-GCM auth tag rejects a stale or wrong key
183
+ // before we touch any page content.
184
+ return aesDecrypt(key, canaryIv, canaryCt).then(function (plaintext) {
185
+ if (plaintext !== "narrarium-ok") throw new Error("stale");
186
+ return window._narrariumDecryptPage(key, true);
187
+ });
188
+ })
179
189
  .catch(function () {
180
190
  // Stored key is stale or corrupt — clear it and show lock screen.
181
191
  try { localStorage.removeItem("narrarium-reader-aes-key"); } catch (_) {}
@@ -188,7 +198,7 @@ const isChapterPage = Number.isFinite(currentChapterNumber);
188
198
  }
189
199
  })();
190
200
  </script>
191
- {passwordHash && (
201
+ {canary && (
192
202
  <div id="reader-password-gate" role="dialog" aria-modal="true" aria-label="Access required">
193
203
  <div class="reader-password-gate__inner">
194
204
  <p class="eyebrow">Narrarium Reader</p>
@@ -233,24 +243,19 @@ const isChapterPage = Number.isFinite(currentChapterNumber);
233
243
  </main>
234
244
  <SiteSearch entries={searchIndex} />
235
245
  <ReaderRuntime glossary={canonGlossary} chapters={readerChapters} currentChapterNumber={currentChapterNumber} />
236
- {passwordHash && (
246
+ {canary && (
237
247
  <script is:inline>
238
248
  (function initPasswordGate() {
239
249
  var gate = document.getElementById("reader-password-gate");
240
250
  var form = document.getElementById("reader-password-form");
241
251
  var input = document.getElementById("reader-password-input");
242
252
  var errorEl = document.getElementById("reader-password-error");
243
- var passwordHash = document.body.dataset.readerPasswordHash;
244
253
  var cryptoSalt = document.body.dataset.cryptoSalt;
245
- if (!gate || !form || !passwordHash) return;
254
+ var canaryIv = document.body.dataset.canaryIv;
255
+ var canaryCt = document.body.dataset.canaryCt;
256
+ if (!gate || !form || !cryptoSalt || !canaryIv || !canaryCt) return;
246
257
  if (input) { input.focus(); }
247
258
 
248
- function buf2hex(buffer) {
249
- return Array.from(new Uint8Array(buffer))
250
- .map(function (b) { return b.toString(16).padStart(2, "0"); })
251
- .join("");
252
- }
253
-
254
259
  function b64ToBytes(b64) {
255
260
  var bin = atob(b64);
256
261
  var bytes = new Uint8Array(bin.length);
@@ -258,34 +263,39 @@ const isChapterPage = Number.isFinite(currentChapterNumber);
258
263
  return bytes;
259
264
  }
260
265
 
266
+ function aesDecrypt(cryptoKey, ivB64, ctB64) {
267
+ return crypto.subtle.decrypt(
268
+ { name: "AES-GCM", iv: b64ToBytes(ivB64) },
269
+ cryptoKey,
270
+ b64ToBytes(ctB64)
271
+ ).then(function (buf) { return new TextDecoder().decode(buf); });
272
+ }
273
+
261
274
  form.addEventListener("submit", function (event) {
262
275
  event.preventDefault();
263
276
  var password = input ? input.value : "";
264
277
  if (!password) return;
265
278
 
266
279
  var encoded = new TextEncoder().encode(password);
280
+ var saltBytes = b64ToBytes(cryptoSalt);
267
281
 
268
- // Quick SHA-256 check before the expensive PBKDF2 step.
269
- crypto.subtle.digest("SHA-256", encoded).then(function (hashBuffer) {
270
- if (buf2hex(hashBuffer) !== passwordHash) {
271
- if (errorEl) { errorEl.hidden = false; }
272
- if (input) { input.value = ""; input.focus(); }
273
- return;
274
- }
282
+ // Derive the AES-256-GCM key via PBKDF2 — the full 100 000-iteration
283
+ // cost is paid on every attempt, making offline brute-force impractical.
284
+ crypto.subtle.importKey("raw", encoded, "PBKDF2", false, ["deriveKey"])
285
+ .then(function (keyMaterial) {
286
+ return crypto.subtle.deriveKey(
287
+ { name: "PBKDF2", salt: saltBytes, iterations: 100000, hash: "SHA-256" },
288
+ keyMaterial,
289
+ { name: "AES-GCM", length: 256 },
290
+ true, // extractable so we can export and store it
291
+ ["decrypt"]
292
+ );
293
+ })
294
+ .then(function (aesKey) {
295
+ // Verify against the canary block — AES-GCM auth tag rejects wrong keys.
296
+ return aesDecrypt(aesKey, canaryIv, canaryCt).then(function (plaintext) {
297
+ if (plaintext !== "narrarium-ok") throw new Error("wrong password");
275
298
 
276
- // Hash matched — derive the AES-256-GCM key via PBKDF2.
277
- var saltBytes = b64ToBytes(cryptoSalt);
278
- crypto.subtle.importKey("raw", encoded, "PBKDF2", false, ["deriveKey"])
279
- .then(function (keyMaterial) {
280
- return crypto.subtle.deriveKey(
281
- { name: "PBKDF2", salt: saltBytes, iterations: 100000, hash: "SHA-256" },
282
- keyMaterial,
283
- { name: "AES-GCM", length: 256 },
284
- true, // extractable so we can export and store it
285
- ["decrypt"]
286
- );
287
- })
288
- .then(function (aesKey) {
289
299
  // Persist the raw key bytes so auto-decrypt works on reload.
290
300
  crypto.subtle.exportKey("raw", aesKey).then(function (rawBuf) {
291
301
  var rawBytes = new Uint8Array(rawBuf);
@@ -296,14 +306,12 @@ const isChapterPage = Number.isFinite(currentChapterNumber);
296
306
  // Decrypt the page — passes false = manual unlock, so the
297
307
  // content-unlocked event will fire for late-init scripts.
298
308
  return window._narrariumDecryptPage(aesKey, false);
299
- })
300
- .catch(function () {
301
- if (errorEl) { errorEl.hidden = false; }
302
- if (input) { input.value = ""; input.focus(); }
303
309
  });
304
- }).catch(function () {
305
- if (errorEl) { errorEl.hidden = false; }
306
- });
310
+ })
311
+ .catch(function () {
312
+ if (errorEl) { errorEl.hidden = false; }
313
+ if (input) { input.value = ""; input.focus(); }
314
+ });
307
315
  });
308
316
  })();
309
317
  </script>
@@ -30,6 +30,13 @@ function deriveKey(password: string, salt: Buffer): Buffer {
30
30
  return pbkdf2Sync(password, salt, 100_000, 32, "sha256");
31
31
  }
32
32
 
33
+ /**
34
+ * Known plaintext embedded (encrypted) in the built HTML so the browser can
35
+ * verify a password by attempting decryption rather than comparing a fast hash.
36
+ * This forces brute-force attempts to pay the full PBKDF2 cost every time.
37
+ */
38
+ export const CANARY_PLAINTEXT = "narrarium-ok";
39
+
33
40
  export interface EncryptedChunk {
34
41
  /** Base64-encoded 12-byte random IV. */
35
42
  iv: string;
@@ -56,3 +63,14 @@ export function encryptString(plaintext: string, password: string): EncryptedChu
56
63
  ct: Buffer.concat([body, tag]).toString("base64"),
57
64
  };
58
65
  }
66
+
67
+ /**
68
+ * Encrypt the canary plaintext with the same PBKDF2-derived build key.
69
+ *
70
+ * Embed `iv` and `ct` in the built HTML as `data-canary-iv` / `data-canary-ct`.
71
+ * The browser verifies the password by decrypting the canary and checking that
72
+ * the result equals `CANARY_PLAINTEXT` — no fast-hash oracle in the HTML.
73
+ */
74
+ export function encryptCanary(password: string): EncryptedChunk {
75
+ return encryptString(CANARY_PLAINTEXT, password);
76
+ }
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import { readReaderEnv } from "./env.js";
3
2
 
4
3
  export function isFullCanonMode(): boolean {
@@ -17,15 +16,3 @@ export function isFullCanonMode(): boolean {
17
16
  export function getReaderPassword(): string | null {
18
17
  return readReaderEnv(["NARRARIUM_READER_PASSWORD"]) ?? null;
19
18
  }
20
-
21
- /**
22
- * Returns a SHA-256 hex hash of the NARRARIUM_READER_PASSWORD env var,
23
- * or null when the variable is not set. The hash is embedded in the built
24
- * HTML and compared against the user's input at runtime via SubtleCrypto
25
- * as a fast pre-filter before the more expensive PBKDF2 key derivation.
26
- */
27
- export function getReaderPasswordHash(): string | null {
28
- const raw = readReaderEnv(["NARRARIUM_READER_PASSWORD"]);
29
- if (!raw) return null;
30
- return createHash("sha256").update(raw).digest("hex");
31
- }
package/src/scaffold.ts CHANGED
@@ -126,6 +126,7 @@ The scaffold creates a local \`.env\` with the book root already filled in. Adju
126
126
  \`\`\`bash
127
127
  NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}
128
128
  # NARRARIUM_READER_CANON_MODE=full
129
+ # NARRARIUM_READER_PASSWORD=your-secret-password
129
130
  # EPUBCHECK_CMD=epubcheck
130
131
  # EPUBCHECK_JAR=/absolute/path/to/epubcheck.jar
131
132
  \`\`\`
@@ -142,6 +143,29 @@ It also watches the linked book repository, regenerates the EPUB when canon file
142
143
 
143
144
  By default the reader uses a spoiler-safe public mode. If you want a private full-canon deployment, enable \`NARRARIUM_READER_CANON_MODE=full\` before running dev or build.
144
145
 
146
+ ## Password protection
147
+
148
+ Set \`NARRARIUM_READER_PASSWORD\` to encrypt all prose content at build time using AES-256-GCM.
149
+ Visitors must enter the correct password before any text is revealed.
150
+
151
+ For local development, add the variable to your \`.env\` file:
152
+
153
+ \`\`\`bash
154
+ NARRARIUM_READER_PASSWORD=your-secret-password
155
+ \`\`\`
156
+
157
+ For GitHub Pages deployment, add it to the \`Build site\` step in \`.github/workflows/deploy-pages.yml\`:
158
+
159
+ \`\`\`yaml
160
+ - name: Build site
161
+ env:
162
+ SITE_BASE: /\${{ github.event.repository.name }}/
163
+ NARRARIUM_READER_PASSWORD: \${{ secrets.NARRARIUM_READER_PASSWORD }}
164
+ run: npm run build
165
+ \`\`\`
166
+
167
+ Store the actual password as a repository secret: **Settings → Secrets and variables → Actions → New repository secret**.
168
+
145
169
  ## Doctor
146
170
 
147
171
  \`\`\`bash
@@ -175,6 +199,7 @@ function buildReaderEnvFile(bookRoot: string): string {
175
199
  return [
176
200
  `NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}`,
177
201
  "# NARRARIUM_READER_CANON_MODE=full",
202
+ "# NARRARIUM_READER_PASSWORD=your-secret-password",
178
203
  "# EPUBCHECK_CMD=epubcheck",
179
204
  "# EPUBCHECK_JAR=/absolute/path/to/epubcheck.jar",
180
205
  "",
@@ -257,6 +282,7 @@ jobs:
257
282
  - name: Build site
258
283
  env:
259
284
  ${envBlock}
285
+ # NARRARIUM_READER_PASSWORD: \${{ secrets.NARRARIUM_READER_PASSWORD }}
260
286
  run: npm run build
261
287
 
262
288
  - name: Upload artifact