Demo Mode Tour Security
Public demo architecture for Arcturus-Prime with middleware data interceptor, 11 synthetic generators, cookie sessions, and three-layer safety model
Demo Mode Tour Security
This document describes the public demo safety model for sharing Arcturus-Prime with employers, clients, contributors, and homelab/AI/devops enthusiasts.
The goal: allow a real product walkthrough where visitors see actual admin pages with the real layout, sidebar, and modules — but with all data replaced by synthetic values and all write operations blocked. No secrets, no real infrastructure metadata, no destructive capabilities.
Scope
The demo system covers:
- Public demo hub at
/demo - Auto-generated tour at
/demo/tour - Full admin mirror at
/demo/admin(real pages, synthetic data) - Live sandbox entry at
/demo/sandbox - Live sandbox workbench at
/demo/workbench
The demo mirror works without DEMO_MODE=true — it detects demo_mirror requests independently, so the production admin area stays fully functional while visitors take the tour.
Architecture Overview
The demo runs on a three-layer safety model:
- Middleware data interceptor — returns synthetic data from registered generators before real API handlers execute
- Policy restriction layer — blocks unregistered sensitive endpoints and all mutations
- DOM redaction observer — catches any residual real data that leaks through to the rendered page
Data flow
Visitor → /demo/admin/servers
→ iframe loads /admin/servers?demo_mirror=1&demo_embed=1
→ middleware: hasDemoSignal? → isDemoMirrorRequest()? → validate demo session
→ page JS fetches /api/admin/servers (with demo_mirror referer)
→ middleware interceptor: generator found → return synthetic Response
→ real API handler never executes
→ page renders with synthetic server data
→ MutationObserver redacts any residual real IPs/emails in DOM
Layer 1: Middleware Data Interceptor
Core interceptor
File: src/lib/demo-api.ts
The interceptor is called from src/middleware.ts before the isDemoMode() block — it works independently of DEMO_MODE. A fast-path check (hasDemoSignal) avoids the dynamic import on normal requests:
hasDemoSignal? (query param / header / referer) → import demo-api
→ isDemoMirrorRequest()? → validate demo session
→ admin page gate: valid session → allow through; no session → 401
→ API interceptor: handleDemoApiRequest(pathname, request)
→ block mutations (POST/PUT/PATCH/DELETE) → 403 "view-only"
→ find matching generator in registry → return synthetic Response
→ no generator found → return null → fall through to restriction layer
Key exports:
isDemoMirrorRequest(request)— detects demo mirror via query param, header, or refererhandleDemoApiRequest(pathname, request)— orchestrates validation + generationdemoHash(input)— FNV-1a hash for deterministic pseudo-random valuesdemoJsonResponse(data, status)— standard JSON response withX-Demo-Mode: trueDemoDataGeneratortype —(ctx: DemoGeneratorContext) => Response | Promise<Response>
Generator registry
File: src/lib/demo-data/index.ts
Maps API paths to generator functions. Supports exact match and prefix match (path/*):
| API Path | Generator | Synthetic Data |
|---|---|---|
/api/admin/servers | servers.ts | 5 servers with Galactic Identity names |
/api/admin/health-check | health.ts | 12 service checks (1 degraded for realism) |
/api/admin/credentials | credentials.ts | 13 env var categories, deterministic configured/missing states |
/api/admin/api-keys | api-keys.ts | 16 masked API keys (~80% configured) |
/api/admin/email | email.ts | Mailbox stats (47 inbox, 12 unread) |
/api/admin/cloudflare-status | cloudflare.ts | Zone analytics, workers, KV, pages, DNS, tunnels |
/api/admin/modules | modules.ts | 14 modules with metadata and enabled states |
/api/admin/services | services.ts | 10 service definitions across categories |
/api/admin/content-index | content.ts | 14 blog posts and journal entries |
/api/admin/security | security.ts | Scan results across 4 security categories |
/api/admin/system-test | system-test.ts | 4 test suites, 26 individual tests |
All generators live in src/lib/demo-data/generators/.
Adding a new generator
- Create
src/lib/demo-data/generators/your-endpoint.ts - Export a function matching
DemoDataGeneratortype - Return a
Responsematching the real API’s response shape - Register in
src/lib/demo-data/index.tswith the API path as key - Use
path/*suffix for prefix-matched routes
Design principles
- Response shapes must match real APIs exactly so admin pages render correctly
- Generators use Galactic Identity names (Izar-Host, Tarn-Host, Meridian-Host, etc.)
- IPs use sanitized ranges (
10.42.0.x,192.168.20.x,100.64.0.x) - URLs point to
demo.Arcturus-Prime.test demoHash()produces deterministic values so demos are stable run-to-run- One service is always degraded (Pentest Sentinel with HTTP 502) for visual realism
Layer 2: Policy Restriction Layer
Demo policy
File: src/lib/demo-mode.ts
- Reads
DEMO_MODEfrom runtime env - Defines admin page restrictions (which pages return 404)
- Defines admin API restrictions (which endpoints return 403)
- Supports outcomes:
restricted_endpoint,read_only
Middleware enforcement
File: src/middleware.ts
When DEMO_MODE=true:
- Admin pages: restricted routes return
404unlessdemo_mirror=1with valid session - API interceptor: runs first, returns synthetic data for registered paths
- API restriction: unregistered paths fall through to
getDemoApiRestriction() - Mutations: all POST/PUT/PATCH/DELETE blocked with 403 by the interceptor
Layer 3: DOM Redaction Observer
File: src/layouts/CosmicLayout.astro (lines 1013-1079)
Active when demo_embed=1 is present:
- MutationObserver runs on initial render and all DOM mutations
- Replaces: private IPs, email addresses, raw URLs, token-like strings, phone numbers
- Rewrites text nodes and input/textarea values
- Defense-in-depth layer — should not be the sole control for any endpoint
Demo Admin Mirror
Shell page
File: src/pages/demo/admin/[...slug].astro
This is a mirror-first page:
- With demo session: renders
DemoModeBanner+ route chip nav + full-height iframe loading/admin/{slug}?demo_mirror=1&demo_embed=1 - Without session: centered card prompting to start a demo session at
/demo/sandbox
The iframe loads the real admin page, which:
- Gets layout chrome stripped by CosmicLayout (no header/footer/sidebar/background)
- Gets synthetic data from the middleware interceptor
- Gets residual text redacted by the MutationObserver
DemoModeBanner
File: src/components/DemoModeBanner.astro
Sticky amber banner displaying:
- “DEMO MODE” label
- “Data is synthetic - Actions are view-only”
- Current route
- “Exit Demo” link back to
/demo/sandbox
Rendered in two places:
- Above the iframe in the demo shell page (
[...slug].astro) - Inside the iframe in CosmicLayout (when
demo_embed=1)
Mirror request detection
Uses three detection methods (checked in order):
?demo_mirror=1query parameterx-demo-mirror: 1request headerRefererURL containingdemo_mirror=1
The referer check is important — when admin pages make fetch() calls to APIs, the browser includes the referer from the iframe’s URL which contains demo_mirror=1.
Cookie-based Demo Auth
Files:
src/lib/demo-auth.tssrc/pages/api/demo/session.tssrc/pages/demo/sandbox.astro
Flow:
- Visitor submits access code on
/demo/sandbox POST /api/demo/sessionwithaction: create- API sets HttpOnly cookie
__argobox_demo - Browser redirects to
/demo/admin
Cookie attributes: HttpOnly, SameSite=Lax, Path=/, Secure (non-dev), Max-Age from session expiry.
Sessions are rate-limited and time-bounded (default: 30 min, max 5 concurrent).
Auto-generated Tour Manifest
File: src/pages/api/demo/tour.ts
Generated dynamically from admin nav config + module registry + KV-stored module enabled states + demo restriction policy.
Each route is marked available or restricted_in_demo. The /demo/tour UI uses this for a safe, always-current platform tour. All modules include an Admin Mirror link into /demo/admin/*.
Security Checks
Tour leak scanner
File: scripts/check-demo-tour-leaks.mjs
npm run check:demo-tour
Scans nav field lines in src/config/admin-nav.ts and src/config/modules/*.ts for real IPs, hostnames, usernames, and paths. Exits non-zero on leaks.
Environment Variables
Required for demo deployment:
DEMO_MODE=trueDEMO_ACCESS_CODE=<shared code>
Recommended:
DEMO_MAX_SESSIONS=5DEMO_SESSION_MINS=30DEMO_RATE_LIMIT=50
Test Checklist
npm run check:demo-tour
Manual verification in a demo environment:
- Open
/demoand/demo/tour— verify route statuses render - Start session from
/demo/sandboxwith access code - Navigate to
/demo/admin— real dashboard renders with synthetic data - Click sidebar links (Servers, Credentials, Health, Cloudflare) — each loads real UI with fake data
- Open Network tab — all
/api/admin/*requests return synthetic payloads withX-Demo-Mode: true - Try any mutation (Save, Delete, Toggle) — should see “view-only” 403, no real change
- Verify no real IPs (
10.42.0.x,192.168.20.x) appear in rendered UI - Open
/demo/admin/credentials— values are synthetic, unlock is disabled - Open a mirrored page in new tab — verify
?demo_mirror=1&demo_embed=1present in iframe src - Confirm admin pages return 404 when accessed without mirror flags/session
- Confirm admin APIs return 403 with
X-Demo-Mode: truefor unregistered paths - Exit demo — confirm session ends
Deployment Guidance
Use a dedicated demo deployment (recommended subdomain) with isolated secrets and DEMO_MODE=true.
Do not enable DEMO_MODE on your main production admin deployment. When DEMO_MODE=true is set in the production CF Pages environment, ALL /admin/* routes return 404 — the entire admin area becomes inaccessible.
Recurring outage risk
The credentials vault (credentials.md) lists DEMO_MODE=true in both its Production and Preview env var sections. Any automated process or AI session that syncs environment variables from the vault to CF Pages will silently enable demo mode on production, locking out the admin area. This has caused at least 3 outages (2026-02-28, 2026-03-01).
How to recognize it: Every /admin/* route returns plain text “Not Found” — no styled page, no layout, no navigation. This is the middleware at src/middleware.ts returning new Response('Not Found', { status: 404 }). A real Astro 404 would render a styled page with site navigation. Plain text “Not Found” = demo mode is on.
Prevention:
DEMO_MODEmust befalseor absent in the CF Pages production environment- The credentials vault should mark
DEMO_MODEas demo-only with a clear warning - After any bulk env var sync to CF Pages, verify
DEMO_MODEis not set totruein production - See also: Troubleshooting → Admin Area Returns Plain “Not Found”
Known Limitations
- Demo auth sessions are in-memory (ephemeral per isolate)
- Admin pages that don’t use registered API paths will show empty states (add generators as needed)
- MutationObserver redaction is pattern-based — defense-in-depth, not sole control
- Generators return static synthetic data (no time-series history or pagination)
File Reference
| File | Purpose |
|---|---|
src/lib/demo-api.ts | Interceptor core, mirror detection, shared helpers |
src/lib/demo-data/index.ts | Generator registry (11 paths) |
src/lib/demo-data/generators/*.ts | 11 synthetic data generators |
src/lib/demo-mode.ts | Policy layer (page/API restrictions) |
src/lib/demo-auth.ts | Session management, cookie auth |
src/middleware.ts | Enforcement (interceptor + restrictions) |
src/pages/demo/admin/[...slug].astro | Mirror shell page |
src/components/DemoModeBanner.astro | Amber demo indicator banner |
src/layouts/CosmicLayout.astro | Demo embed handling + DOM redactor |
src/pages/api/demo/session.ts | Session create/validate/destroy |
src/pages/api/demo/tour.ts | Tour manifest generation |
scripts/check-demo-tour-leaks.mjs | Leak scanner |