Skip to content

BeechCMS v0.6.1 — Release Notes

Choose a tag to compare

@fdemusso fdemusso released this 10 Jul 09:10
· 148 commits to master since this release

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 null rejection when allowNull: false — withEmptyPreprocessing previously passed explicit null through unchecked when the field's allowNull option was false. The value is now correctly rejected. (182)

  • JSON and tags depth/size bounds — jsonOrTagsSchema accepted arbitrarily deep/large payloads with no cap, unlike the richtext branch. The schema is now split into jsonSchema (fail-fast size check + depth-guarded control-char stripping walk) and tagsSchema (z.array(z.string()) with a 100-item cap and per-tag cleanString/length checks). This prevents recursion errors and potential DoS on pathological inputs and also fixes the kanban-move.ts cast crash caused by non-string tag values. (183)

  • Relation seed schema cached per idGenerator instance — compileSeedSchema always recompiled the full Zod tree for seeds with relation branches because the closure over idGenerator was skipping the cache entirely. The cache is now partitioned by idGenerator identity via WeakMap, and the single buildCacheKey() call is reused on cache miss. (178)

  • isPlainObject rejects 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 not Object.prototype or null. (audit finding #4, 184)

  • isEffectivelyEmpty scoped to richtext branch 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)

  • sanitizeRichtextJson wraps JSON.stringify in try/catch — Honors the documented "never throws" contract for the sanitizer. (audit finding #7, 184)

  • Depth guard added to gatherRichtextText — A RICHTEXT_MAX_DEPTH constant now prevents RangeError on pathological structures. (audit finding #8, 184)

  • Repeater sub-field required flag respects enforceRequiredFields — Sub-fields inside a repeater incorrectly ignored the enforceRequiredFields option. (audit finding #5, 184)

  • collectAssetListItems bounded to 100 items — MAX_ASSET_LIST_ITEMS = 100 cap 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 own FilterGroup; the engine always joined groups with AND, so OR silently returned narrower results. buildSelectQuery now honors a new filterLogic option to join top-level filter groups with OR while keeping conditions within a group AND-ed. status/search stay AND-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-length check aligned with setup/reset validation — validateLoginInput called trim() before the length check, rejecting passwords with trailing whitespace that setup and reset already accepted, permanently locking those users out. (159)

Kanban & Content

  • Duplicate tags on Kanban move prevented — The server-side atomic swap in kanban-move.ts appended newValue without checking if withoutOld already 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)

  • findById wrapped in try/catch in kanbanPositionHandler — Unhandled exceptions from findById no longer crash the handler on invalid IDs. (136)

  • Relation label read from flat row, not item.data — repository.findMany returns flat rows via rowToData — there is no .data property, so item.data[labelAlias] threw and was swallowed by the surrounding catch, leaving the relations map empty. (134)

CORS & Rate Limiting

  • PATCH added to allowMethods; Idempotency-Key added to allowHeaders — Several endpoints (bulk, kanban-move, rename/retype, automation toggle, notifications read/unread) use PATCH, and the public API expects Idempotency-Key cross-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 null rejection with allowNull: false across all branch types.
  • json/tags depth 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