Repository navigation
Releases: swina/whoobe_project
Release list
Whoobe v0.3.3
Whoobe 0.3.3 - release notes
A patch release of 0.3.0 (Operate): the tokens used by the AI functions, shown to whoever asked and, when an administrator turns it on,
recorded per person; a retention period for the AI history. Written from CHANGES.md on 2026-10-27; the procedure is in
release-process.md. One migration (0073_ai_usage.sql).
AI usage
- Every AI answer carries what it consumed. The answer of each AI function (page, snippet and element text, website structure, wizard
texts, image, page translation) has ausageobject: provider calls, tokens in, tokens out. The dashboard and the editor show it in a small
notice to the person who asked. Nothing is stored for this. Retries and translation batches are counted; a reply that fails to parse is
counted (it was paid for); a provider that reports no tokens counts as a call. - Recording per person, off by default (Security > AI Usage,
settings:manage). While on, one row per request: the person, the
function, provider, model, tokens and date, never the prompt, the answer or what was being edited, deleted after 90 days by default;
monthly totals stay until the person is deleted. The page carries a warning: a record per person is personal data about staff and can be
used to watch their work; inform them, and check whether your country needs an agreement for such a tool (in Italy, article 4 of the
Statuto dei Lavoratori). See data protection. - The report opens as its own tab (Open the AI usage report): totals by person, function, model and day, one person's requests, monthly
totals, CSV export. Every user's figures need the new permissionai_usage:read(administrators only by default); opening one person's
requests is written to the audit log. Every signed-in person sees what is recorded about them under Mine. - Providers: OpenAI-compatible APIs, Anthropic and Google report tokens; for images, the gpt-image models and OpenRouter (tried with a real
OpenRouter image model). Details in AI features, section 5.
Security
F-064 (security/findings.md, Medium), found by the project on 2026-10-27 while designing the report: the AI history
(whoobe_ai_generated) keeps the full prompt and answer of every page, snippet, translation and image generation with the user id, without
any retention, and was missing from the personal-data table of the data-protection page. Affected: every earlier release. Fixed: a period
for it can be set next to the AI usage switch (Keep generated pages and prompts for), applied by the daily retention job; the data-protection
page lists both tables. Empty keeps them, as before: set a period if you do not need the history.
Other changes
- Data Objects: the Generate button of a slug field, on an empty field, now makes the slug from the object's title (it did nothing before).
- Documentation: How Whoobe evolves, what comes next, what is built only when an organisation funds it, and
the rule that the code stays MIT; contributors and testers are asked for.
Behaviour changes to check before you upgrade
| Change | What you see | What to do |
|---|---|---|
Migration 0073_ai_usage.sql: tables ai_usage and ai_usage_monthly, three columns in security_settings |
runs at the start of core-api | back up before upgrading, as for any migration |
The answers of the AI routes have a new usage field |
nothing, unless a client of yours rejects unknown fields | allow the field |
New permission ai_usage:read, given to the admin role |
administrators see every user's AI usage report | give it to another role only if that role should see it |
| AI usage recording | off: nothing is recorded until an administrator turns it on | inform your staff before turning it on |
| Retention of the AI history | unchanged (kept) until you set a period | set one in Security > AI Usage if you do not need the history |
New routes GET /ai/usage, /ai/usage/monthly, /ai/usage/me |
in the API reference | none |
No new environment variable. Upgrade as for any release (scripts/workspaces.sh upgrade v0.3.3, or pull the 0.3.3 images).
Known limits
The limits of 0.3.0, 0.3.1 and
0.3.2 still hold. For this release: the token counts are those the provider reports, not a bill (no
price is computed); a provider that reports no tokens shows calls only; the recording covers the dashboard and editor AI functions, not the
publisher or the Gateway's API clients. Tried against a real provider (OpenRouter): images, a snippet and the text of an element; the
website wizard and the page translation share the same provider code and were tested with a fake provider only.
Images (by digest, signed with cosign, keyless)
admin-dashboard ghcr.io/swina/whoobe-admin-dashboard@sha256:c540e788fb73dd9068d56db56d71deaa497fcdeb1742558e2f53763b932f3c26
asset-processor ghcr.io/swina/whoobe-asset-processor@sha256:7d9a2909c40e66560cd98ac55423c24af35a6a2a902f87c93fdc1dba71d48657
core-api ghcr.io/swina/whoobe-core-api@sha256:87792af53a7cb218c9a525f20706bb5d8ab9881a82187dd81f99bc49acc03aa7
gateway ghcr.io/swina/whoobe-gateway@sha256:698ba5971eb51bee5d01611bc577de07ea926678512eab2dd8e116f7b8e83bb3
publisher ghcr.io/swina/whoobe-publisher@sha256:ccb58a9ccfdd9f9af29865a8bfa27b999a4676a6f90c2650ce1e04634cb9a656
whoobe-one-spike ghcr.io/swina/whoobe-whoobe-one-spike@sha256:497ad275ec39c1ca434f16a7987fa8200816345095e220c3fadfebf42f3ebfb8
Whoobe v0.3.2
Whoobe 0.3.2 - release notes
A patch release of 0.3.0 (Operate): the publisher image without the build tools it carried, and the image scan of the CI now covering it. Written
from CHANGES.md on 2026-10-26; the procedure is in release-process.md. No migration.
Security
F-063 (security/findings.md), found by the project on 2026-10-26 while checking the published 0.3.1 images. The
publisher image (the optional service that runs the site generator for the dashboard's Publish tab) was never in the image scan of the
security workflow: the scan took its list from docker compose config --images, and the publisher sits behind the compose profile publisher.
The published whoobe-publisher:0.3.1 has 171 fixable HIGH/CRITICAL reports (11 critical), almost all in build tools shipped with it: the Go
standard library of three esbuild binaries, the pnpm 9.0.0 left in the corepack cache, the npm of the node base image, and dev packages of
the build tree (vite, vitest, tinypool, devalue, brace-expansion). Affected: the publisher image of 0.3.1; earlier releases were
built the same way and were not measured. The other five images are not affected.
Exposure: the publisher is optional, publishes no port and runs only the site builds the dashboard asks for; none of these tools runs as a server,
but they do run on content written by editors.
What changed:
- the publisher is built like the other services: the runtime base without pnpm, corepack or npm, and the pruned production tree. 0 fixable
HIGH/CRITICAL on the new image; - the SDK packages the generator puts in each generated site (
vendor/*.tgz) are packed when the image is built, so the image needs no package
manager; outside the image the generator still packs them withpnpm; - the image scan of the
securityworkflow builds and scans the publisher too, so a release cannot again ship it unscanned.
Behaviour changes to check before you upgrade
| Change | What you see | What to do |
|---|---|---|
The publisher image has no pnpm, npm or corepack |
the same generated sites; the SDK tarballs in vendor/ come from the image |
a script of yours that ran a package manager inside the publisher container: run it elsewhere |
New environment variable WHOOBE_SDK_TARBALL_DIR, set in the image to /opt/whoobe-sdk (configuration reference) |
nothing | do not override it in your compose file |
Documentation sites: whoobe-publish --kind docs has three new optional flags, --version-label, --version-url and --repo-url (a version badge after the site title, a repository link in the header) |
nothing until you pass them | see documentation sites |
No migration. Upgrade as for any release (scripts/workspaces.sh upgrade v0.3.2, or pull the 0.3.2 images). Only the publisher image
changes in substance; pull or rebuild it if you run the publisher profile.
Known limits
The limits of 0.3.0 and 0.3.1 still hold. For this
release: a publish job was run in the new image through the publisher's own job runner (Vue and Next sites, against a local API), not from the
dashboard of a running workspace with the publisher profile; the arm64 image is built and scanned only by the release workflow.
Images (by digest, signed with cosign, keyless)
admin-dashboard ghcr.io/swina/whoobe-admin-dashboard@sha256:8e79a923108fd445156778472cb610116615d42e7ff7c6a53ecc59fd64e13e15
asset-processor ghcr.io/swina/whoobe-asset-processor@sha256:bb53311c7d6d8fbfac89a4e3aae2120055b414640e621c67fb9da2633b557136
core-api ghcr.io/swina/whoobe-core-api@sha256:68a0ad91424dfa22b1a457ca8f20f3665910d87000c4981eb3d61fccf82bb70d
gateway ghcr.io/swina/whoobe-gateway@sha256:1c94b267cb83e8803ee2174468975729946afe6c340cab8b646581ed426e19f0
publisher ghcr.io/swina/whoobe-publisher@sha256:1a89eb22d2c7b14ab09dd0df7c10e81f12d667204ed694797cd8fa09c0e4e727
whoobe-one-spike ghcr.io/swina/whoobe-whoobe-one-spike@sha256:643908abe9277cff3b0197d387451333bc9c55e7dad51e2b0bef782aa1e11d88
Whoobe v0.3.1
Whoobe 0.3.1 - release notes
A patch release of 0.3.0 (Operate): security updates of dependencies and of the two nginx images, and two changes to the public sandbox's
visit log. Written from CHANGES.md on 2026-10-26; the procedure is in release-process.md. No new feature, no migration.
Security
Advisories published by others in third-party packages, found by the nightly security run of 2026-10-24 (static scan, pnpm audit --prod) and
by the image scan; none was found by the project, so all are public (disclosure policy). Affected: every release
from 0.1.0 to 0.3.0 (the same versions are in the lockfile of each tag).
No 0.2.1. The support policy (release process, section 2) gives security fixes to the latest two minor lines; for this
patch the owner decided (2026-10-26) not to release a 0.2.1: 0.2.0 is one week old and the upgrade from it to 0.3.x has six migrations, tested from
0.2.0 on 2026-10-23. On 0.2.x, upgrade to 0.3.1.
| Package | Was | Now | Advisory | Where |
|---|---|---|---|---|
zod |
3.25.76 | 4.6.7 | GHSA-mrhx-c7xc-m3w5 (high): memory exhaustion when an array schema without a length limit validates a large array | core, schema SDK (validation of Data Objects written through the gateway, site specifications, page content) |
body-parser |
1.20.6 | 1.20.9 | GHSA-27r3-jxmh-p828 (high): denial of service via trailing bytes after a compressed body | express in core-api, asset-processor, gateway |
multer |
2.4.0 | 2.5.0 | GHSA-q7qp-rjg7-q26m (high): denial of service via a pending file filter after the request is torn down | asset-processor uploads |
undici |
6.28.1 | 6.29.1 | GHSA-xjj6-f33q-929c (high): denial of service via too many content encodings | gateway (OpenAPI parser) |
brotli-libs |
1.2.0-r1 | 1.2.1-r0 | CVE-2026-111497 (high) | the admin-dashboard and whoobe-one-spike images (nginx alpine base) |
curl, libcurl |
8.22.0-r0 | 8.23.0-r0 | CVE-2026-92392 (high) | the same two images |
The two nginx images now upgrade every Alpine package at build time instead of a list of named ones (the list grew by one package each
time the pinned base fell behind). The register entries are F-003 (dependencies) and F-050 (images) in security/findings.md. postgres:16-alpine still carries
the 26 reports of its gosu helper (advisory, see 0.3.0).
Behaviour changes to check before you upgrade
| Change | What you see | What to do |
|---|---|---|
| Zod 4 validates Data Objects written by API clients, site specifications and page content | the same values pass and fail; some error messages read differently (a site specification's messages are still rewritten in plain words) | a client that matches the exact text of an error message: check it |
The JSON Schema of a site specification (Settings > Import site specification > Download, GET of the spec endpoint) describes the element union with oneOf instead of anyOf |
same meaning (each element has its own kind) |
a tool that does not accept oneOf: tell us |
Sandbox profile: requests from ClaudeBot are not written to the visit log; new optional SANDBOX_UNTRACKED_IPS in /etc/whoobe-sandbox.env (addresses or CIDR ranges left out of the log, see sandbox) |
a smaller log | after the next reset, sudo nginx -t && sudo systemctl reload nginx once |
No migration. Upgrade as for any release (scripts/workspaces.sh upgrade v0.3.1, or pull the 0.3.1 images); the images must be rebuilt or pulled,
because the fixes are inside them.
Known limits
The limits of 0.3.0 still hold. For this release: the API and browser suites, isolation, TLS and
DAST passed on the release candidate 99365c4 (run 40297447238; the image scan PASS for five of the six own images: the publisher image is not in that scan, and the published 0.3.1 publisher has 171 fixable
HIGH/CRITICAL, mostly in build tools; F-063, found after the tag, fixed in a later patch), and the upgrade test from
v0.3.0 passed on 99c5041 (run 40295030726; only the Dockerfile of the two nginx images changed after it); a manual try of the site wizard, the spec import and gateway writes with Zod 4 on a running stack
was not done.
Images (by digest, signed with cosign, keyless)
admin-dashboard ghcr.io/swina/whoobe-admin-dashboard@sha256:019ba2ae4455c519db90a853565707247c5ace666a8e65ce03cccff7dec7ebca
asset-processor ghcr.io/swina/whoobe-asset-processor@sha256:1e9056d17e5394af84ae075c8113f9f04c91cecaf78af303714f352c2fdd1dfe
core-api ghcr.io/swina/whoobe-core-api@sha256:ace6e0b921ca7817a039dbf0c79d02740675fa8ddb061a918b981232e7fd204a
gateway ghcr.io/swina/whoobe-gateway@sha256:e94af59dcb39796eb45e1cc61b5196dc735d220278cc233536de4446d5f9adb0
publisher ghcr.io/swina/whoobe-publisher@sha256:a314e8d8282fa8a7df7f25092859af8256b8ec19d31e489e7f953087a2b14899
whoobe-one-spike ghcr.io/swina/whoobe-whoobe-one-spike@sha256:a7bc99daa60cdc866f33af7f642f4fdf1bc5b7b114326f4ec73bf47f46c46ef2
Whoobe v0.3.0
Whoobe 0.3.0 - release notes
Release 0.3.0, named Operate: Whoobe for those who run it for others (an agency with many client workspaces) and for an organisation on a
private network. Written from CHANGES.md on 2026-10-23; the procedure that turns this into a release is in release-process.md.
The plan is docs/plans/system/release-0.3-plan.md; on 2026-10-23 its scope was cut (decision 7): what is below is built, the rest moves to 0.4
(Not in this release yet). Because the version starts with 0., a minor release may change behaviour: read Behaviour changes before upgrading.
Base image: Node.js 22.23.4.
What is in it
Operate many workspaces
- Fleet commands,
scripts/workspaces.sh:list,status,verify,backupandupgrade <tag> [--canary slug] [--no-backup] [--yes]over
every workspace of the host. One failing workspace does not stop the others (exit status 1, the slugs are listed).upgradebacks up first (a
workspace whose backup fails is not upgraded), checks the tag out once, waits for health, and on a workspace running released images puts back the
previousWHOOBE_VERSIONif the new images cannot start; if no workspace could be recreated, the tree goes back to where it was. Every action is a
JSON line in$HOME/.whoobe/fleet-actions.log. Tried on three real throwaway workspaces and on released-style images. Guide. - Workspace label.
WHOOBE_WORKSPACEaddsworkspace="<name>"to every core-api metric and aworkspacefield to every log line (unset: output
unchanged). - Install from released images.
provision-workspace.sh --image-prefix P --image-version Vstarts a workspace from published images with no build;
the dashboard and the editor read their addresses when the container starts (/config.js), so one image serves every workspace.
Private network (no route to the internet)
- Profile
private(provision-workspace.sh --profile private): no Google Fonts, no Material Icons CDN, no Unsplash or picsum; Inter is bundled in
the dashboard and the editor, and image placeholders are a local grey picture. Run on a Docker network with no route out: 7 containers healthy, the
API suite andworkspaces.sh verifypass, no failed outside connection in any log. Page. - Custom fonts. Upload woff2 files in Settings > Custom fonts (several files of a family at once; family and style guessed from the names).
They are used in the editor, copied into generated sites and mobile apps, and written as@font-facein e-mail. Tried end to end on a network with no
route out. Settings guide. enable-tls.sh --reuse-certaccepts a certificate from the organisation's own CA on a private host name (syntax checked, not run).
Generate a site
- A site specification any LLM can write: schema, expansion into a bundle, instructions, check and import.
- Template wizard and Generate Website Wizard: snippets as default blocks, with placeholders; texts written by an AI (needs a configured provider;
the AI provider path has not been exercised); palettes made by a person, with a Palettes section in the menu; pages reorderable and collapsible. - Kits: a section of the dashboard to manage, export as zip and import; preview of the templates, Duplicate to edit, origin and version.
- Website preview with draft pages; deleting a website deletes its pages, templates, paths and theme; where a snippet is used, with a filter by
website or template. - Pages: a copy link button opens a page in the editor for a colleague on the same network (no credential in the link).
- Published sites: fixes to links, trailing slashes, static redirects, covers of the blog, the http to https redirect of the generated vhost, and the
classes of a container.
Supply chain
release.ymlrequires an annotated tag whose signature GitHub verifies, builds six images (core-api,asset-processor,gateway,publisher,
admin-dashboard,whoobe-one-spike) for amd64 and arm64, signs them with cosign, attests build provenance (SLSA) and drafts the release with the
digests,openapi.yamland the SBOM. Run on a trial tag, then installed fromghcr.ioon an isolated network; the trial was removed.
Security policy
- Disclosure policy: a High or Critical finding found by the project is private until the release that fixes it, then public in the register
(new column Public from); the private place is a draft security advisory with its temporary private fork; acknowledgement in 7 days, target 30
days to fix High/Critical and 90 days Medium. Policy,SECURITY.md. - Security results stay public with a release gate; markdown-it 14.3.3 and libx11 updated in the images.
Behaviour changes to check before you upgrade
| Change | What you see | What to do |
|---|---|---|
| The dashboard's image fallback was an Unsplash photo; it is now a local grey picture | broken images in the dashboard look different | nothing |
docker-compose.yml names the five application images ${WHOOBE_IMAGE_PREFIX:-whoobe}-<service>:${WHOOBE_VERSION:-local} |
unchanged when the variables are not set | nothing |
The dashboard and the editor read /config.js at start (build-time values remain the fallback) |
none for a workspace built as before | rebuild keeps working |
nginx routes /fonts, /whoobe-one/editor-base, and a browser navigation to /settings, /pages, /documents reaches the dashboard |
a refresh of these pages no longer shows Cannot GET |
on a workspace provisioned earlier, run scripts/render-workspace-nginx.sh <slug> and reload nginx |
POST /fonts and wizard palette create/update answer 409 (not 500) on a duplicate |
nothing | |
Excel import and XLSX export removed (the library xlsx had two high advisories and no fix): Objects > Export offers CSV and JSON; Class from file accepts .json, .csv, .tsv |
a .xlsx, .xls or .ods file is refused |
save the sheet as CSV first |
Kits get an origin: every kit that exists is marked user (editable); bundled kits are read-only, refreshed by the seed, and copied with Duplicate to edit. Kit templates with a duplicate or non-string id get a new one (migration 0068) |
the Kits section shows origin and version | nothing |
| The dashboard and the editor no longer load Google Fonts or Material Icons: Inter is bundled | the dashboard looks the same, with no request to fonts.googleapis.com |
nothing |
New optional variables: COMMUNITY_URL (the Help entry of the dashboard; unset = the project's GitHub Discussions, empty hides it), ADMIN_PUBLIC_URL (also used by copy editor link), PLACEHOLDER_IMAGES / VITE_PLACEHOLDER_IMAGES (local = grey picture instead of picsum), WHOOBE_WORKSPACE (label in metrics and logs), WHOOBE_IMAGE_PREFIX / WHOOBE_VERSION (named images; set by provision-workspace.sh --image-version) |
the dashboard sidebar has a Help entry | set COMMUNITY_URL= to hide it; all others are off by default |
In Assets, a type filter looks in the whole library and hides folders; audio files (mp3, wav, m4a) have their own type and player; files already stored as application/octet-stream are served with the type of their extension |
an mp3 uploaded before opens in an audio player | nothing |
Six migrations (0067 to 0072), applied by core-api at the first start; back up first (scripts/workspaces.sh upgrade does). Five are additions (new columns on kits and snippets, tables wizard_palettes and custom_fonts); 0068 rewrites data: in each kit, template ids that are not a string or repeat an earlier one are replaced by tpl-<8 hex> (the old editor wrote the number 0 for several templates); kits whose ids are fine are not touched. Upgrade test from v0.2.0 on 2026-10-23: 67 migrations at the start, 73 after, data written by 0.2.0 intact, login works (CI run 39937017622).
Known limits of this release
- The Assets changes (the type filter across folders, audio types and player, the PDF preview sending the session) were not checked in a browser before
the release; the PDF preview 404 reported by the owner has a probable cause (the session cookie missing on a cross-origin fetch) that was not reproduced. - The Generate Website Wizard has never been run with a real AI provider; the manual security checklist was not repeated for the features added since 0.2.0
(see the status summary). postgres:16-alpinecarries 26 HIGH/CRITICAL reports, all in itsgosuhelper (Go stdlib, runs once at start); reported, not gating. A derived image
without it is planned for 0.4.
Not in this release yet
Level 1 items of the plan that are not built, and so not promised: more than one replica (challenges and counters out of memory, shared limiters,
migration lock, multi-replica test), the managed-database variant and Helm chart, failed-attempt audit with correlation id and hash chain, metrics for
asset-processor and gateway, rehearsals on the reference host, the enterprise profile and the installation checklist. Not tried: pnpm build of a
generated site on a private network (needs a registry mirror), a real e-mail send, the mobile-app and docs site kinds, real Cloudflare Images.
Images (by digest, signed with cosign, keyless)
admin-dashboard ghcr.io/swina/whoobe-admin-dashboard@sha256:fd20431717aeeaeb72c1d73e7de626e3ce8d25ab9dfddc3041904f83cbd39227
asset-processor ghcr.io/swina/whoobe-asset-processor@sha256:0496536aa3773044207b3434f249d297abacdb459f4f3dfd9549a51726fe5f37
core-api ghcr.io/swina/whoobe-core-api@sha256:a0bd2986d728e73ae60832940d9ff18cb4c448dd44d2fa2ef8705efe4478abc6
gateway ghcr.io/swina/whoobe-gat...
Whoobe v0.2.0
Whoobe 0.2.0 - release notes
Release 0.2.0, named Conduct: the release that lets an organisation with a security officer say yes. Written from CHANGES.md on 2026-10-19;
the procedure that turns this into a release is in release-process.md (copy the text into .release/notes-v0.2.0.md after
scripts/release.sh prepare). The plan it follows is docs/plans/system/release-0.2-plan.md. Because the version starts with 0., a minor release may
change behaviour: read Behaviour changes before upgrading. Seven migrations (0060 to 0066), all additions; back up first.
What is in it
Govern: what a security officer asks
- Audit log of administrative actions. An append-only table (the database refuses UPDATE and TRUNCATE; DELETE only for the retention job) with
who, from which address, what, on which target and the fields before and after; secrets are recorded as "changed", never as a value. Users, roles,
websites, settings, publication, integrations, single sign-on, data subject requests. A route that changes something must say whether it is
audited: a test fails for a new route with no decision. Read it in Security > Audit Logs (filters, CSV),GET /audit-log, permission
audit:read. RetentionAUDIT_RETENTION_DAYS(365). Guide. - Mandatory two-factor authentication and a password policy. An administrator chooses the roles that must use a second factor; a user of such a
role who has none is led to enrolment at the next sign-in and gets no session until it is done, and cannot turn it off afterwards. The password
policy (minimum length 8 to 128, optionally three of four character classes, never the e-mail address) applies where a password is set. Off by
default: an upgrade changes nothing. - Single sign-on for staff (OpenID Connect). Authorization code with PKCE against one provider (Keycloak, Microsoft Entra, Okta, Google ...), roles
from a claim at every sign-in (the roles in the mapping follow the claim, the ones given by hand are left alone), provisioning and linking by a
verified e-mail only,localLoginfor the break-glass of administrators. A person removed at the provider cannot start a new session. Tried
against the test mock and a real Keycloak 26; Entra, Okta and Google follow the standard and were not tried. SAML and LDAP are not built (a
broker in front covers them). Guide. - Data subject requests and retention. Find, export and erase everything held about one person by one value (an e-mail): form submissions, the
files they uploaded (deleted from the library first), the mail log; staff accounts are reported, never erased by it. A dry run first, the value typed
again to erase, audited with a fingerprint and never the value. Retention per data class and for the mail log, deleted daily, off until you set
a number. Procedure. - Readiness and metrics.
GET /healthanswers 503 when the database does not answer;GET /health/liveis the cheap liveness;GET /metrics
(Prometheus: requests by matched route, queue depth of publication jobs and event deliveries, database up, audit rows lost) is off unless
METRICS_TOKENis set. Observability.
Orchestrate
- More events. Files in the library (
asset.created,.updated,.moved,.deleted,.bulk) and pages (page.published,.unpublished)
through the same subscriptions, delivery, signature and loop guard as object events. The envelopes carry ids and metadata, never contents.
Object events.
CDN mirroring
- Per website, with a default, and a second provider: Cloudflare Images next to Cloudinary (it takes
multipart/form-data, which the Gateway
cannot send, so Whoobe calls it directly with an API token stored encrypted). The mirror of the website's own provider is served when an asset has
several. Tried against a mock of Cloudflare Images only: a real account has not been tried. Publishing, step 1b.
Dashboard
- A Security entry in the sidebar groups Roles/Users/Workspaces, Two-Factor Authentication, Sign-in Requirements, SSO, Data Subject Requests, System
Logs and Audit Logs; each page is shown by permission. The sidebar opens as four closed groups with closed submenus (showing a section opens its own). - Websites > Paths: a pencil on a path linked to a page opens it in the editor. The status badge reads
/healthand the build version. - A Resend mail provider recipe (the sender goes out as the address only).
Operations and quality
- The upgrade test, the restore rehearsal, the load test and the release workflow of 0.1.1 stay; the upgrade test now runs from 0.1.1 to this tree
across the seven new migrations. - API suites of the new features run on the disposable stack with a mock OIDC provider that forges nine kinds of token, a mock Cloudflare Images,
a real Keycloak (opt-in) and real form uploads;check-docs.pyis green again (earlier branches had edited the generated API reference by hand).
Behaviour changes to check before you upgrade
| Change | What you see | What to do |
|---|---|---|
Migrations 0060 to 0066: audit_log (append-only by trigger), security_settings, oidc_settings, user_identities, cdn_mirror_settings; columns data_classes.retention_days, security_settings.mail_log_retention_days, oidc_settings.dashboard_url, object_events.subject_type; object_events.class_id becomes nullable; three indexes |
applied by core-api at the first start; 0066 copies an existing CDN mirror setting (kept until now in the first website's settings) as the default |
back up first (upgrade); going back to 0.1.1 means restoring that backup (the migrations are additions, a rollback on the migrated database is not tested) |
New permissions audit:read and data_subject:manage, given to the role admin at the first start |
Security > Audit Logs and Data Subject Requests appear for administrators | grant them to other roles if wanted |
PUT / DELETE /whoobe-one/settings/cdn-mirror need settings:manage (they needed objects:write) |
an editor can no longer change the CDN settings (reading stays) | grant settings:manage or let an administrator do it |
GET /health answers 503 when the database is down; the Docker HEALTHCHECK of core-api calls /health/live |
a load balancer probing /health takes the instance out during a database outage |
point liveness probes at /health/live, readiness probes at /health |
nginx: new core-api prefixes (audit-log, security-settings, oidc-settings, data-subject, and the ones 0.1.1 missed: mail-providers, docs-sites, object-event-*, pwa, publish/jobs, api/v1/system) |
on a host nginx, a new screen fails with "Unexpected token '<'" until the include is re-rendered | scripts/render-workspace-nginx.sh <workspace> after the upgrade; /metrics is deliberately not routed |
| Dashboard navigation: Roles/Users and Security left Settings; System Logs and Audit log are inside Security; sidebar groups and submenus start closed | people look for them in the old places | the dashboard tour and the settings guide describe the new ones |
@whoobe-cms/core depends on jose; the token verifier of the gateway moved into core |
none | none |
Public sandbox: /security-settings, /oidc-settings and /data-subject are blocked |
the demo account cannot switch on mandatory 2FA or a provider | none |
New optional variables: AUDIT_RETENTION_DAYS, METRICS_TOKEN, OIDC_ALLOW_HTTP, OIDC_START_RATE_LIMIT_PER_MINUTE, DATA_SUBJECT_RATE_LIMIT_PER_MINUTE, CDN_ALLOW_HTTP, CDN_CLOUDFLARE_API_BASE |
none unless set | configuration reference |
Defaults that do not change on upgrade: nobody is required to use two-factor authentication, single sign-on is off, no retention is set, /metrics is
off, no password rule beyond the length of 8.
Known limits
- Single sign-on: one provider, no SAML or LDAP, no single logout. A session already open outlives the person's removal at the provider (up to 7
days) until an administrator deactivates the account. Entra, Okta and Google were not tried. - Data subject requests match the whole value only, find a person typed inside a longer text in the mail log but not in objects, and do not reach
backups (a restore brings the person back: erase again). There is no register of requests. - Audit log: successful actions only; no failed attempts, no gateway administration, no tamper evidence beyond the database rule.
- Events: creating, editing and deleting a page, and the end of a publish job, are not events yet.
- CDN: a real Cloudflare Images account was not tried; deleting an asset does not delete its mirror.
- In-memory state in core-api (the sign-in in progress, the enrolment tokens, the login throttle) is per process: one core-api replica.
Tested
The API and browser suites on the disposable stack, the unit and integration suites of core, check-docs.py, the upgrade test from v0.1.1, the
static scans, the isolation and TLS checks and the ZAP scan on the release commit: see security/status-summary.md.
Images (by digest, signed with cosign, keyless)
asset-processor ghcr.io/swina/whoobe-asset-processor@sha256:707e6ca70b32abd5bf1704d064563985cbf4f844db38b6821e4b5eb7d4de797b
core-api ghcr.io/swina/whoobe-core-api@sha256:db04ed0e2718ad4bfcb0c378bfe838600a35112d0c4776483c15b4e11418dc3a
gateway ghcr.io/swina/whoobe-gateway@sha256:825be06b36ac7ac012a8e1e1e8e663dda8...
Whoobe v0.1.1
Whoobe 0.1.1 - release notes
The first release after 0.1.0 (2026-10-14): the work done on master since then, 80 commits. One version number for the whole
product. Written from CHANGES.md on 2026-10-18; the procedure that turns this into a release is in
release-process.md (copy the text into .release/notes-v0.1.1.md after scripts/release.sh prepare).
Because the version starts with 0., a patch release is meant to contain fixes; this one also carries one new feature (object
events) that touches the database and the gateway, so read "Behaviour changes" before upgrading.
What is in it
Object events (new)
- A subscription tells an automation engine or any HTTP service when a Data Object is created, changed, deleted, published or
unpublished: outbox, delivery with retries, signature (HMAC), delivery log, test event. Dashboard section Data & Communication >
Object events (permissionobject_events:manage), the Connect a flow wizard, the API (eight routes) and an administrators'
guide:communication/object-events.md. - End of a flow and loop guard: a write through the client API with the header
X-Whoobe-Flow: stopis recorded but not delivered
(statusstopped); a client that keeps writing the same record is cut by a circuit breaker (people are never cut). The delivery log
is a tab with filters, selection and delete. - Whoobe does not ship or favour an automation engine: the guides cover Node-RED, Activepieces and n8n as the operator's choice
(communication/node-red.md); the example flows are inexamples/, not part of the distribution.
Dashboard
- Objects list: compact rows, a spreadsheet-like table view (resizable columns, cells editable in place), Copy id in the row menu,
Rows per page fixed from page 2 on; the tab of an open record is named by a field marked tab title. A click on a row no longer opens
the record (right click > Edit). Guide:content/objects-list.md. - Object edit form: field classes override the defaults (Class > View Layout > Input styles), a Refresh button, markdown and rich-text
editors follow a height set on the field. - The API clients list shows write scopes as write.
Generated sites and SDK
- A
markdownfield bound to an element is converted to HTML, and awysiwygfield kept as markup, both sanitised, like in the editor
preview (@whoobe-cms/renderer; before, a generated site showed the source). Regenerate and republish a site to get it.
Operations and quality
- Upgrade test:
scripts/upgrade/upgrade-test.shinstalls the previous release, writes data, upgrades to the working tree and checks the
migrations, the tables, the data and a login; it also runs as a GitHub workflow on a release tag. Passed 0.1.0 -> this tree. - Restore rehearsal and bucket backup:
scripts/backup-workspace-bucket.sh(copies the S3 bucket, or restores it) and
scripts/restore/rehearse.sh(backs up a throwaway workspace, deletes it, restores it and checks the data). - Load test:
scripts/load/run.sh(k6): about 690 public pages, 330 public queries and 120 API-client reads per second under the smallest
container limits, on a development machine; seeoperations/capacity-and-scaling.mdsection 2.1. - Release workflow (
.github/workflows/release.yml): signed multi-arch images on ghcr.io and a draft GitHub Release from a tag. Written,
first run is this release. - Documentation corrected where it was no longer true (machine credentials exist as API clients; a form is not the only automation trigger; the
editor preview and the published output are two implementations); the documentation is published at docs.whoobe.dev.
Security
- The
admin-dashboardandwhoobe-one-spikeimages upgradelibexpat(HIGH CVE-2026-77214, fixed in 2.9.0-r0); all five product images pass the
image scan with 0 fixable HIGH/CRITICAL. tailwind-merge3.7.0 andmarked^14.1.4 are new dependencies (MIT);THIRD_PARTY_NOTICES.mdis current.stack-tests, static scans, isolation, TLS and the ZAP DAST scan ran on the release commit: see
security/status-summary.md.
Behaviour changes to check before you upgrade
| Change | What you see | What to do |
|---|---|---|
Migration 0059 adds three tables (object_events, object_event_subscriptions, object_event_deliveries); nothing existing changes |
applied by core-api at the first start | back up first (upgrade); the previous version still runs on the new schema |
The role admin gets the permission object_events:manage at the first start; other roles do not |
the Object events section appears for administrators only | grant it to other roles if wanted |
| The gateway runs a delivery worker for object events, in the same env file as core-api | inert until a subscription exists | OBJECT_EVENTS_ENABLED=0 stops events and worker for the instance |
New optional variables: OBJECT_EVENTS_ENABLED, OBJECT_EVENTS_CONCURRENCY, OBJECT_EVENTS_LOOP_MAX, OBJECT_EVENTS_LOOP_SUB_MAX, OBJECT_EVENTS_LOOP_WINDOW_SECONDS, OBJECT_EVENTS_SECRET_<NAME> |
none unless set | configuration reference, .env.example |
| Markdown / rich-text fields bound to an element are converted in generated sites | pages that showed raw markdown now show formatted text | regenerate and republish the sites |
| Objects list: a click on a row no longer opens the record | editors who relied on it | right click > Edit |
Images of the two SPAs are rebuilt with a newer libexpat |
none | docker compose up -d --build |
| The product is called "Whoobe" (it was "Whoobe CMS"): login page, browser title, the issuer shown by an authenticator app for a new 2FA enrolment (existing enrolments keep working), the sender name suggested for mail, the CLI help | text only; package names (@whoobe-cms/*), image names and the repository are unchanged |
nothing |
Known limits
- Everything listed as a limit in the 0.1.0 notes and in chapter 4 of the overview still applies,
except that upgrading from 0.1.0 is now tested by script (database step only: image rebuild on a real host, nginx and the manual checks of step 7
are written, not rehearsed) and a restore of a throwaway workspace is rehearsed. - No audit log of administrative actions, no OIDC login for staff, no mandatory 2FA: planned for 0.2 (plan).
- The load figures come from one development machine and four endpoints; uploads, AI, publishing and mixed traffic were not measured.
release.ymlhas not run before this release: if it fails, the images are built by hand as in 0.1.0 (release process, steps 5 to 7).- The manual security checklist was not repeated for the features added after 2026-10-09 (object events included).
Images (by digest, signed with cosign, keyless)
asset-processor ghcr.io/swina/whoobe-asset-processor@sha256:017712e2051817ec21628346d5c4fb840d20f8c85a1385b9a6de6b19bc6ad50e
core-api ghcr.io/swina/whoobe-core-api@sha256:9d73650d4ec81938d916f3ea0596def6e3e0892ec2b9ea89da6fbf0a91db773d
gateway ghcr.io/swina/whoobe-gateway@sha256:ce2e1fd2033e123cf8c091b99f819305df8f881b1949c8ba20e8e93474decdf9
publisher ghcr.io/swina/whoobe-publisher@sha256:0b069fbd33a6a35a389071ed69e75c367f1e6a874f011136e8acef891d8c4273
Whoobe v0.1.0
Whoobe 0.1.0 - release notes
First release. One version number for the whole product (0.1.0: while the version starts with 0., a minor release
may change behaviour). Written from CHANGES.md and checked against the code on 2026-10-14; the procedure that
turns this into a release is in release-process.md (copy the text into
.release/notes-v0.1.0.md after scripts/release.sh prepare).
What is in it
Authoring
-
Page editor (Whoobe One): blocks, snippets and kits, themes, templates with header/aside/footer slots, dynamic website
paths and menus, App Mode (phone frame with responsive classes), thumbnails from the Mobile/Tablet preview, version
history, AI page generation and translation, Figma import. -
Rich text with TipTap: colour, highlight, alignment, links, lists. The colours now survive selecting the paragraph and
using another control. -
Audio element (player or waveform, from an asset or a URL, optional title) and Alpine presets (named sets of
directives applied from the editor). Guides:content/audio-element.md,
content/alpine-presets.md. -
Customize > Border sliders can give 1px (
border), and their first step removes the class instead of setting a 0. -
Website export and import as a
.zipwith everything the site refers to - snippets, module pages, data classes and
objects, asset files - and the references rewritten on import, so the copy is a twin. Dry run, rollback on failure.
Guide:content/website-bundle.md. -
Documentation sites: a website whose source is a folder of Markdown assets, with an index (automatic or edited in the
dashboard), navigation and client-side search, rendered by the editor app or generated as a static site. Guide:
content/documentation-sites.md. -
Documentation sites draw
mermaidcode blocks as diagrams (editor, dashboard preview and generated site; the library is copied into the site, not loaded from a CDN). Guide:content/documentation-sites.md. -
Table element bound to data: a table can show a Data Class query or a gateway list, with chosen columns and formats, and paginate it
(arrows or a More button); the pagination also works for gateway-bound repeaters. Also drawn by the generated Next.js, Nuxt and Svelte sites and the SDK (first page in the HTML; at most 1,000 rows). Guide:
content/table-element.md. -
Download element tied to an asset or a URL (link with
download, or opened in a new tab); guide
content/download-element.md. -
Snippet lock: a locked snippet is not saved automatically.
-
Marketing automation: a form sends its submission to an automation engine (Activepieces, n8n or any webhook) through the Gateway, the
engine can answer a one-wordoutcome, and the page reacts with AlpineJS directives to the newwhoobe-form-resultevent. The forms
that start an automation are listed in Data & Communication > Automations (and in the editor). Whoobe does not run flows
itself. Guide:communication/marketing-automation.md. -
File upload in forms: a File Upload field, per-website limits (Settings > Form uploads), content checks, private
storage. Guide:communication/form-uploads.md. -
Editor tools: a Data panel (the binding of the selected element, the fields of the nearest bound list as chips to click or drag, nested and linked bindings) and an AlpineJS panel (presets, with a built-in Form result preset that sets
done,okandoutcomefromwhoobe-form-result, and the element's directives); a status bar with the selected element (type > <tag> > #id) and its Custom CSS (classes), a twin of Custom Classes in Advanced. -
Forms: the message a visitor sees names the field that was refused (a field named differently from the class field is the usual cause); the form can be hidden once it has been sent (On success > Hide the form once it has been sent; editor preview and React, Vue, Svelte, Next.js and Nuxt sites). Guide:
communication/marketing-automation.md. -
A consistent look for buttons, fields and windows (checked for contrast), the editor's tools as an icon bar, a menu
next to the collapsed navigation.
Data and assets
- Creating classes faster: + From template (Contact, Category, Product), + From file (JSON, CSV, Excel: types are guessed, HTML becomes Rich text) and Settings > Classes > Integration import (from a GET route of an Integration); each can create the schema only or schema and data. Bulk import of objects (
POST /data-objects/bulk: up to 500 rows per call, drafts, rows that break a rule come back inrejected). - Unique fields: a class field can be marked unique; a second object with the same value is refused (409 in the API, " is already in use" in forms).
- Empty data: removes every object of a class after typing the class name (Data Objects list and Settings > Classes); logged.
- Pimcore migration: class files with text ids and the Fields Summary of real installations are read correctly; before importing, Choose fields and import... lets the user pick and map fields; objects import in the background with a progress bar and a downloadable list of failed records.
- Data classes, components and objects (Pimcore-style), with workflow and publication; DAM with folders, thumbnails,
visibility rules, optional CDN mirror; Garage as the default S3 store for local/LAN, an external S3 in production.
Integration and publishing
- Outgoing mail per website: SMTP or an HTTP mail provider described by a JSON file (OneSignal built in, more are a file each), secrets encrypted at rest.
communication/mail-providers.md. - API Gateway with endpoints, routes, OpenAPI import, site keys, forms; vendor presets for the "Add Endpoint" picker
(Brevo, Cloudinary, HubSpot, Mailchimp, Shopify, Stripe, a fake-products demo). A preset can now be added to a running
gateway: drop the.jsoninapps/gateway/presets/or inGATEWAY_PRESETS_PATH(a mounted volume) and reload the page. - Publishing a website as a Next.js or Nuxt project, a Capacitor mobile app, a headless SDK (React, Vue, Svelte).
Operations
- Workspaces (one isolated stack per customer) with
provision-workspace.sh, profiles and removal; TLS template. - Public sandbox: a workspace with a profile (
deploy/profiles/sandbox.env) that turns AI, mail, imports, CDN mirror
and outbound HTTP off, caps assets, and is rebuilt every three hours.operations/sandbox.md.
Security - 59 findings recorded, all High fixed; none of the review's findings is open (see below); API (1171 tests), unit (269), isolation (14), browser (30) and static suites,
OWASP ZAP and image/TLS scans run in CI on every release commit.
security/status-summary.md.
Behaviour changes to check before you install over a development setup
| Change | What you see | What to do |
|---|---|---|
Compose refuses to start without credentials; Postgres and the S3 store listen on 127.0.0.1 only |
docker compose names the missing variable; a database client on another machine cannot connect |
set POSTGRES_PASSWORD, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY; WS_DATA_BIND_ADDRESS=0.0.0.0 to open the ports |
| Garage replaces MinIO as the default store | a fresh stack has no MinIO; the MinIO overlay (its compose file and Docker build folder) is removed | copy existing data to the new store (see operations/storage-backends.md); an existing MinIO can stay as an external S3 endpoint |
Asset visibility is enforced (DAM_ENFORCE_ASSET_VISIBILITY, on by default); /public/** is public |
assets recorded as private answer 404 without a session | move what public pages use under /public, or set the flag to false |
| Credentialed CORS only for an allowlist | "Failed to fetch" from an origin that is not WHOOBE_HOSTNAME |
CORS_ALLOWED_HOSTS, CORS_ALLOWED_ORIGINS |
Publishing needs objects:publish (pages, data objects, and now website import) |
403 when an editor changes published; an imported site arrives as drafts |
grant the permission; ENFORCE_PUBLISH_PERMISSION=false restores the old behaviour |
Rich text is filtered when rendered (no iframes/embeds, <style>, id, data-*; only colour, background and alignment as inline style) |
embedded HTML disappears from pages | extend the allow-list in packages/sdk/schema/src/sanitize.ts |
Image endpoints need a session or DAM_SERVICE_TOKEN; images over 100 MP are refused |
401 from scripts, 413 on huge uploads | DAM_SERVICE_TOKEN, ASSET_MAX_INPUT_PIXELS |
| Outbound requests (gateway, OpenAPI import, AI endpoints) refuse loopback and metadata addresses | an endpoint on a local address is refused | OUTBOUND_ALLOW_HOSTS, OUTBOUND_ALLOW_LOOPBACK; OUTBOUND_ALLOW_PRIVATE=false on internet-facing installs |
| Public routes are rate limited per address | 429 for a server-side rendering host | PUBLIC_*_RATE_LIMIT_PER_MINUTE; TRUST_PROXY=1 behind nginx |
| Gateway forms forward only the form's own fields | an extra field is dropped | save the page again and republish; GATEWAY_REQUIRE_FIELD_ALLOWLIST=true refuses old bindings |
| Password reset revokes sessions; reset tokens are stored hashed; MFA secrets are encrypted at rest | tokens issued earlier stop working | set TOTP_ENCRYPTION_KEY and keep it (losing it forces MFA re-enrolment) |
| 5xx answers are generic in production | no SQL or stack text in the... |