Repository navigation
BeechCMS v0.6.1 — Release Notes
Released: July 10, 2026
Type: Patch — Bug Fixes & Security Hardening
Repository: fdemusso/BeechCMS
Closes: #184, #183, #182, #178, #139, #166, #162, #159, #136, #137, #134, #140, #158
Overview
v0.6.1 is a focused patch release that consolidates bug fixes and security hardening across the validation engine, authentication handlers, Kanban move operations, CORS configuration, rate-limiting, and query filters. It introduces depth and size bounds for json/tags branches, fixes the handling of explicit null values, restores correct top-level OR logic in engine query filters, and addresses all seven open findings from the security and validation audit tracked in 184.
All changes are non-breaking: no HTTP routes, public API payloads, or @beechcms/core barrel exports were modified. The full monorepo build and test suites (pnpm build && pnpm test) pass with zero TypeScript errors.
Bug Fixes
Validation Engine
-
Explicit
nullrejection whenallowNull: false—withEmptyPreprocessingpreviously passed explicitnullthrough unchecked when the field'sallowNulloption wasfalse. The value is now correctly rejected. (182) -
JSON and tags depth/size bounds —
jsonOrTagsSchemaaccepted arbitrarily deep/large payloads with no cap, unlike the richtext branch. The schema is now split intojsonSchema(fail-fast size check + depth-guarded control-char stripping walk) andtagsSchema(z.array(z.string())with a 100-item cap and per-tagcleanString/length checks). This prevents recursion errors and potential DoS on pathological inputs and also fixes thekanban-move.tscast crash caused by non-string tag values. (183) -
Relation seed schema cached per
idGeneratorinstance —compileSeedSchemaalways recompiled the full Zod tree for seeds with relation branches because the closure overidGeneratorwas skipping the cache entirely. The cache is now partitioned byidGeneratoridentity viaWeakMap, and the singlebuildCacheKey()call is reused on cache miss. (178) -
isPlainObjectrejects class instances —Date,Map,Set, and other class instances were accepted where a plain object was required. The proto-chain check now rejects anything whose prototype is notObject.prototypeornull. (audit finding #4, 184) -
isEffectivelyEmptyscoped torichtextbranch only — A JSON field shaped as{ type: 'doc', … }was misclassified as empty because the doc-root check was not gated on the actual branch type. (audit finding #3, 184) -
sanitizeRichtextJsonwrapsJSON.stringifyintry/catch— Honors the documented "never throws" contract for the sanitizer. (audit finding #7, 184) -
Depth guard added to
gatherRichtextText— ARICHTEXT_MAX_DEPTHconstant now preventsRangeErroron pathological structures. (audit finding #8, 184) -
Repeater sub-field
requiredflag respectsenforceRequiredFields— Sub-fields inside a repeater incorrectly ignored theenforceRequiredFieldsoption. (audit finding #5, 184) -
collectAssetListItemsbounded to 100 items —MAX_ASSET_LIST_ITEMS = 100cap prevents unbounded array processing. (audit finding #10, 184) -
Clarified "no valid fields" error message — The error returned when a seed payload contains no recognized fields is now unambiguous. (audit finding #1, 184)
API Query Filters
- Real
OR-group support in engine filter query —logic: 'OR'was accepted and validated by the public API but every condition got mapped into its ownFilterGroup; the engine always joined groups withAND, soORsilently returned narrower results.buildSelectQuerynow honors a newfilterLogicoption to join top-level filter groups withORwhile keeping conditions within a groupAND-ed.status/searchstayAND-ed regardless. (137)
Authentication
-
Email lowercased on login to match signup normalization — Signup stored emails in lowercase, but login lookup was case-sensitive, permanently locking out users who typed their original casing. (158)
(Note: this fix was tracked as #158 in the commit history and is included in this release.) -
Login
min-lengthcheck aligned withsetup/resetvalidation —validateLoginInputcalledtrim()before the length check, rejecting passwords with trailing whitespace thatsetupandresetalready accepted, permanently locking those users out. (159)
Kanban & Content
-
Duplicate tags on Kanban move prevented — The server-side atomic swap in
kanban-move.tsappendednewValuewithout checking ifwithoutOldalready contained it, causing duplicate tags on retried or cross-source moves. (140) -
Deleted card evicted from cache on 404 move — A stale cache entry for a deleted card was not cleared when a move operation returned 404, causing subsequent reads to serve ghost data. (139)
-
findByIdwrapped intry/catchinkanbanPositionHandler— Unhandled exceptions fromfindByIdno longer crash the handler on invalid IDs. (136) -
Relation label read from flat row, not
item.data—repository.findManyreturns flat rows viarowToData— there is no.dataproperty, soitem.data[labelAlias]threw and was swallowed by the surroundingcatch, leaving the relations map empty. (134)
CORS & Rate Limiting
-
PATCHadded toallowMethods;Idempotency-Keyadded toallowHeaders— Several endpoints (bulk, kanban-move, rename/retype, automation toggle, notifications read/unread) usePATCH, and the public API expectsIdempotency-Keycross-origin. Both were missing from the CORS config, causing preflight failures for cross-origin clients. (166) -
Public rate-limit key now includes seed from request path — The key generator was not incorporating the request path, causing unrelated routes to share the same rate-limit bucket. (162)
Regression Tests Added
All seven audit findings from 184 are covered by new regression tests co-located with their respective source files (<filename>.test.ts). Additionally:
logic: 'OR'filter group behavior verified end-to-end.- Explicit
nullrejection withallowNull: falseacross all branch types. json/tagsdepth and size cap boundary cases.- Duplicate-tag prevention on Kanban move retries.
Closed Issues
| Issue | Title |
|---|---|
| 184 | Security & validation audit |
| 183 | JSON/tags depth and size bounds missing |
| 182 | Explicit null not rejected when allowNull: false |
| 178 | Relation seed schema not cached per idGenerator |
| 166 | CORS missing PATCH method and Idempotency-Key header |
| 162 | Public rate-limit key ignores request path |
| 159 | Login rejects passwords with trailing whitespace |
| 158 | Login case-sensitive email lookup |
| 140 | Duplicate tags on Kanban move |
| 139 | Deleted card not evicted from cache on 404 move |
| 137 | logic: 'OR' silently returns AND results |
| 136 | findById not guarded in kanbanPositionHandler |
| 134 | Relation label read from missing item.data property |
Upgrade Notes
No migration steps are required. This release contains no schema changes, no new environment variables, and no breaking changes to the HTTP API or SDK surface.
Run the standard update procedure:
pnpm install
pnpm build