@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.30

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.
Files changed (342) hide show
  1. package/AUTHORING.md +493 -5
  2. package/CHANGELOG.md +127 -1
  3. package/README.md +52 -6
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +106 -62
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +63 -55
  8. package/bin/apifuse-pack-check.ts +22 -2
  9. package/bin/apifuse-pack-smoke.ts +78 -82
  10. package/bin/apifuse-pack-types.ts +583 -0
  11. package/bin/apifuse-perf.ts +59 -140
  12. package/bin/apifuse-record.ts +698 -113
  13. package/bin/apifuse-submit-check.ts +517 -44
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml.ts +1 -1
  18. package/dist/auth-turn/index.d.ts +4 -4
  19. package/dist/auth-turn/index.js +1 -1
  20. package/dist/auth.d.ts +16 -2
  21. package/dist/auth.js +76 -18
  22. package/dist/ceremonies/index.d.ts +9 -1
  23. package/dist/ceremonies/index.js +117 -29
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/cli/templates/provider/README.md.tpl +4 -4
  32. package/dist/config/loader.d.ts +176 -17
  33. package/dist/config/loader.js +434 -161
  34. package/dist/contract-serialization.d.ts +2 -2
  35. package/dist/contract-serialization.js +7 -14
  36. package/dist/contract-types.d.ts +3 -2
  37. package/dist/contract.d.ts +3 -3
  38. package/dist/contract.js +6 -6
  39. package/dist/declaration-validation.d.ts +23 -0
  40. package/dist/declaration-validation.js +159 -0
  41. package/dist/define.d.ts +13 -1
  42. package/dist/define.js +391 -122
  43. package/dist/dev.d.ts +1 -1
  44. package/dist/dev.js +1 -1
  45. package/dist/error-resolution.d.ts +4 -0
  46. package/dist/error-resolution.js +122 -0
  47. package/dist/errors.d.ts +19 -1
  48. package/dist/errors.js +41 -3
  49. package/dist/fixture-sanitization.d.ts +26 -0
  50. package/dist/fixture-sanitization.js +216 -0
  51. package/dist/i18n/catalog.d.ts +2 -2
  52. package/dist/i18n/catalog.js +4 -10
  53. package/dist/i18n/index.d.ts +2 -2
  54. package/dist/i18n/index.js +2 -2
  55. package/dist/i18n/keys.d.ts +2 -2
  56. package/dist/index.d.ts +50 -42
  57. package/dist/index.js +41 -37
  58. package/dist/lint.d.ts +6 -1
  59. package/dist/lint.js +370 -18
  60. package/dist/native-address.d.ts +43 -0
  61. package/dist/native-address.js +281 -0
  62. package/dist/native-egress-policy.d.ts +31 -0
  63. package/dist/native-egress-policy.js +288 -0
  64. package/dist/observability.d.ts +5 -2
  65. package/dist/observability.js +48 -1
  66. package/dist/provider.d.ts +13 -11
  67. package/dist/provider.js +10 -9
  68. package/dist/public-schema-field-lint.d.ts +1 -1
  69. package/dist/recipes/gov-api.js +1 -1
  70. package/dist/runtime/auth-flow.d.ts +3 -1
  71. package/dist/runtime/auth-flow.js +8 -3
  72. package/dist/runtime/browser.d.ts +1 -1
  73. package/dist/runtime/browser.js +138 -40
  74. package/dist/runtime/cache.d.ts +2 -1
  75. package/dist/runtime/cache.js +173 -23
  76. package/dist/runtime/choice-wordlist.d.ts +9 -0
  77. package/dist/runtime/choice-wordlist.js +138 -0
  78. package/dist/runtime/choice.d.ts +13 -1
  79. package/dist/runtime/choice.js +514 -124
  80. package/dist/runtime/credential.d.ts +1 -1
  81. package/dist/runtime/credential.js +1 -1
  82. package/dist/runtime/env.d.ts +1 -1
  83. package/dist/runtime/executor.d.ts +1 -1
  84. package/dist/runtime/executor.js +25 -3
  85. package/dist/runtime/http.d.ts +3 -2
  86. package/dist/runtime/http.js +517 -55
  87. package/dist/runtime/insights.d.ts +1 -1
  88. package/dist/runtime/insights.js +6 -13
  89. package/dist/runtime/instrumentation.d.ts +2 -2
  90. package/dist/runtime/instrumentation.js +371 -23
  91. package/dist/runtime/keyring.js +1 -1
  92. package/dist/runtime/namespace.js +1 -1
  93. package/dist/runtime/native-network-errors.d.ts +33 -0
  94. package/dist/runtime/native-network-errors.js +69 -0
  95. package/dist/runtime/native-network.d.ts +96 -0
  96. package/dist/runtime/native-network.js +1232 -0
  97. package/dist/runtime/ocr.d.ts +29 -0
  98. package/dist/runtime/ocr.js +440 -0
  99. package/dist/runtime/otlp.d.ts +1 -1
  100. package/dist/runtime/perf.d.ts +1 -1
  101. package/dist/runtime/provider.d.ts +1 -1
  102. package/dist/runtime/provider.js +1 -2
  103. package/dist/runtime/proxy-errors.d.ts +1 -1
  104. package/dist/runtime/proxy-errors.js +9 -7
  105. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  106. package/dist/runtime/proxy-nodemaven.js +146 -0
  107. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  108. package/dist/runtime/proxy-retry-policy.js +2 -2
  109. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  110. package/dist/runtime/proxy-telemetry.js +58 -52
  111. package/dist/runtime/redirects.d.ts +29 -0
  112. package/dist/runtime/redirects.js +36 -0
  113. package/dist/runtime/redis.d.ts +1 -1
  114. package/dist/runtime/redis.js +5 -5
  115. package/dist/runtime/request-options.d.ts +68 -1
  116. package/dist/runtime/request-options.js +548 -0
  117. package/dist/runtime/resolver-config.d.ts +6 -0
  118. package/dist/runtime/resolver-config.js +6 -0
  119. package/dist/runtime/resolver-public.d.ts +1 -0
  120. package/dist/runtime/resolver-public.js +1 -0
  121. package/dist/runtime/resolver-shared.d.ts +3 -0
  122. package/dist/runtime/resolver-shared.js +12 -0
  123. package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
  124. package/dist/runtime/resolver-vendors/bindings.js +40 -0
  125. package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
  126. package/dist/runtime/resolver-vendors/browser.js +282 -0
  127. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  128. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  129. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  130. package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
  131. package/dist/runtime/resolver-vendors/types.d.ts +83 -0
  132. package/dist/runtime/resolver-vendors/types.js +69 -0
  133. package/dist/runtime/resolver.d.ts +59 -0
  134. package/dist/runtime/resolver.js +705 -0
  135. package/dist/runtime/secrets.d.ts +27 -0
  136. package/dist/runtime/secrets.js +51 -0
  137. package/dist/runtime/state.d.ts +5 -2
  138. package/dist/runtime/state.js +280 -74
  139. package/dist/runtime/stealth-cookies.d.ts +20 -0
  140. package/dist/runtime/stealth-cookies.js +111 -0
  141. package/dist/runtime/stealth.d.ts +30 -5
  142. package/dist/runtime/stealth.js +523 -259
  143. package/dist/runtime/stt.d.ts +1 -1
  144. package/dist/runtime/stt.js +12 -27
  145. package/dist/runtime/timeout.d.ts +5 -0
  146. package/dist/runtime/timeout.js +12 -0
  147. package/dist/runtime/trace.d.ts +2 -2
  148. package/dist/runtime/trace.js +2 -4
  149. package/dist/runtime/waterfall.d.ts +1 -1
  150. package/dist/schema.d.ts +1 -1
  151. package/dist/schema.js +7 -15
  152. package/dist/serve.d.ts +1 -1
  153. package/dist/serve.js +1 -1
  154. package/dist/server/index.d.ts +7 -7
  155. package/dist/server/index.js +6 -6
  156. package/dist/server/self-test-input-tokens.d.ts +2 -1
  157. package/dist/server/self-test-input-tokens.js +18 -14
  158. package/dist/server/self-test-redaction.d.ts +1 -1
  159. package/dist/server/self-test-redaction.js +1 -1
  160. package/dist/server/self-test.d.ts +117 -3
  161. package/dist/server/self-test.js +787 -151
  162. package/dist/server/serve-implementation.d.ts +210 -0
  163. package/dist/server/serve-implementation.js +2077 -0
  164. package/dist/server/serve.d.ts +1 -70
  165. package/dist/server/serve.js +1 -1143
  166. package/dist/server/types.d.ts +34 -9
  167. package/dist/server/types.js +8 -1
  168. package/dist/stateful/errors.d.ts +19 -0
  169. package/dist/stateful/errors.js +24 -0
  170. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  171. package/dist/stateful/http-provider-event-emitter.js +237 -0
  172. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  173. package/dist/stateful/http-session-owner-registry.js +210 -0
  174. package/dist/stateful/index.d.ts +18 -0
  175. package/dist/stateful/index.js +18 -0
  176. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  177. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  178. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  179. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  180. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  181. package/dist/stateful/provider-event-pipeline.js +1 -0
  182. package/dist/stateful/provider-events.d.ts +101 -0
  183. package/dist/stateful/provider-events.js +289 -0
  184. package/dist/stateful/session-key.d.ts +15 -0
  185. package/dist/stateful/session-key.js +86 -0
  186. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  187. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  188. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  189. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  190. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  191. package/dist/stateful/stateful-provider-adapter.js +287 -0
  192. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  193. package/dist/stateful/stateful-provider-observability.js +161 -0
  194. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  195. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  196. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  197. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  198. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  199. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  200. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  201. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  202. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  203. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  204. package/dist/stateful-signing.d.ts +18 -0
  205. package/dist/stateful-signing.js +27 -0
  206. package/dist/stealth/profiles.d.ts +1 -1
  207. package/dist/stealth/profiles.js +21 -21
  208. package/dist/stream-evidence.d.ts +74 -0
  209. package/dist/stream-evidence.js +785 -0
  210. package/dist/stream.d.ts +1 -1
  211. package/dist/stream.js +7 -1
  212. package/dist/testing/index.d.ts +3 -2
  213. package/dist/testing/index.js +3 -2
  214. package/dist/testing/run.d.ts +32 -2
  215. package/dist/testing/run.js +488 -28
  216. package/dist/types.d.ts +566 -19
  217. package/dist/types.js +1 -0
  218. package/dist/user-input.d.ts +30 -0
  219. package/dist/user-input.js +66 -0
  220. package/package.json +42 -7
  221. package/src/auth-turn/index.ts +2 -2
  222. package/src/auth.ts +146 -86
  223. package/src/ceremonies/index.ts +167 -92
  224. package/src/cli/commands.ts +10 -0
  225. package/src/cli/create.ts +42 -35
  226. package/src/cli/prompt-assets.ts +865 -0
  227. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  228. package/src/cli/templates/provider/README.md.tpl +4 -4
  229. package/src/config/loader.ts +667 -289
  230. package/src/contract-serialization.ts +10 -18
  231. package/src/contract-types.ts +3 -2
  232. package/src/contract.ts +14 -28
  233. package/src/declaration-validation.ts +202 -0
  234. package/src/define.ts +631 -495
  235. package/src/dev.ts +4 -9
  236. package/src/error-resolution.ts +127 -0
  237. package/src/errors.ts +56 -11
  238. package/src/fixture-sanitization.ts +247 -0
  239. package/src/i18n/catalog.ts +10 -32
  240. package/src/i18n/index.ts +2 -2
  241. package/src/i18n/keys.ts +5 -11
  242. package/src/index.ts +157 -44
  243. package/src/lint.ts +488 -154
  244. package/src/native-address.ts +340 -0
  245. package/src/native-egress-policy.ts +358 -0
  246. package/src/observability.ts +51 -1
  247. package/src/provider.ts +65 -11
  248. package/src/public-schema-field-lint.ts +7 -33
  249. package/src/recipes/gov-api.ts +2 -5
  250. package/src/runtime/auth-flow.ts +13 -7
  251. package/src/runtime/browser.ts +252 -207
  252. package/src/runtime/cache.ts +209 -81
  253. package/src/runtime/choice-wordlist.ts +145 -0
  254. package/src/runtime/choice.ts +685 -216
  255. package/src/runtime/credential.ts +2 -2
  256. package/src/runtime/env.ts +1 -1
  257. package/src/runtime/executor.ts +37 -19
  258. package/src/runtime/http.ts +645 -65
  259. package/src/runtime/insights.ts +15 -53
  260. package/src/runtime/instrumentation.ts +530 -67
  261. package/src/runtime/keyring.ts +7 -19
  262. package/src/runtime/namespace.ts +2 -7
  263. package/src/runtime/native-network-errors.ts +99 -0
  264. package/src/runtime/native-network.ts +1605 -0
  265. package/src/runtime/ocr.ts +523 -0
  266. package/src/runtime/otlp.ts +12 -23
  267. package/src/runtime/perf.ts +1 -1
  268. package/src/runtime/provider.ts +4 -9
  269. package/src/runtime/proxy-errors.ts +29 -42
  270. package/src/runtime/proxy-nodemaven.ts +221 -0
  271. package/src/runtime/proxy-retry-policy.ts +3 -3
  272. package/src/runtime/proxy-telemetry.ts +84 -77
  273. package/src/runtime/redirects.ts +66 -0
  274. package/src/runtime/redis.ts +10 -13
  275. package/src/runtime/request-options.ts +679 -9
  276. package/src/runtime/resolver-config.ts +6 -0
  277. package/src/runtime/resolver-public.ts +18 -0
  278. package/src/runtime/resolver-shared.ts +17 -0
  279. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  280. package/src/runtime/resolver-vendors/browser.ts +408 -0
  281. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  282. package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
  283. package/src/runtime/resolver-vendors/types.ts +173 -0
  284. package/src/runtime/resolver.ts +1060 -0
  285. package/src/runtime/secrets.ts +64 -0
  286. package/src/runtime/state.ts +399 -161
  287. package/src/runtime/stealth-cookies.ts +132 -0
  288. package/src/runtime/stealth.ts +681 -295
  289. package/src/runtime/stt.ts +39 -113
  290. package/src/runtime/timeout.ts +18 -0
  291. package/src/runtime/trace.ts +14 -44
  292. package/src/runtime/waterfall.ts +5 -18
  293. package/src/schema.ts +23 -84
  294. package/src/serve.ts +6 -1
  295. package/src/server/index.ts +30 -7
  296. package/src/server/self-test-input-tokens.ts +29 -14
  297. package/src/server/self-test-redaction.ts +2 -2
  298. package/src/server/self-test.ts +1030 -180
  299. package/src/server/serve-implementation.ts +3060 -0
  300. package/src/server/serve.ts +1 -1781
  301. package/src/server/types.ts +12 -13
  302. package/src/stateful/README.md +146 -0
  303. package/src/stateful/errors.ts +35 -0
  304. package/src/stateful/http-provider-event-emitter.ts +314 -0
  305. package/src/stateful/http-session-owner-registry.ts +306 -0
  306. package/src/stateful/index.ts +18 -0
  307. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  308. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  309. package/src/stateful/provider-event-pipeline.ts +61 -0
  310. package/src/stateful/provider-events.ts +462 -0
  311. package/src/stateful/session-key.ts +111 -0
  312. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  313. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  314. package/src/stateful/stateful-provider-adapter.ts +562 -0
  315. package/src/stateful/stateful-provider-observability.ts +261 -0
  316. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  317. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  318. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  319. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  320. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  321. package/src/stateful-signing.ts +46 -0
  322. package/src/stealth/profiles.ts +27 -33
  323. package/src/stream-evidence.ts +988 -0
  324. package/src/stream.ts +16 -20
  325. package/src/testing/index.ts +11 -2
  326. package/src/testing/run.ts +668 -74
  327. package/src/types.ts +665 -35
  328. package/src/user-input.ts +118 -0
  329. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  330. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  331. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  332. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  333. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  334. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  335. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  336. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  337. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  338. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  339. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  340. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  341. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  342. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,6 +1,7 @@
