All notable changes to @goobits/security are documented here. The format adheres to Keep a Changelog and the project adheres to Semantic Versioning.
csrf: CSRF tokens are now HMAC-signed and bound to an opaque current session identifier. Factories require a secret of at least 32 bytes, and the framework-neutralgenerate()andvalidate()calls requiresessionBinding. Matching attacker-selected cookie/header values no longer pass validation.csrf/sveltekit: The adapter requires the signing secret and rotates a token when its authenticated session binding changes. Anonymous requests use a server-issued HttpOnly host binding cookie, with the__Host-prefix in secure deployments.- Removed the deprecated
GenerateOptionsandValidateOptionsaliases. UseCsrfGenerateOptionsandCsrfValidateOptions. jwt: Static-JWKS verification can now returnkey-not-found, allowing caller-owned caches to refresh only for an unknown key ID. Expiration remainsexpired; all other signature and claim failures remain opaque asinvalid.rate-limit:postgresRateLimitSchemaSqlis now the canonical default schema string. UsecreatePostgresRateLimitSchemaSql({ table })when a consumer owns a custom PostgreSQL table name.
- Prevented naive double-submit CSRF bypasses caused by sibling-domain cookie planting or any other attacker-selected matching cookie and request token.
rate-limit: Added one atomic PostgreSQL sliding-window store and its idempotent schema helper so multi-process services share canonical counters.request-body: Added a cross-runtime bounded async-iterable reader so Node and other stream hosts share one body-size enforcement owner.request-body: Added bounded FetchFormDataparsing so authentication and other form consumers never need to parse an unrestricted body first.rate-limit: Added explicit right-counted trusted-proxy-hop resolution for append-stylex-forwarded-fordeployments.crypto: AddedcreateIncrementalHasher()for bounded-memory SHA-256 and BLAKE3 hashing over caller-supplied chunks, backed by permissively licensedhash-wasm.crypto: Added versioned framed AES-GCM streams with authenticated ordering, terminal-frame validation, caller-bound context, and bounded plaintext buffering for large encrypted artifacts.jwt: Added one framework-agnostic, purpose-bound HMAC JWT primitive with algorithm, audience, issuer, type, lifetime, and required-claim verification. Principal authentication now delegates to this shared owner.jwt: Added bounded static-JWKS verification for externally issued JWTs, with asymmetric-public-key enforcement and mandatory algorithm, issuer, audience, and required-claim pinning. Network fetching remains caller-owned.redaction: AddedredactSecretText()for credentials embedded in environment assignments, authorization headers, and URLs.- A publish-only compiled ESM artifact with declarations and an isolated tarball smoke test for every public entrypoint. Workspace consumers continue to use the existing source exports.
request-origin: one framework-agnostic verifier for Fetch Metadata,Origin, andRefererchecks across browser mutation boundaries.audit: explicitfailureMode: 'throw'support for required durable audit pipelines, andsafeErrorContext()for bounded error logging.runtime: publicreadRuntimeEnv()andisProductionRuntime()helpers so packages and applications share one production-safe runtime decision.- A shared
PrincipalIdentitybase type for verified identity, principal-auth, and admin-auth results.
- Public algorithm and CSRF option aliases from 3.0 remain available as
deprecated wrappers around the canonical
HmacAlgorithm,CsrfGenerateOptions, andCsrfValidateOptionscontracts. This keeps the additive 3.1 release source-compatible while giving new consumers one type owner. rate-limit: added one generic resilient-store wrapper with an explicitclosed | fallbackfailure policy. Fallback mode requires a caller-owned store, and observer failures cannot change the selected policy.- Unknown runtime modes now use production-safe defaults; development bypasses
require an explicit
NODE_ENV=developmentorNODE_ENV=test. - Audit redaction defaults can only be extended, not disabled, and arbitrary exception messages and stacks are excluded from default projections.
- Trusted client-IP headers are bounded and validated before use.
- D1 rate-limit storage now accepts only the canonical JSON timestamp format; the expired numeric-counter migration bridge was removed.
- Principal authentication validates and bounds principal IDs, roles, API-key mappings, header names, algorithms, token lifetimes, and expected claims at construction or signing time. API keys must be unique and contain 32-4096 bytes.
- The successful API-key authentication method is consistently named
api-keyinstead ofapikey. - D1 audit sinks now propagate write failures after bounded logging so the
owning
AuditLoggercan honor its configuredreportorthrowpolicy. - Runtime checks distinguish an omitted argument from an explicitly absent deployment binding; explicit unknown bindings always retain production-safe behavior.
validation/sveltekit: Application handler errors now propagate to SvelteKit instead of being rewritten as generic validation500responses.validation/sveltekit: Malformed JSON bodies now use the body-validation response instead of being reported as internal middleware errors.
- The internal
resolveLogger()fallback is no longer exported from@goobits/security/logger. Consumers should pass a logger to public factories or use the exportednoopLoggerexplicitly. This closes the logger surface around stable public contracts ahead of v3. - Authentication-specific rate-limit presets moved to
@goobits/auth/security.@goobits/security/rate-limitnow owns only the generic counter mechanism, and therate-limit/authsubpath has been removed. - The root-only
SECURITY_PACKAGE_VERSIONconstant was removed. Package metadata is not a runtime security primitive; read the installed manifest when tooling needs a version.
- Removed legacy boolean CAPTCHA helpers. Use
verifyRecaptcha()andverifyTurnstile()directly so callers can branch on structured failure reasons instead of flattening security decisions totrue/false. - Removed the unused
ioredispeer and development dependency. Redis-backed CSRF remains client-agnostic through the publicRedisLikecontract. - Stopped exporting the private request-body and cookie parser helper types.
redaction: Normalized secret-key detection and recursive omission for public projections, including common camel-case, snake-case, and kebab-case variants.http-credentials: Strict, bounded parsers for Basic, Bearer, and explicit API-key authorization headers; constant-work Basic password verification; HMAC-bound API-key verifiers; secure key generation; and sanitized challenge responses.redaction: Recursive, cycle-safe structured-value redaction with conservative secret-key defaults and consumer-provided string scrubbing for application PII policy.crypto: Opaque, rotation-ready AES-GCM keyrings that expose key IDs without exposing key material.rate-limit: HMAC-backed store wrapper that keeps raw emails, usernames, IP addresses, and tokens out of rate-limit persistence while propagating backing-store failures for explicit consumer policy.csrf/sveltekit: New SvelteKit adapter for stateless double-submit cookies, bounded form/JSON token extraction, and unsafe-method middleware.- 🌐
turnstile: New@goobits/security/turnstilesub-export for Cloudflare Turnstile token verification with the same discriminated-union result shape asrecaptcha. - 🔒
csp: CSP3 and Trusted Types directives are now supported by the CSP builder. - 📦
rate-limit:peek()exposes limiter state without incrementing counters. crypto: New@goobits/security/cryptosub-export with framework-agnostic Web Crypto helpers for encoding, random bytes/hex, SHA-256, constant-time comparison, HMAC signatures, AES-GCM sealing/opening, and deterministicSecurityProofenvelopes. Adds narrower sub-exports forcrypto/encoding,crypto/signatures,crypto/aead, andcrypto/proofso auth/session and decentralized protocol packages can share one primitive layer without product-specific permissions.identity: New@goobits/security/identitysub-export with generic DID-WBA and HTTP Signature identity adapters. The adapters parse and validate request identity envelopes, enforce timestamp/domain checks, and call a consumer-supplied signature verifier so protocol packages can plug in their own key resolution without making this package Goobits-product-specific.principal-auth: New generic@goobits/security/principal-authsub-export for JWT/API-key principal authentication.admin-authnow delegates to this shared implementation and remains as a focused adapter for admin-only routes.
- 🛡️
csrf: Store failures during requested expiry checks now fail closed by default. Availability-sensitive callers must opt out explicitly. - 🔔
alerting: Added one generic, store-backed threshold observer and a singleinfo | warning | criticalseverity vocabulary for consumer packages. - 📦 module layout: CSRF and bounded request-body primitives now live at
honest public module paths (
csrf.tsandrequest-body) instead of private or implementation-named files. Existingvalidation/sveltekitre-exports remain available for compatibility. csrf/rate-limit: In-memory stores now enforce deterministic configurablemaxKeysbounds, cleaning stale entries before evicting the oldest active key.- Request bodies: Oversized cloned-body cancellation is non-blocking so a rejected inspection cannot deadlock while the original request remains available to the host framework.
- 🔔
alerting: Webhook delivery now aborts after a configurable timeout (5000msby default) instead of waiting indefinitely on an unavailable receiver. - 🧮
rate-limit: Limiter construction now rejects empty names and non-positive, fractional, non-finite, or unsafe window values. - 📦
validation/sveltekit: Validation options now satisfy exact optional property types without weakening public option shapes.
- 📚 README guidance refreshed for source-level distribution and current app paths.
- 🧪 Timing-sensitive security tests now document their probes more clearly.
- 📦 Development dependencies refreshed for the current package toolchain.
- Source-only distribution via git submodule.
package.json#exportsnow points directly at./src/*.ts. No build step, nodist/, no npm publish. Consumers add this repo as a git submodule, wire it into theirpnpm-workspace.yaml, and their bundler (Vite/esbuild/SvelteKit) compiles the source as part of its own pipeline. Removed:tsup,tsup.config.ts,@arethetypeswrong/*,publint,scripts/attw.mjs, thebuild/dev/attw/publint/prepublishOnlyscripts.
Security
recaptcha:allowInDevelopmentdefault flipped fromtruetofalse. Closes a silent-bypass foot-gun on runtimes that don't setNODE_ENV(Cloudflare Workers, Deno, CI). Consumers who relied on the old behavior must now pass{ allowInDevelopment: true }explicitly.admin-auth: swappedjsonwebtoken(CJS, Node-only) forjose(Web Crypto, cross-runtime). The module now genuinely loads on Cloudflare Workers, Deno, and Bun.createAdminTokenis nowasync.admin-auth: JWT verification now pinsalgorithms: ['HS256']by default (overridable viaalgorithmsconfig). Defense-in-depth against future jsonwebtoken-style regressions and algorithm-confusion attacks.admin-auth:jwtSecretis now validated to be ≥32 characters atcreateAdminAuth()time. Throws loudly on weak secrets.csrf:DISABLE_CSRFnow throws atcreateCsrf()time whenNODE_ENV === 'production'. Previously it only logged. Fixes JSDoc-vs-implementation drift.csrf: AddedfailClosed?: booleanoption - store errors returnfalsefromvalidate()when set. Default remains fail-open (availability over correctness); compliance-sensitive routes can opt in.rate-limit:MemoryRateLimitStorenow performs opportunistic cleanup (~1% chance per increment) to bound memory growth on attacker-rotated identifiers.rate-limit:getClientIPnow requires explicittrustHeadersopt-in. By default returns'unknown'- refuses to blindly trust spoof-friendly proxy headers._internal/cookies:serializeCookienow validates cookie name + value against RFC 6265 character classes. Throws on CRLF /;/,/\/"/ space in values (mitigates header-injection latent risk).
API + types
audit:withAuditaddsredactKeysoption, defaulting to['password', 'token', 'secret', 'apiKey', 'authorization', 'creditCard', 'cvv']. Request body capture (includeRequestBody: true) now strips these fields before logging. PassredactKeys: []to disable explicitly.audit: documented fire-and-forget dispatch semantics + outcome derivation rules in JSDoc.audit: caller-suppliedtimestampinauditor.log({ timestamp })now correctly takes precedence (was always being overwritten by spread order).alerting:Alert.sourcewidened from literal'goobits/security'tostring. Lets app code reuse the same channels for its own alerts.rate-limit: removed deadsetEntrymethod fromRateLimitStoreinterface (was never called and the in-memory implementation ignored itsttlMsarg).rate-limit:RateLimitResult.windowis now also populated on theallowed: truebranch (was previously only onallowed: false), so consumers can emitX-RateLimit-*headers consistently.index.ts: barrel now re-exportscreateRedisCsrfStoreand the three auth rate-limit factories. Previously only reachable via subpath.
Build + docs
tsconfig.test.json: added sopnpm typecheckcovers tests as well assrc/.package.json:zodmoved intopeerDependenciesMeta.optional: true. Aligns with the package's actual runtime behavior - consumers who don't import@goobits/security/validationdon't need zod.package.json: removedjsonwebtoken(and@types/jsonwebtoken) entirely; addedjoseto runtime deps._internal/env.ts(new): sharedreadEnv()+isProduction()helpers. Replaces four duplicatedglobalThis as unknown as { process? }shims across modules.- README: per-module runtime compatibility table; fixed
import.meta.env.PRODexample (was Vite-only); documentedwithAuditfire-and-forget semantics; documentedcookieOptionsreplace-not-merge; documentedgetClientIPno-default-trust policy; added explicit Zod v4 syntax callouts. - Added vitest suites for previously-untested modules:
recaptcha,audit,alerting,logger,rate-limit/auth,_internal/cookies.
First standalone-package release. The bump to v2 reflects breaking API changes vs the legacy internal v1.x; everything below is the v2 surface.
- ESM-only TypeScript-native package with full
.d.tsdeclarations - Subpath exports for every capability (
csrf,csrf-redis,csp,recaptcha,validation,rate-limit,rate-limit/auth,rate-limit/sveltekit,admin-auth,audit,audit/sveltekit,alerting,logger). Framework-agnostic primitives live at the parent subpaths; SvelteKit-specific adapters live under dedicated/sveltekitsubpaths so non-SvelteKit consumers never pay for the@sveltejs/kittypes. - Pluggable
Loggerinterface - every module acceptslogger?: Loggerand is silent by default; bring Pino/Winston/console as needed createCsrf()factory returning aCsrfProtectionwithgenerate/setCookie/validate/cleanup/clearcreateCspDirectives()+buildCsp()- fully parameterized CSP builder (no hardcoded vendor allowlist;extraSourcesis now caller-supplied)createCspNonce()for per-request nonce generationverifyRecaptcha()returns a discriminated-unionRecaptchaResultwith explicitreasoncodescreateRateLimiter()with multi-window sliding-counter support; pluggableRateLimitStorecreateRateLimitHandle()SvelteKit Handle helper (at@goobits/security/rate-limit/sveltekit)- Pre-baked
createLoginRateLimiter/createRegistrationRateLimiter/createPasswordResetRateLimiterfactories createAdminAuth()with JWT + API key fallback (constant-time comparison)generateAdminApiKey()returns a 256-bit hex API keycreateAuditLogger()(framework-agnostic) +withAudit()SvelteKit handler wrapper (at@goobits/security/audit/sveltekit) for structured event emission with pluggable sinkscreateSecurityAlerter()+createWebhookChannel()for rule-based dispatch- Comprehensive test suite (vitest) covering CSRF, CSP, rate-limit, validation, admin-auth
- All source files converted from JavaScript to TypeScript with strict typing throughout
- Replaced direct
@goobits/loggerdependency with a pluggableLoggerinterface - package now has zero hard logging dep - Bumped
zodpeer dep from^3.xto^4.x; validation helpers updated for v4 API (safeParseAsync,issues,z.email()) - CSP builder no longer ships an opinionated vendor allowlist; consumers must pass
extraSourcesfor any vendor URLs they need (Stripe, fonts, CDNs, dev domains) - Rate limiter API redesigned around a
windows: [{ name, windowMs, maxEvents }]config (replacing the fixed short/medium/long windows in v1.x) verifyRecaptcha()now returnsRecaptchaResult(discriminated union) instead of a plain boolean- Cookie + header parsing moved to internal helpers; no external dependency on
cookieorset-cookie-parser - Minimum Node version is now 22 (was 18)
- Internal migration docs (
RATE_LIMITER_MIGRATION.md,REDIS_RATE_LIMITER_QUICK_START.md) - these were specific to the source repo's internal cutover, not relevant to standalone consumers - Opinionated default vendor allowlist from CSP (Stripe paths, MapLibre CDN, local development domains) - consumers now supply these via
extraSources - Hard dependency on
@sveltejs/kitruntime - now an optional peer (CSRF/CSP/recaptcha/rate-limit work in any Fetch-API environment) - Hard dependency on
ioredis- now an optional peer (only required when usingcsrf-redisor a Redis rate-limit store) - Hard dependency on
jsonwebtoken- replaced withjoseas the package's only runtime dependency
- Verified clean: no hardcoded secrets, no embedded credentials, no project-specific paths in source
- All cryptographic primitives use Web Crypto from
globalThis.crypto; no Node-onlycryptoimports - Constant-time comparison preserved for CSRF + admin API key
- Default cookie options:
HttpOnly,SameSite=Lax,Securein production