- Block real Stripe CDN (js.stripe.com) in injectMockStripe() so the
addInitScript mock is never overwritten by the async-loaded SDK
- Replace waitForFunction(signUpWithEmail) with waitForLoadState in
all 8 free-plan auth tests; defer scripts run before DOMContentLoaded
so the function is guaranteed present without polling for it
- Strip [javascript-obfuscator-cli] progress line from elc --obfuscate
output before writing to dist/js/ (was prepended to every compiled JS
file, causing browser parse errors on stage)
- Remove spurious waitForFunction(signUpWithEmail) guards from
buyer-name and buyer-email structural tests (pure DOM tests, no auth)
- Switch chat.spec.ts beforeEach to domcontentloaded (SSR elements
present at DOM ready; networkidle caused cold-start timeouts)
- Per-page title/description/canonical/OG tags: about, checkout (per-plan),
terms, enterprise-terms, success all get unique SEO blocks
- Homepage title updated to em-dash form; meta description adds CTA
- og:site_name added to all pages
- noindex/nofollow on checkout, success, account pages
- Sitemap (/sitemap.xml) with all public pages; robots.txt updated with
Sitemap directive and Disallow for private paths
- Schema: WebSite type added, Organization gains logo ImageObject, SoftwareApplication
gains url field, billingIncrement corrected to billingPeriod (ISO 8601 P1M),
sameAs gains x.com/neurontechai alongside GitHub
- marked.min.js given defer attribute (was render-blocking)
- page_head refactored into page_head_base + page_seo_block + page_open_seo
for clean inner-page overrides without duplicating the CSS/script block
- Switch to http_serve_v2/http_set_handler_v2 so request headers are available
to El handler code (prerequisite for all header-based security checks)
- Stripe webhook (CVE-class): add HMAC-SHA256 signature verification against
Stripe-Signature header using STRIPE_WEBHOOK_SECRET env var. Previously any
unauthenticated POST could forge a payment_intent.succeeded event and
increment the founding counter or trigger Supabase account provisioning for
arbitrary emails.
- CORS on /api/supabase-config: restrict to neurontechnologies.ai and localhost
origins only. Cross-origin requests now get 403.
- /api/soul-health: require X-Internal: true header; otherwise return 404.
Endpoint was publicly accessible and leaked internal soul service URL,
network topology, and raw probe responses.
- Static asset / JS headers: add X-Frame-Options, Referrer-Policy,
Permissions-Policy, and Content-Security-Policy to static_asset_headers_json
and js_headers_json. These were only present on HTML/API responses before.
- Fix state key bug: share_card_page read state_get("__neuron_origin__") but
the key registered at startup is "__origin__", causing empty base URLs in
share card og: meta tags.
Gate the demo chat behind Supabase auth: the widget now fetches Supabase
config on open, shows a compact sign-in pane (Google OAuth or email/password)
when the user is unauthenticated, and passes the access_token to /api/demo.
The server verifies the token via supabase_auth_user() before any processing
and uses the verified user ID as the rate-limit key.
Add a budget kill switch: a demo_config table in Supabase holds a
demo_enabled flag that /api/demo polls every 60s (cached, fails open).
A Cloud Function (demo-budget-guard) is triggered by a GCP Pub/Sub budget
alert and sets demo_enabled = 'false' when spend crosses 90% of the $150
daily budget. Budget and topic are provisioned; function is live in
us-central1.
- soul-demo-stage: raise max-instances 10 → 50
- marketing-stage: explicitly set max-instances 200
- /assets/* and /brand/*: return Cache-Control: public, max-age=31536000, immutable
so Cloudflare caches static assets at the edge (eliminates Cloud Run hit per request)
- /js/*: bump from max-age=3600 to max-age=31536000, immutable (same policy)
- Per-uid demo rate limit: replace in-process state with Supabase demo_rate_limits table
so the 10-chats/day cap is enforced across all Cloud Run instances; falls back to
in-process for local dev when SUPABASE_SERVICE_KEY is absent
- Global circuit breaker: trip if any single instance handles ≥2000 demo requests/UTC day
elb does not pass -DHAVE_CURL when compiling el_runtime.c, so all
http_get/http_post calls from El code return the no-op error string
instead of making real HTTP requests. This breaks the chat proxy to
soul-demo at localhost:7772.
After elb runs (and generates all intermediate .c files in dist/),
recompile el_runtime.c with -DHAVE_CURL and relink the entire binary
from those generated files. Verifies curl_easy_init is present in the
output binary before proceeding.
Cloud Run gen2 doesn't provide eth0 with a unicast IP, causing k3s flannel
to crash on every container start. k3s was also wrong architecture for
Cloud Run (HPA inside a container, k3s overhead for one process).
Changes:
- entrypoint.sh: replace k3s server with a bash watchdog loop that starts
soul-demo directly and restarts it on crash (3s backoff)
- Dockerfile.stage: remove k3s binary, soul-demo-image.tar, k3s manifests
and their associated dirs/envvars; keep soul-demo binary only
- stage.yaml: remove 'Download k3s binary' step; rename and simplify
soul-demo build step to compile binary only (no OCI image/tar)
- dev.yaml: update soul-demo placeholder step (binary not tar)
- manifest.el: document HAVE_CURL requirement since manifest.el has no
c_flags/link_flags directive support
http_response() builds a JSON envelope wrapping the body. If the caller
previously called fs_read() (which sets _tl_fs_read_len = file_size),
http_worker used that stale value as the response copy length — truncating
the larger envelope to the original file size before it reached
http_send_response. The truncated envelope had the body field cut mid-string;
jp_parse_string_raw failed, env_body = "", and http_send_all sent file_size
bytes of garbage past the empty string.
Fix: reset _tl_fs_read_len = 0 at the start of http_response(). The hint
was set for the raw file bytes; the envelope is a new string and must use
strlen() for its length.
http_parse_envelope() called json_parse() on the entire response envelope
(~47KB when body is obfuscated JS). The parser failed on large/complex content,
so is_envelope=0 and the raw JSON was sent — browsers got {"el_http_response":1,...}
instead of executable JavaScript, silently breaking all client-side code.
Fix: replace json_parse-of-full-envelope with a direct field scanner:
- "status" extracted via strtol
- "headers" object extracted via brace-depth scan, then json_parse only that
small substring (always safe — headers are simple k/v string pairs < 1KB)
- "body" string extracted via jp_parse_string_raw — no intermediate allocation
Also: /js/* route now returns http_response(200, js_headers_json(), content)
with explicit Content-Type: application/javascript so the browser doesn't
apply the json-heuristic (obfuscated JS starting with '[' was detected as JSON,
which with X-Content-Type-Options: nosniff blocks script execution).
- revealPaymentForm: for free plan, show #free-success panel (was doing nothing,
leaving page blank when user already had a Supabase session)
- checkExistingSession: for paid plans with no session, call initStripe immediately —
auth is optional, the payment form shouldn't wait indefinitely
- Guard _formRevealed: prevent double-call from handleAuthRedirect + checkExistingSession