@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.
- package/AUTHORING.md +493 -5
- package/CHANGELOG.md +127 -1
- package/README.md +52 -6
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +106 -62
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +63 -55
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +78 -82
- package/bin/apifuse-pack-types.ts +583 -0
- package/bin/apifuse-perf.ts +59 -140
- package/bin/apifuse-record.ts +698 -113
- package/bin/apifuse-submit-check.ts +517 -44
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +4 -4
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +16 -2
- package/dist/auth.js +76 -18
- package/dist/ceremonies/index.d.ts +9 -1
- package/dist/ceremonies/index.js +117 -29
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +34 -35
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/config/loader.d.ts +176 -17
- package/dist/config/loader.js +434 -161
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +7 -14
- package/dist/contract-types.d.ts +3 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +6 -6
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +13 -1
- package/dist/define.js +391 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +41 -3
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +50 -42
- package/dist/index.js +41 -37
- package/dist/lint.d.ts +6 -1
- package/dist/lint.js +370 -18
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +13 -11
- package/dist/provider.js +10 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +3 -1
- package/dist/runtime/auth-flow.js +8 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +138 -40
- package/dist/runtime/cache.d.ts +2 -1
- package/dist/runtime/cache.js +173 -23
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +514 -124
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +25 -3
- package/dist/runtime/http.d.ts +3 -2
- package/dist/runtime/http.js +517 -55
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +371 -23
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +58 -52
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +5 -5
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
- package/dist/runtime/resolver-vendors/browser.js +282 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
- package/dist/runtime/resolver-vendors/types.d.ts +83 -0
- package/dist/runtime/resolver-vendors/types.js +69 -0
- package/dist/runtime/resolver.d.ts +59 -0
- package/dist/runtime/resolver.js +705 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +5 -2
- package/dist/runtime/state.js +280 -74
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +30 -5
- package/dist/runtime/stealth.js +523 -259
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +12 -27
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +117 -3
- package/dist/server/self-test.js +787 -151
- package/dist/server/serve-implementation.d.ts +210 -0
- package/dist/server/serve-implementation.js +2077 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1143
- package/dist/server/types.d.ts +34 -9
- package/dist/server/types.js +8 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +21 -21
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +488 -28
- package/dist/types.d.ts +566 -19
- package/dist/types.js +1 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +42 -7
- package/src/auth-turn/index.ts +2 -2
- package/src/auth.ts +146 -86
- package/src/ceremonies/index.ts +167 -92
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/config/loader.ts +667 -289
- package/src/contract-serialization.ts +10 -18
- package/src/contract-types.ts +3 -2
- package/src/contract.ts +14 -28
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +631 -495
- package/src/dev.ts +4 -9
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +56 -11
- package/src/fixture-sanitization.ts +247 -0
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +157 -44
- package/src/lint.ts +488 -154
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +65 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +13 -7
- package/src/runtime/browser.ts +252 -207
- package/src/runtime/cache.ts +209 -81
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +685 -216
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +37 -19
- package/src/runtime/http.ts +645 -65
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +530 -67
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +84 -77
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +10 -13
- package/src/runtime/request-options.ts +679 -9
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +408 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
- package/src/runtime/resolver-vendors/types.ts +173 -0
- package/src/runtime/resolver.ts +1060 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +399 -161
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +681 -295
- package/src/runtime/stt.ts +39 -113
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +6 -1
- package/src/server/index.ts +30 -7
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +1030 -180
- package/src/server/serve-implementation.ts +3060 -0
- package/src/server/serve.ts +1 -1781
- package/src/server/types.ts +12 -13
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +27 -33
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +16 -20
- package/src/testing/index.ts +11 -2
- package/src/testing/run.ts +668 -74
- package/src/types.ts +665 -35
- package/src/user-input.ts +118 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/src/runtime/cache.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
return
|
|
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:
|
|
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
|
-
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
return
|
|
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:
|
|
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
|
+
}
|