1
- import { createHash } from "node:crypto";
1
+ import { createHash, createHmac } from "node:crypto";
2
2
 
3
- import { providerCacheRedisUrlFromEnv } from "../config/loader";
3
+ import { providerCacheRedisUrlFromEnv } from "../config/loader.js";
4
+ import { ProviderError } from "../errors.js";
4
5
  import type {
5
6
  ProviderCache,
6
7
  ProviderCacheGetOrSetOptions,
@@ -8,13 +9,13 @@ import type {
8
9
  ProviderCacheLookupMeta,
9
10
  ProviderCacheResponseMeta,
10
11
  ProviderCacheResult,
11
- } from "../types";
12
+ } from "../types.js";
12
13
  import {
13
14
  createProviderRedisClient,
14
15
  ensureRedisReady,
15
16
  type ProviderRedisClient,
16
17
  withRedisTimeout,
17
- } from "./redis";
18
+ } from "./redis.js";
18
19
 
19
20
  type CacheSource = ProviderCacheLookupMeta["source"];
20
21
 
@@ -43,9 +44,12 @@ export type ProviderCacheOptions = {
43
44
  now?: () => number;
44
45
  };
45
46
 
47
+ export const APIFUSE__CACHE__KEY_PEPPER_ENV = "APIFUSE__CACHE__KEY_PEPPER";
48
+
46
49
  const DEFAULT_PREFIX = "apifuse:provider-cache:v1";
