Whoobe Docs

Supply chain, dependencies and build integrity#

What goes into a Whoobe release, how it is checked, what a consumer can verify, and what is not done yet. For procurement and security teams that need an SBOM, licence information and a statement about build provenance.

1. What a release is built from#

Input Control
Source one git repository, tagged vMAJOR.MINOR.PATCH on master (annotated tag, never moved); one product version for every package (release process)
JavaScript dependencies pnpm workspaces; pnpm-lock.yaml is committed and every image build runs pnpm install --frozen-lockfile, so a build cannot pick up a different version than the lockfile names; 669 components in the source SBOM (section 3)
Base images node:22-bookworm-slim and nginxinc/nginx-unprivileged:alpine pinned by digest in the Dockerfile; postgres:16-alpine pinned by digest in docker-compose.yml; Garage built from docker/garage (v2.4.1)
Build tools in the image pnpm/corepack/npm exist only in the build stage; the runtime images contain none of them (they carried most of the image CVEs)
Generated sites packages/publish vendors the SDK packages as tarballs into the site's own Dockerfile, so a published site does not fetch @whoobe-cms/* from a registry

2. Checks that run on every change#

All in .github/workflows/security.yml (jobs static, stack-tests, tls, isolation, plus a monthly ZAP DAST). The thresholds below are summarised; the exact commands and pass criteria of each tool are in security-test-suite.md:

Check Tool Gate
Known vulnerabilities in dependencies pnpm audit fails on any HIGH/CRITICAL not in scripts/security/static/baseline.json; each accepted entry has a reason and a review date (expired entries fail the run). Accepted today: xlsx (2 high, write-only use, no npm fix) and braces, until 2027-01-31
Secrets in the repository and history gitleaks fails on a finding outside the baseline
Code patterns (injection, unsafe eval, SQL string building ...) semgrep fails on findings outside the baseline
Shell scripts shellcheck fails
Dockerfiles / compose misconfiguration hadolint, trivy config fails on HIGH/CRITICAL
Container images trivy image on the images the stack built fails on HIGH/CRITICAL with a fix available; the PostgreSQL image is advisory (upstream)
TLS configuration testssl.sh against the rendered nginx template fails below the policy
Dynamic scan OWASP ZAP (active API scan, passive SPA baseline) monthly or by hand; required, on the release commit, by the release gate
Dependency updates Dependabot, monthly, one grouped PR per ecosystem; majors are planned by hand the static scan runs on each PR

Heavy suites are limited to master pushes, nightly and manual runs to save CI minutes; the release procedure requires a green run on the release commit, including DAST (scripts/release.sh check).

Known residual advisories: PostgreSQL's gosu in the upstream image (F-050; MinIO was removed from the stack), the two accepted npm advisories above, and the base-image lag handled by explicit apt-get --only-upgrade / apk upgrade lines in the Dockerfile that are removed when the pinned digest catches up.

3. Software bill of materials#

scripts/release/sbom.sh writes CycloneDX 1.7 JSON to .release/sbom/ using the same scanner as CI (aquasec/trivy):

  • whoobe-cms-<version>-source.cdx.json: every component of the lockfile across all workspaces (669 at the time of writing);
  • with --images, one SBOM per service image of the running disposable stack (OS packages included).

Attach the files to the GitHub Release. They are not generated automatically by the release workflow yet.

4. Licences#

The product is MIT (LICENSE.md); every product package.json declares "license": "MIT" and the release gate checks it.

Third-party licences. THIRD_PARTY_NOTICES.md (root) lists the production dependencies of every workspace with version, licence and copyright notice, and the licence texts. It is generated by scripts/release/third-party-notices.py from the installed packages (pnpm licenses) and checked by the release gate: the check fails when the file is out of date, or when a dependency has a licence that is neither on the permissive allow-list (MIT, ISC, Apache-2.0, BSD, and similar) nor reviewed with a written reason in the script. Today (617 packages) one is reviewed: the libvips binaries of sharp (LGPL-3.0-or-later, a separate library, not modified). The editor's animation library, GSAP (a proprietary licence with a restriction on visual animation builders), was removed on 2026-10-13: element animations now run on the browser's Web Animations API (plan), and the gate would fail if it came back. Packages whose metadata lacks a licence (two) are set to MIT by hand after reading their licence files. Operating-system packages in the images are not covered (the image SBOMs carry them). A legal review of the inventory has not been done.

Fonts loaded from Google Fonts by the editor, dashboard and generated sites are third-party content under their own licences (and a privacy consideration, see data protection).

5. Provenance and signing - what a consumer can and cannot verify#

Claim Today
The tag is the reviewed source git tag; tags and commits are not GPG/Sigstore-signed
The images match the tag images are built and pushed by hand to ghcr.io; the release workflow (release.yml) that builds them from the tag is not written yet
The images are signed no (no cosign signature, no SLSA provenance attestation)
Images pinned by digest in the release notes planned (the release process records digests); not automated
Reproducible builds the inputs are pinned (lockfile, digests) but a bit-for-bit reproduction has not been checked

Until release.yml exists, a consumer who needs assurance should build from the tag themselves (docker compose up -d --build uses only the pinned inputs) and compare with the published digests.

6. Incoming code#

  • Third-party code inside the product: the gateway plugins (GATEWAY_PLUGINS_PATH) are arbitrary code loaded at start with the gateway's privileges; treat the folder like deployed code. Vendor presets and mail provider definitions are JSON, not code.
  • Generated sites run with the credentials their operator gives them; the CMS never ships its own secrets into them (the site key is the site's own and belongs on its server).
  • Contributions: pull requests run the static scan; there is no signed-off-by or CLA requirement recorded in the repository.

7. Gaps (candidates for the next releases)#

Automated release workflow with image build from the tag, digests and provenance (SLSA) and cosign signatures; SBOM attached automatically; a licence report with an allow-list policy; signed commits/tags; a dependency-review check on pull requests; scanning of the generated sites' dependencies.

Last updated