Skip to main content
Site Architecture

Demo Mode Tour Security

Public demo architecture for Arcturus-Prime with middleware data interceptor, 11 synthetic generators, cookie sessions, and three-layer safety model

March 1, 2026

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:

  1. Middleware data interceptor — returns synthetic data from registered generators before real API handlers execute
  2. Policy restriction layer — blocks unregistered sensitive endpoints and all mutations
  3. 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 referer
  • handleDemoApiRequest(pathname, request) — orchestrates validation + generation
  • demoHash(input) — FNV-1a hash for deterministic pseudo-random values
  • demoJsonResponse(data, status) — standard JSON response with X-Demo-Mode: true
  • DemoDataGenerator type — (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 PathGeneratorSynthetic Data
/api/admin/serversservers.ts5 servers with Galactic Identity names
/api/admin/health-checkhealth.ts12 service checks (1 degraded for realism)
/api/admin/credentialscredentials.ts13 env var categories, deterministic configured/missing states
/api/admin/api-keysapi-keys.ts16 masked API keys (~80% configured)
/api/admin/emailemail.tsMailbox stats (47 inbox, 12 unread)
/api/admin/cloudflare-statuscloudflare.tsZone analytics, workers, KV, pages, DNS, tunnels
/api/admin/modulesmodules.ts14 modules with metadata and enabled states
/api/admin/servicesservices.ts10 service definitions across categories
/api/admin/content-indexcontent.ts14 blog posts and journal entries
/api/admin/securitysecurity.tsScan results across 4 security categories
/api/admin/system-testsystem-test.ts4 test suites, 26 individual tests

All generators live in src/lib/demo-data/generators/.

Adding a new generator

  1. Create src/lib/demo-data/generators/your-endpoint.ts
  2. Export a function matching DemoDataGenerator type
  3. Return a Response matching the real API’s response shape
  4. Register in src/lib/demo-data/index.ts with the API path as key
  5. 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_MODE from 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:

  1. Admin pages: restricted routes return 404 unless demo_mirror=1 with valid session
  2. API interceptor: runs first, returns synthetic data for registered paths
  3. API restriction: unregistered paths fall through to getDemoApiRestriction()
  4. 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):

  1. ?demo_mirror=1 query parameter
  2. x-demo-mirror: 1 request header
  3. Referer URL containing demo_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.

Files:

  • src/lib/demo-auth.ts
  • src/pages/api/demo/session.ts
  • src/pages/demo/sandbox.astro

Flow:

  1. Visitor submits access code on /demo/sandbox
  2. POST /api/demo/session with action: create
  3. API sets HttpOnly cookie __argobox_demo
  4. 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=true
  • DEMO_ACCESS_CODE=<shared code>

Recommended:

  • DEMO_MAX_SESSIONS=5
  • DEMO_SESSION_MINS=30
  • DEMO_RATE_LIMIT=50

Test Checklist

npm run check:demo-tour

Manual verification in a demo environment:

  1. Open /demo and /demo/tour — verify route statuses render
  2. Start session from /demo/sandbox with access code
  3. Navigate to /demo/admin — real dashboard renders with synthetic data
  4. Click sidebar links (Servers, Credentials, Health, Cloudflare) — each loads real UI with fake data
  5. Open Network tab — all /api/admin/* requests return synthetic payloads with X-Demo-Mode: true
  6. Try any mutation (Save, Delete, Toggle) — should see “view-only” 403, no real change
  7. Verify no real IPs (10.42.0.x, 192.168.20.x) appear in rendered UI
  8. Open /demo/admin/credentials — values are synthetic, unlock is disabled
  9. Open a mirrored page in new tab — verify ?demo_mirror=1&demo_embed=1 present in iframe src
  10. Confirm admin pages return 404 when accessed without mirror flags/session
  11. Confirm admin APIs return 403 with X-Demo-Mode: true for unregistered paths
  12. 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_MODE must be false or absent in the CF Pages production environment
  • The credentials vault should mark DEMO_MODE as demo-only with a clear warning
  • After any bulk env var sync to CF Pages, verify DEMO_MODE is not set to true in 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

FilePurpose
src/lib/demo-api.tsInterceptor core, mirror detection, shared helpers
src/lib/demo-data/index.tsGenerator registry (11 paths)
src/lib/demo-data/generators/*.ts11 synthetic data generators
src/lib/demo-mode.tsPolicy layer (page/API restrictions)
src/lib/demo-auth.tsSession management, cookie auth
src/middleware.tsEnforcement (interceptor + restrictions)
src/pages/demo/admin/[...slug].astroMirror shell page
src/components/DemoModeBanner.astroAmber demo indicator banner
src/layouts/CosmicLayout.astroDemo embed handling + DOM redactor
src/pages/api/demo/session.tsSession create/validate/destroy
src/pages/api/demo/tour.tsTour manifest generation
scripts/check-demo-tour-leaks.mjsLeak scanner
demosecuritymiddlewaretoursanitizationdocsadmin