Files
react-website/docs/DEPLOYMENT.md
Mehboob Khan 28d0addfc0
Some checks failed
CI / build (push) Has been cancelled
Address High/Medium findings from the web app security assessment
Fixes the two High-severity findings from Isaac Hague's 19/08/2026 review
(F-01, F-02) plus F-03 through F-09:

- F-01: content writes are now sanitised server-side (sanitize-html) as the
  real security boundary — the browser-side sanitiser is UX, not enforcement,
  and a direct API write bypassed it entirely. Also closes the javascript:
  href gap in sanitiseInline().
- F-02: refusing the factory admin password no longer depends on NODE_ENV;
  it's the unconditional default now, with an explicit ALLOW_DEV_PASSWORD=1
  opt-in for local dev.
- F-03: adds CSP (report-only — the legacy inline onclick="" handlers would
  break under enforcement) and HSTS, in both server/index.mjs and vercel.json.
- F-04/F-05: rate-limits /api/leads and rotates leads.jsonl past 5MB; CSV
  export neutralises leading =+-@ so exports can't carry live formulas.
- F-06: sessions drop from 12h to 4h and are tied to a per-boot random epoch,
  so a restart now actually revokes outstanding tokens.
- F-07/F-08/F-09: generic messages on 5xx, fixed-length password comparison
  (no more length disclosure via the short-circuit), periodic throttle-map
  cleanup.

F-10 (dependency advisories): applied the two non-breaking patches (nanoid,
postcss); the vite/react-router-dom major bumps are left for a separate pass,
per the report's own recommendation. F-11 (PDF Content-Disposition) and a
CAPTCHA/honeypot on the enquiry form are deliberately left open — both are
product/UX calls, not pure security fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 18:08:24 +05:00

5.1 KiB

Deployment and operations

The app ships as a static build plus one small Node process. The Node process is what makes /admin able to publish, so host it wherever the CMS is needed.

npm ci
npm run build                 # → dist/
ADMIN_PASSWORD='…' npm start  # serves dist/ + the API on port 8787

Environment

Variable Default Purpose
ADMIN_PASSWORD Password for /admin. Required — the server refuses to start without it (or with the factory password blackdice).
ALLOW_DEV_PASSWORD unset Local development only: accept the factory password blackdice instead of requiring ADMIN_PASSWORD. Never set in a real deployment.
ADMIN_SECRET derived from the password Signs session tokens. Set it to invalidate all sessions independently of the password (a server restart also invalidates every session, regardless of this setting).
PORT 8787 Listen port.
CONTENT_DIR ./content Published content, uploads, snapshots, enquiries. Put this on persistent storage.
DIST_DIR ./dist The built site.
SITE_URL https://www.blackdice.ai Canonical origin used in sitemap.xml.

Sessions last 4 hours. Failed logins are throttled at 10 per IP per 15 minutes; enquiry submissions at 8 per IP per 10 minutes. Uploads are capped at 32MB and limited to images, MP4/WebM and PDF. leads.jsonl rolls over to a .bak file once it passes 5MB rather than growing without bound.

What lives where

content/
  site-content.json        the published site: copy, articles, demos, settings
  versions/                one snapshot per publish (latest 60 kept)
  uploads/                 images and clips uploaded through Studio
  leads.jsonl              enquiry form submissions
dist/                      the built site (safe to delete and rebuild)
public/content/posts/      article heroes imported from the old studio (in git)

Back up content/. It is the only thing that cannot be rebuilt from the repo. A nightly copy of the directory is enough; site-content.json is a single small JSON file. Editors can also take their own backup from Studio → History → Download this draft.

Behind IIS or nginx

Serve the app from Node and reverse-proxy to it — that keeps /api, uploads, the SPA fallback and the live sitemap working with no extra configuration.

nginx:

location / {
    proxy_pass http://127.0.0.1:8787;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    client_max_body_size 40M;   # Studio uploads
}

IIS: install URL Rewrite + ARR and proxy the site to http://127.0.0.1:8787, or run it under iisnode. Raise maxAllowedContentLength to ~40MB for uploads.

Keep the process alive with a Windows service (nssm), pm2, or systemd:

# /etc/systemd/system/blackdice-site.service
[Service]
WorkingDirectory=/srv/blackdice
Environment=ADMIN_PASSWORD=…
Environment=CONTENT_DIR=/srv/blackdice-content
ExecStart=/usr/bin/node server/index.mjs
Restart=always

Docker:

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
ENV PORT=8787 CONTENT_DIR=/data
VOLUME /data
EXPOSE 8787
CMD ["node", "server/index.mjs"]

Static hosting without the CMS

dist/ can be served by any static host. Deep links need an SPA fallback, which is already configured for Vercel (vercel.json), Netlify (public/_redirects) and IIS (public/web.config). Apache:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]

In that setup the site shows the content compiled into the build and /admin cannot publish. To make a change, run Studio locally (npm run dev), publish, then commit the resulting content/site-content.json and any content/uploads/ files and redeploy — the build copies neither automatically, so they must be placed alongside dist/ (dist/content/site-content.json, dist/content/uploads/…).

Release checklist

  1. npm ci && npm run build — the build fails on type errors, so it gates itself. build also regenerates public/sitemap.xml/robots.txt from whatever is in content/site-content.json (falling back to factory content) before vite build copies public/ into dist/, so the static sitemap can't go stale relative to the last build. Set SITE_URL if it isn't https://www.blackdice.ai.
  2. Deploy dist/, server/, package.json, and keep content/ in place.
  3. Check: /, /mobile-sdk, /news, one /blog/<slug> deep link, /sitemap.xml, /admin login.
  4. In Search Console, submit https://www.blackdice.ai/sitemap.xml and request indexing for the product pages — they now have their own URLs to index.

After the first deploy

  • Set ADMIN_PASSWORD and share it only with the people who edit the site.
  • Publish once from Studio so content/site-content.json exists and snapshots begin.
  • Approve and publish the GSMA Open Gateway release (Studio → Articles).
  • Upload the Retina and Angel walkthrough clips (Studio → Demos) to finish the Halo CPE demos.