47
50
  const DEFAULT_MEMORY_MAX_ENTRIES = 1_000;
48
51
  const DEFAULT_REDIS_TIMEOUT_MS = 150;
52
+ const SECRET_SCOPED_KEY_MARKER = "[secret-scoped";
49
53
  const SECRET_FIELD_NAMES = new Set([
50
54
  "authorization",
51
55
  "cookie",
@@ -61,6 +65,7 @@ const SECRET_FIELD_NAMES = new Set([
61
65
  ]);
62
66
 
63
67
  const sharedBackends = new Map<string, SharedCacheBackend>();
68
+ let warnedAboutUnpepperedSecretKeys = false;
64
69
 
65
70
  function backendKey(redisUrl: string | undefined): string {
66
71
  return redisUrl ?? "memory";
@@ -107,26 +112,182 @@ function shouldRedactField(name: string, extra: Set<string>): boolean {
107
112
  );
108
113
  }
109
114
 
110
- function normalizeKeyPart(value: unknown, extra: Set<string>): unknown {
115
+ type NormalizedKeyPart = {
116
+ value: unknown;
117
+ secretScoped: boolean;
118
+ };
119
+
120
+ function unsupportedSecretValue(path: string, reason: string): never {
121
+ throw new ProviderError(`Secret cache-key values must be JSON-safe; ${reason} at ${path}.`, {
122
+ code: "CACHE_KEY_SECRET_VALUE_UNSUPPORTED",
123
+ fix: "Convert the secret cache-key selector to JSON-safe primitives, arrays, or plain objects.",
124
+ });
125
+ }
126
+
127
+ function assertJsonSafeSecretValue(
128
+ value: unknown,
129
+ reportedPath: string,
130
+ ancestors = new Set<object>(),
131
+ ): void {
132
+ if (value === undefined) return;
133
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
134
+ if (typeof value === "number") {
135
+ if (!Number.isFinite(value))
136
+ unsupportedSecretValue(reportedPath, "non-finite numbers are unsupported");
137
+ if (Object.is(value, -0))
138
+ unsupportedSecretValue(reportedPath, "negative zero is unsupported");
139
+ return;
140
+ }
141
+ if (typeof value !== "object") {
142
+ unsupportedSecretValue(reportedPath, `${typeof value} values are unsupported`);
143
+ }
144
+ if (ancestors.has(value))
145
+ unsupportedSecretValue(reportedPath, "cyclic values are unsupported");
146
+
147
+ ancestors.add(value);
148
+ try {
149
+ if (Array.isArray(value)) {
150
+ if (Object.getOwnPropertySymbols(value).length > 0) {
151
+ unsupportedSecretValue(reportedPath, "symbol-keyed array properties are unsupported");
152
+ }
153
+ const expectedNames = new Set(["length"]);
154
+ for (let index = 0; index < value.length; index += 1) {
155
+ const key = String(index);
156
+ expectedNames.add(key);
157
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
158
+ if (!descriptor)
159
+ unsupportedSecretValue(reportedPath, "sparse arrays are unsupported");
160
+ if (!descriptor.enumerable || !("value" in descriptor)) {
161
+ unsupportedSecretValue(reportedPath, "array accessors are unsupported");
162
+ }
163
+ assertJsonSafeSecretValue(descriptor.value, reportedPath, ancestors);
164
+ }
165
+ if (Object.getOwnPropertyNames(value).some((name) => !expectedNames.has(name))) {
166
+ unsupportedSecretValue(reportedPath, "custom array properties are unsupported");
167
+ }
168
+ return;
169
+ }
170
+
171
+ const prototype = Object.getPrototypeOf(value);
172
+ if (prototype !== Object.prototype && prototype !== null) {
173
+ unsupportedSecretValue(reportedPath, "non-plain objects are unsupported");
174
+ }
175
+ if (Object.getOwnPropertySymbols(value).length > 0) {
176
+ unsupportedSecretValue(reportedPath, "symbol-keyed properties are unsupported");
177
+ }
178
+ for (const key of Object.getOwnPropertyNames(value)) {
179
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
180
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
181
+ unsupportedSecretValue(
182
+ reportedPath,
183
+ "non-enumerable properties and accessors are unsupported",
184
+ );
185
+ }
186
+ assertJsonSafeSecretValue(descriptor.value, reportedPath, ancestors);
187
+ }
188
+ } finally {
189
+ ancestors.delete(value);
190
+ }
191
+ }
192
+
193
+ function containsUndefined(value: unknown): boolean {
194
+ if (value === undefined) return true;
195
+ if (Array.isArray(value)) return value.some(containsUndefined);
196
+ if (isRecord(value)) return Object.values(value).some(containsUndefined);
197
+ return false;
198
+ }
199
+
200
+ function tagSecretValue(value: unknown): unknown {
201
+ if (value === undefined) return ["undefined"];
202
+ if (value === null) return ["null"];
203
+ if (typeof value === "string") return ["string", value];
204
+ if (typeof value === "number") return ["number", value];
205
+ if (typeof value === "boolean") return ["boolean", value];
206
+ if (Array.isArray(value)) return ["array", value.map(tagSecretValue)];
207
+ return [
208
+ "object",
209
+ Object.entries(value as Record<string, unknown>).map(([key, entry]) => [
210
+ key,
211
+ tagSecretValue(entry),
212
+ ]),
213
+ ];
214
+ }
215
+
216
+ function serializeSecretValue(value: unknown): string {
217
+ if (!containsUndefined(value)) return JSON.stringify([value]);
218
+ return `undefined-v1:${JSON.stringify(tagSecretValue(value))}`;
219
+ }
220
+
221
+ function warnAboutUnpepperedSecretKey(): void {
222
+ if (warnedAboutUnpepperedSecretKeys) return;
223
+ warnedAboutUnpepperedSecretKeys = true;
224
+ console.warn(
225
+ JSON.stringify({
226
+ level: "warn",
227
+ event: "provider_cache_secret_key_unpeppered",
228
+ message: `Secret-bearing cache keys are using unkeyed SHA-256 because ${APIFUSE__CACHE__KEY_PEPPER_ENV} is not configured.`,
229
+ }),
230
+ );
231
+ }
232
+
233
+ function normalizeKeyPart(
234
+ value: unknown,
235
+ extra: Set<string>,
236
+ pepper: string | undefined,
237
+ ): NormalizedKeyPart {
111
238
  if (Array.isArray(value)) {
112
- return value.map((entry) => normalizeKeyPart(entry, extra));
239
+ const entries = value.map((entry) => normalizeKeyPart(entry, extra, pepper));
240
+ return {
241
+ value: entries.map((entry) => entry.value),
242
+ secretScoped: entries.some((entry) => entry.secretScoped),
243
+ };
113
244
  }
114
245
  if (isRecord(value)) {
115
- const normalized: Record<string, unknown> = {};
246
+ const normalized: Record<string, unknown> = Object.create(null);
247
+ let secretScoped = false;
116
248
  for (const key of Object.keys(value).sort()) {
117
- if (shouldRedactField(key, extra)) continue;
118
- normalized[key] = normalizeKeyPart(value[key], extra);
249
+ const part = shouldRedactField(key, extra)
250
+ ? hashSecretValue(value[key], key, extra, pepper)
251
+ : normalizeKeyPart(value[key], extra, pepper);
252
+ normalized[key] = part.value;
253
+ secretScoped ||= part.secretScoped;
119
254
  }
120
- return normalized;
255
+ return { value: normalized, secretScoped };
121
256
  }
122
- return value;
257
+ return { value, secretScoped: false };
258
+ }
259
+
260
+ function hashSecretValue(
261
+ value: unknown,
262
+ fieldName: string,
263
+ extra: Set<string>,
264
+ pepper: string | undefined,
265
+ ): NormalizedKeyPart {
266
+ assertJsonSafeSecretValue(value, `${fieldName} (inside secret value)`);
267
+ const canonical = serializeSecretValue(normalizeKeyPart(value, extra, pepper).value);
268
+ if (pepper === undefined) {
269
+ warnAboutUnpepperedSecretKey();
270
+ const digest = createHash("sha256").update(canonical).digest("hex");
271
+ return { value: `sha256:${digest}`, secretScoped: true };
272
+ }
273
+ const digest = createHmac("sha256", pepper).update(canonical).digest("hex");
274
+ return { value: `hmac-sha256:${digest}`, secretScoped: true };
275
+ }
276
+
277
+ function metadataKeys(
278
+ events: ProviderCacheLookupMeta[],
279
+ secretScopedKeys: Set<string>,
280
+ ): string[] {
281
+ let secretScopedIndex = 0;
282
+ return Array.from(new Set(events.map((event) => event.key))).map((key) => {
283
+ if (!secretScopedKeys.has(key)) return key;
284
+ secretScopedIndex += 1;
285
+ return `${SECRET_SCOPED_KEY_MARKER}#${secretScopedIndex}]`;
286
+ });
123
287
  }
124
288
 
125
289
  function stableHash(value: unknown): string {
126
- return createHash("sha256")
127
- .update(JSON.stringify(value))
128
- .digest("hex")
129
- .slice(0, 32);
290
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 32);
130
291
  }
131
292
 
132
293
  function jitteredTtlMs(ttlMs: number, jitterPct: number | undefined): number {
@@ -161,10 +322,7 @@ function safeParseEnvelope(raw: string | null): CacheEnvelope | null {
161
322
  }
162
323
  }
163
324
 
164
- function resultWithValue<T>(
165
- value: unknown,
166
- meta: ProviderCacheLookupMeta,
167
- ): ProviderCacheResult<T> {
325
+ function resultWithValue<T>(value: unknown, meta: ProviderCacheLookupMeta): ProviderCacheResult<T> {
168
326
  return {
169
327
  value: <T>value,
170
328
  meta,
@@ -187,18 +345,14 @@ function resultFromEnvelope<T>(
187
345
  });
188
346
  }
189
347
 
190
- function sourceSummary(
191
- events: ProviderCacheLookupMeta[],
192
- ): ProviderCacheResponseMeta["source"] {
348
+ function sourceSummary(events: ProviderCacheLookupMeta[]): ProviderCacheResponseMeta["source"] {
193
349
  const sources = new Set(events.map((event) => event.source));
194
350
  if (sources.size === 0) return undefined;
195
351
  if (sources.size === 1) return events[0]?.source;
196
352
  return "mixed";
197
353
  }
198
354
 
199
- async function withRedisFallback<T>(
200
- operation: () => Promise<T>,
201
- ): Promise<T | undefined> {
355
+ async function withRedisFallback<T>(operation: () => Promise<T>): Promise<T | undefined> {
202
356
  return await withRedisTimeout(operation, {
203
357
  timeoutMs: DEFAULT_REDIS_TIMEOUT_MS,
204
358
  onTimeout: () => undefined,
@@ -206,17 +360,15 @@ async function withRedisFallback<T>(
206
360
  });
207
361
  }
208
362
 
209
- export function createProviderCache(
210
- options: ProviderCacheOptions,
211
- ): ProviderCache {
363
+ export function createProviderCache(options: ProviderCacheOptions): ProviderCache {
212
364
  const redisUrl = options.redisUrl ?? providerCacheRedisUrlFromEnv();
365
+ const configuredPepper = process.env[APIFUSE__CACHE__KEY_PEPPER_ENV];
366
+ const pepper = configuredPepper && configuredPepper.length > 0 ? configuredPepper : undefined;
213
367
  const backend = getSharedBackend(redisUrl);
214
- const memoryMaxEntries = Math.max(
215
- 1,
216
- options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES,
217
- );
368
+ const memoryMaxEntries = Math.max(1, options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES);
218
369
  const now = options.now ?? Date.now;
219
370
  const events: ProviderCacheLookupMeta[] = [];
371
+ const secretScopedKeys = new Set<string>();
220
372
 
221
373
  function record(meta: ProviderCacheLookupMeta): void {
222
374
  events.push(meta);
@@ -238,11 +390,7 @@ export function createProviderCache(
238
390
  }
239
391
  }
240
392
 
241
- function rememberEnvelope(
242
- key: string,
243
- envelope: CacheEnvelope,
244
- currentTime: number,
245
- ): void {
393
+ function rememberEnvelope(key: string, envelope: CacheEnvelope, currentTime: number): void {
246
394
  sweepMemory(currentTime);
247
395
  backend.memory.delete(key);
248
396
  backend.memory.set(key, {
@@ -253,11 +401,7 @@ export function createProviderCache(
253
401
  enforceMemoryLimit();
254
402
  }
255
403
 
256
- function touchMemory(
257
- key: string,
258
- entry: MemoryEntry,
259
- currentTime: number,
260
- ): void {
404
+ function touchMemory(key: string, entry: MemoryEntry, currentTime: number): void {
261
405
  backend.memory.delete(key);
262
406
  backend.memory.set(key, { ...entry, lastAccessedAt: currentTime });
263
407
  }
@@ -298,12 +442,7 @@ export function createProviderCache(
298
442
  if (memoryEntry.expiresAt <= currentTime) {
299
443
  backend.memory.delete(key);
300
444
  } else {
301
- const memoryResult = resultFromEnvelope<T>(
302
- key,
303
- memoryEntry,
304
- currentTime,
305
- "memory",
306
- );
445
+ const memoryResult = resultFromEnvelope<T>(key, memoryEntry, currentTime, "memory");
307
446
  if (memoryResult && !memoryResult.meta.stale) {
308
447
  touchMemory(key, memoryEntry, currentTime);
309
448
  return memoryResult;
@@ -334,10 +473,7 @@ export function createProviderCache(
334
473
  cacheOptions: ProviderCacheGetOrSetOptions,
335
474
  ): Promise<void> {
336
475
  const currentTime = now();
337
- const freshTtlMs = jitteredTtlMs(
338
- cacheOptions.ttlMs,
339
- cacheOptions.jitterPct,
340
- );
476
+ const freshTtlMs = jitteredTtlMs(cacheOptions.ttlMs, cacheOptions.jitterPct);
341
477
  const staleIfErrorMs = cacheOptions.staleIfErrorMs ?? 0;
342
478
  const staleTtlMs = freshTtlMs + staleIfErrorMs;
343
479
  const envelope: CacheEnvelope = {
@@ -352,9 +488,7 @@ export function createProviderCache(
352
488
  if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
353
489
  return;
354
490
  }
355
- await withRedisFallback(() =>
356
- redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs),
357
- );
491
+ await withRedisFallback(() => redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs));
358
492
  }
359
493
 
360
494
  async function loadAndStore<T>(
@@ -385,16 +519,14 @@ export function createProviderCache(
385
519
 
386
520
  return {
387
521
  key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
388
- const extra = new Set(
389
- (keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()),
390
- );
391
- const normalized = normalizeKeyPart(parts, extra);
392
- return `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized)}`;
522
+ const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
523
+ const normalized = normalizeKeyPart(parts, extra, pepper);
524
+ const key = `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized.value)}`;
525
+ if (normalized.secretScoped) secretScopedKeys.add(key);
526
+ return key;
393
527
  },
394
528
 
395
- async get<T = unknown>(
396
- key: string,
397
- ): Promise<ProviderCacheResult<T> | null> {
529
+ async get<T = unknown>(key: string): Promise<ProviderCacheResult<T> | null> {
398
530
  const result = await read<T>(key);
399
531
  if (result) record(result.meta);
400
532
  return result;
@@ -405,10 +537,7 @@ export function createProviderCache(
405
537
  async delete(key: string): Promise<void> {
406
538
  backend.memory.delete(key);
407
539
  const redis = backend.redis;
408
- if (
409
- !redis ||
410
- !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))
411
- ) {
540
+ if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
412
541
  return;
413
542
  }
414
543
  await withRedisFallback(() => redis.del(key));
@@ -428,10 +557,7 @@ export function createProviderCache(
428
557
  const existingInflight = backend.inflight.get(key);
429
558
  if (existingInflight) {
430
559
  const inflightResult = await existingInflight;
431
- const result = resultWithValue<T>(
432
- inflightResult.value,
433
- inflightResult.meta,
434
- );
560
+ const result = resultWithValue<T>(inflightResult.value, inflightResult.meta);
435
561
  record(result.meta);
436
562
  return result;
437
563
  }
@@ -456,7 +582,7 @@ export function createProviderCache(
456
582
  return {
457
583
  hit: events.some((event) => event.hit),
458
584
  stale: events.some((event) => event.stale),
459
- keys: Array.from(new Set(events.map((event) => event.key))),
585
+ keys: metadataKeys(events, secretScopedKeys),
460
586
  source: sourceSummary(events),
461
587
  };
462
588
  },
@@ -466,20 +592,21 @@ export function createProviderCache(
466
592
  export function createBypassProviderCache(
467
593
  options: Pick<ProviderCacheOptions, "providerId">,
468
594
  ): ProviderCache {
595
+ const configuredPepper = process.env[APIFUSE__CACHE__KEY_PEPPER_ENV];
596
+ const pepper = configuredPepper && configuredPepper.length > 0 ? configuredPepper : undefined;
469
597
  const events: ProviderCacheLookupMeta[] = [];
598
+ const secretScopedKeys = new Set<string>();
470
599
 
471
600
  return {
472
601
  key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
473
- const extra = new Set(
474
- (keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()),
475
- );
476
- const normalized = normalizeKeyPart(parts, extra);
477
- return `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized)}`;
602
+ const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
603
+ const normalized = normalizeKeyPart(parts, extra, pepper);
604
+ const key = `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized.value)}`;
605
+ if (normalized.secretScoped) secretScopedKeys.add(key);
606
+ return key;
478
607
  },
479
608
 
480
- async get<T = unknown>(
481
- _key: string,
482
- ): Promise<ProviderCacheResult<T> | null> {
609
+ async get<T = unknown>(_key: string): Promise<ProviderCacheResult<T> | null> {
483
610
  return null;
484
611
  },
485
612
 
@@ -511,7 +638,7 @@ export function createBypassProviderCache(
511
638
  return {
512
639
  hit: false,
513
640
  stale: false,
514
- keys: Array.from(new Set(events.map((event) => event.key))),
641
+ keys: metadataKeys(events, secretScopedKeys),
515
642
  source: sourceSummary(events),
516
643
  };
517
644
  },
@@ -525,4 +652,5 @@ export function resetProviderCacheForTests(): void {
525
652
  backend.redis?.disconnect();
526
653
  }
527
654
  sharedBackends.clear();
655
+ warnedAboutUnpepperedSecretKeys = false;
528
656
  }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Official EFF Short Wordlist #2, embedded unchanged from:
3
+ * https://www.eff.org/files/2016/09/08/eff_short_wordlist_2_0.txt
4
+ *
5
+ * The source's dice-roll column is intentionally omitted; word order is preserved.
6
+ */
7
+ const EFF_SHORT_WORDLIST_2_TEXT = `
8
+ aardvark abandoned abbreviate abdomen abhorrence abiding abnormal abrasion absorbing abundant abyss
9
+ academy accountant acetone achiness acid acoustics acquire acrobat actress acuteness aerosol
10
+ aesthetic affidavit afloat afraid aftershave again agency aggressor aghast agitate agnostic
11
+ agonizing agreeing aidless aimlessly ajar alarmclock albatross alchemy alfalfa algae aliens
12
+ alkaline almanac alongside alphabet already also altitude aluminum always amazingly ambulance
13
+ amendment amiable ammunition amnesty amoeba amplifier amuser anagram anchor android anesthesia
14
+ angelfish animal anklet announcer anonymous answer antelope anxiety anyplace aorta apartment apnea
15
+ apostrophe apple apricot aquamarine arachnid arbitrate ardently arena argument aristocrat armchair
16
+ aromatic arrowhead arsonist artichoke asbestos ascend aseptic ashamed asinine asleep asocial
17
+ asparagus astronaut asymmetric atlas atmosphere atom atrocious attic atypical auctioneer auditorium
18
+ augmented auspicious automobile auxiliary avalanche avenue aviator avocado awareness awhile awkward
19
+ awning awoke axially azalea babbling backpack badass bagpipe bakery balancing bamboo banana
20
+ barracuda basket bathrobe bazooka blade blender blimp blouse blurred boatyard bobcat body bogusness
21
+ bohemian boiler bonnet boots borough bossiness bottle bouquet boxlike breath briefcase broom
22
+ brushes bubblegum buckle buddhist buffalo bullfrog bunny busboy buzzard cabin cactus cadillac
23
+ cafeteria cage cahoots cajoling cakewalk calculator camera canister capsule carrot cashew cathedral
24
+ caucasian caviar ceasefire cedar celery cement census ceramics cesspool chalkboard cheesecake
25
+ chimney chlorine chopsticks chrome chute cilantro cinnamon circle cityscape civilian clay clergyman
26
+ clipboard clock clubhouse coathanger cobweb coconut codeword coexistent coffeecake cognitive
27
+ cohabitate collarbone computer confetti copier cornea cosmetics cotton couch coverless coyote
28
+ coziness crawfish crewmember crib croissant crumble crystal cubical cucumber cuddly cufflink
29
+ cuisine culprit cup curry cushion cuticle cybernetic cyclist cylinder cymbal cynicism cypress
30
+ cytoplasm dachshund daffodil dagger dairy dalmatian dandelion dartboard dastardly datebook daughter
31
+ dawn daytime dazzler dealer debris decal dedicate deepness defrost degree dehydrator deliverer
32
+ democrat dentist deodorant depot deranged desktop detergent device dexterity diamond dibs
33
+ dictionary diffuser digit dilated dimple dinnerware dioxide diploma directory dishcloth ditto
34
+ dividers dizziness doctor dodge doll dominoes donut doorstep dorsal double downstairs dozed
35
+ drainpipe dresser driftwood droppings drum dryer dubiously duckling duffel dugout dumpster duplex
36
+ durable dustpan dutiful duvet dwarfism dwelling dwindling dynamite dyslexia eagerness earlobe easel
37
+ eavesdrop ebook eccentric echoless eclipse ecosystem ecstasy edged editor educator eelworm eerie
38
+ effects eggnog egomaniac ejection elastic elbow elderly elephant elfishly eliminator elk elliptical
39
+ elongated elsewhere elusive elves emancipate embroidery emcee emerald emission emoticon emperor
40
+ emulate enactment enchilada endorphin energy enforcer engine enhance enigmatic enjoyably enlarged
41
+ enormous enquirer enrollment ensemble entryway enunciate envoy enzyme epidemic equipment erasable
42
+ ergonomic erratic eruption escalator eskimo esophagus espresso essay estrogen etching eternal
43
+ ethics etiquette eucalyptus eulogy euphemism euthanize evacuation evergreen evidence evolution exam
44
+ excerpt exerciser exfoliate exhale exist exorcist explode exquisite exterior exuberant fabric
45
+ factory faded failsafe falcon family fanfare fasten faucet favorite feasibly february federal
46
+ feedback feigned feline femur fence ferret festival fettuccine feudalist feverish fiberglass
47
+ fictitious fiddle figurine fillet finalist fiscally fixture flashlight fleshiness flight florist
48
+ flypaper foamless focus foggy folksong fondue footpath fossil fountain fox fragment freeway fridge
49
+ frosting fruit fryingpan gadget gainfully gallstone gamekeeper gangway garlic gaslight gathering
50
+ gauntlet gearbox gecko gem generator geographer gerbil gesture getaway geyser ghoulishly gibberish
51
+ giddiness giftshop gigabyte gimmick giraffe giveaway gizmo glasses gleeful glisten glove glucose
52
+ glycerin gnarly gnomish goatskin goggles goldfish gong gooey gorgeous gosling gothic gourmet
53
+ governor grape greyhound grill groundhog grumbling guacamole guerrilla guitar gullible gumdrop
54
+ gurgling gusto gutless gymnast gynecology gyration habitat hacking haggard haiku halogen hamburger
55
+ handgun happiness hardhat hastily hatchling haughty hazelnut headband hedgehog hefty heinously
56
+ helmet hemoglobin henceforth herbs hesitation hexagon hubcap huddling huff hugeness hullabaloo
57
+ human hunter hurricane hushing hyacinth hybrid hydrant hygienist hypnotist ibuprofen icepack icing
58
+ iconic identical idiocy idly igloo ignition iguana illuminate imaging imbecile imitator immigrant
59
+ imprint iodine ionosphere ipad iphone iridescent irksome iron irrigation island isotope issueless
60
+ italicize itemizer itinerary itunes ivory jabbering jackrabbit jaguar jailhouse jalapeno jamboree
61
+ janitor jarring jasmine jaundice jawbreaker jaywalker jazz jealous jeep jelly jeopardize jersey
62
+ jetski jezebel jiffy jigsaw jingling jobholder jockstrap jogging john joinable jokingly journal
63
+ jovial joystick jubilant judiciary juggle juice jujitsu jukebox jumpiness junkyard juror justifying
64
+ juvenile kabob kamikaze kangaroo karate kayak keepsake kennel kerosene ketchup khaki kickstand
65
+ kilogram kimono kingdom kiosk kissing kite kleenex knapsack kneecap knickers koala krypton
66
+ laboratory ladder lakefront lantern laptop laryngitis lasagna latch laundry lavender laxative
67
+ lazybones lecturer leftover leggings leisure lemon length leopard leprechaun lettuce leukemia
68
+ levers lewdness liability library licorice lifeboat lightbulb likewise lilac limousine lint lioness
69
+ lipstick liquid listless litter liverwurst lizard llama luau lubricant lucidity ludicrous luggage
70
+ lukewarm lullaby lumberjack lunchbox luridness luscious luxurious lyrics macaroni maestro magazine
71
+ mahogany maimed majority makeover malformed mammal mango mapmaker marbles massager matchstick
72
+ maverick maximum mayonnaise moaning mobilize moccasin modify moisture molecule momentum monastery
73
+ moonshine mortuary mosquito motorcycle mousetrap movie mower mozzarella muckiness mudflow mugshot
74
+ mule mummy mundane muppet mural mustard mutation myriad myspace myth nail namesake nanosecond
75
+ napkin narrator nastiness natives nautically navigate nearest nebula nectar nefarious negotiator
76
+ neither nemesis neoliberal nephew nervously nest netting neuron nevermore nextdoor nicotine niece
77
+ nimbleness nintendo nirvana nuclear nugget nuisance nullify numbing nuptials nursery nutcracker
78
+ nylon oasis oat obediently obituary object obliterate obnoxious observer obtain obvious occupation
79
+ oceanic octopus ocular office oftentimes oiliness ointment older olympics omissible omnivorous
80
+ oncoming onion onlooker onstage onward onyx oomph opaquely opera opium opossum opponent optical
81
+ opulently oscillator osmosis ostrich otherwise ought outhouse ovation oven owlish oxford oxidize
82
+ oxygen oyster ozone pacemaker padlock pageant pajamas palm pamphlet pantyhose paprika parakeet
83
+ passport patio pauper pavement payphone pebble peculiarly pedometer pegboard pelican penguin peony
84
+ pepperoni peroxide pesticide petroleum pewter pharmacy pheasant phonebook phrasing physician plank
85
+ pledge plotted plug plywood pneumonia podiatrist poetic pogo poison poking policeman poncho popcorn
86
+ porcupine postcard poultry powerboat prairie pretzel princess propeller prune pry pseudo psychopath
87
+ publisher pucker pueblo pulley pumpkin punchbowl puppy purse pushup putt puzzle pyramid python
88
+ quarters quesadilla quilt quote racoon radish ragweed railroad rampantly rancidity rarity raspberry
89
+ ravishing rearrange rebuilt receipt reentry refinery register rehydrate reimburse rejoicing
90
+ rekindle relic remote renovator reopen reporter request rerun reservoir retriever reunion revolver
91
+ rewrite rhapsody rhetoric rhino rhubarb rhyme ribbon riches ridden rigidness rimmed riptide riskily
92
+ ritzy riverboat roamer robe rocket romancer ropelike rotisserie roundtable royal rubber rudderless
93
+ rugby ruined rulebook rummage running rupture rustproof sabotage sacrifice saddlebag saffron
94
+ sainthood saltshaker samurai sandworm sapphire sardine sassy satchel sauna savage saxophone scarf
95
+ scenario schoolbook scientist scooter scrapbook sculpture scythe secretary sedative segregator
96
+ seismology selected semicolon senator septum sequence serpent sesame settler severely shack shelf
97
+ shirt shovel shrimp shuttle shyness siamese sibling siesta silicon simmering singles sisterhood
98
+ sitcom sixfold sizable skateboard skeleton skies skulk skylight slapping sled slingshot sloth
99
+ slumbering smartphone smelliness smitten smokestack smudge snapshot sneezing sniff snowsuit
100
+ snugness speakers sphinx spider splashing sponge sprout spur spyglass squirrel statue steamboat
101
+ stingray stopwatch strawberry student stylus suave subway suction suds suffocate sugar suitcase
102
+ sulphur superstore surfer sushi swan sweatshirt swimwear sword sycamore syllable symphony synagogue
103
+ syringes systemize tablespoon taco tadpole taekwondo tagalong takeout tallness tamale tanned
104
+ tapestry tarantula tastebud tattoo tavern thaw theater thimble thorn throat thumb thwarting tiara
105
+ tidbit tiebreaker tiger timid tinsel tiptoeing tirade tissue tractor tree tripod trousers trucks
106
+ tryout tubeless tuesday tugboat tulip tumbleweed tupperware turtle tusk tutorial tuxedo tweezers
107
+ twins tyrannical ultrasound umbrella umpire unarmored unbuttoned uncle underwear unevenness
108
+ unflavored ungloved unhinge unicycle unjustly unknown unlocking unmarked unnoticed unopened unpaved
109
+ unquenched unroll unscrewing untied unusual unveiled unwrinkled unyielding unzip upbeat upcountry
110
+ update upfront upgrade upholstery upkeep upload uppercut upright upstairs uptown upwind uranium
111
+ urban urchin urethane urgent urologist username usher utensil utility utmost utopia utterance
112
+ vacuum vagrancy valuables vanquished vaporizer varied vaseline vegetable vehicle velcro vendor
113
+ vertebrae vestibule veteran vexingly vicinity videogame viewfinder vigilante village vinegar violin
114
+ viperfish virus visor vitamins vivacious vixen vocalist vogue voicemail volleyball voucher voyage
115
+ vulnerable waffle wagon wakeup walrus wanderer wasp water waving wheat whisper wholesaler wick
116
+ widow wielder wifeless wikipedia wildcat windmill wipeout wired wishbone wizardry wobbliness
117
+ wolverine womb woolworker workbasket wound wrangle wreckage wristwatch wrongdoing xerox xylophone
118
+ yacht yahoo yard yearbook yesterday yiddish yield yo-yo yodel yogurt yuppie zealot zebra zeppelin
119
+ zestfully zigzagged zillion zipping zirconium zodiac zombie zookeeper zucchini
120
+ `;
121
+
122
+ export const CHOICE_WORDLIST_SIZE = 1_296;
123
+ export const CHOICE_WORD_ENTROPY_BITS = Math.log2(CHOICE_WORDLIST_SIZE);
124
+ export const STANDARD_CHOICE_WORD_COUNT = 4;
125
+ export const HIGH_CHOICE_WORD_COUNT = 5;
126
+ export const STANDARD_CHOICE_ENTROPY_BITS = STANDARD_CHOICE_WORD_COUNT * CHOICE_WORD_ENTROPY_BITS;
127
+ export const HIGH_CHOICE_ENTROPY_BITS = HIGH_CHOICE_WORD_COUNT * CHOICE_WORD_ENTROPY_BITS;
128
+
129
+ export const EFF_SHORT_WORDLIST_2: readonly string[] = Object.freeze(
130
+ EFF_SHORT_WORDLIST_2_TEXT.trim().split(/\s+/),
131
+ );
132
+
133
+ const EFF_SHORT_WORD_SET: ReadonlySet<string> = new Set(EFF_SHORT_WORDLIST_2);
134
+
135
+ export function choiceWordAt(index: number): string {
136
+ const word = EFF_SHORT_WORDLIST_2[index];
137
+ if (word === undefined) {
138
+ throw new RangeError(`Choice word index is out of range: ${index}`);
139
+ }
140
+ return word;
141
+ }
142
+
143
+ export function isChoiceWord(value: string): boolean {
144
+ return EFF_SHORT_WORD_SET.has(value);
145
+ }