Public release notes for javascriptobfuscator.com. Rendered live from the repository CHANGELOG.md — no curation step between shipping a feature and you reading about it here.
Changelog
All notable changes to JSO-Website — the ASP.NET WebForms marketing
site, dashboard, hosted JavaScript-obfuscation service, and admin tooling
under javascriptobfuscator.com.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
[Unreleased]
Added - Supply-chain integrity surface in jso-protector npm (2026-05-28)
A full client-side integrity story shipped in one session — watermarking,
release attestation, pre-flight quota gate, and bulk forensic scanner.
All four pieces compose: stamp during build ⇒ sign the manifest
⇒ verify single file post-deploy ⇒ bulk-scan a directory tree.
Pure client-side; no new server endpoints; no schema changes.
- Watermarking —
--watermark <tag> --watermark-key <key> injects an HMAC-SHA256-signed header comment into every input file before the obfuscation API call. The obfuscator's existing KeepComment preserves it through every transform; verifiers can re-derive the HMAC in constant time. - --verify-watermark <file> — single-file validator - --scan-watermarks <dir> — tree walker for forensics + CDN inventory; three-state exit (0 = clean / 1 = invalid signature found / 2 = no watermarks at all) - Wire format ported to Python (jso_protector.watermark) and .NET (JsoProtector.Watermark); cross-language verification tests in all three (Node, Python, .NET) prove an artifact stamped by any client validates under any other. - Ed25519-signed release attestations — SLSA-style.
--sign-release <priv.pem> writes a .manifest.json.sig next to the existing manifest containing canonical-JSON-signed {BuildId, polymorphismFingerprint, label, files[{name, sha256}]}. --verify-release <sig> validates the signature; pair with --public-key to pin to a trusted key and --verify-root <dir> to also re-hash files on disk (catches post-signing artifact tampering). --genkey-release <name> mints a fresh keypair (.priv.pem 0o600, .pub.pem 0o644) so customers don't have to wrangle openssl. --estimate — pre-flight quota gate. Walks input files, calls /v1/ai/usage for current-month counters, prints a CI-friendly build-cost report with three gate states (OK / WARN / FAIL). FAIL (exit 1) when actions remaining = 0 — blocks builds that can't finish. WARN (exit 0) when < 5 actions or < 20% cost cap remaining. Network failure on the usage endpoint downgrades to WARN with a note rather than crashing the build.
Coverage:
packages/jso-protector/test/watermark.test.js — 14 casespackages/jso-protector/test/release-signer.test.js — 12 casespackages/jso-protector/test/estimate.test.js — 6 casespackages/jso-protector/test/scan-watermarks.test.js — 7 casespackages/jso-protector-python/tests/test_watermark.py — 11 cases (including cross-language verification against Node)packages/jso-protector-dotnet/JsoProtector.Tests/WatermarkTests.cs — 10 cases (including cross-language verification against Node)- npm total: 56/56 across 7 suites
- Python total: 30/30 across 3 suites
- .NET total: 18/18 across 2 test classes
Files touched:
packages/jso-protector/watermark.js (new)packages/jso-protector/release-signer.js (new)packages/jso-protector/bin/jso-protector.js (8 new flags + dispatch)packages/jso-protector-python/jso_protector/watermark.py (new)packages/jso-protector-dotnet/JsoProtector/Watermark.cs (new)
Added - AI built into the obfuscation CLI (2026-05-27)
jso-protector --ai-precheck — new top-level flag on the main obfuscation CLI. After resolving the input file set but before submitting to the obfuscation API, the CLI calls /v1/ai/compat-check on every file. Findings are aggregated and printed; the build aborts (exit 1) on findings that cross the configurable gate. - --ai-precheck-fail-on error (default) / warning / never. - Gate fails ⇒ obfuscation API is not called — quota untouched, no broken build shipped. - Gate passes ⇒ obfuscation proceeds normally, no behavior change. - Transport-level errors (auth / network) surface immediately rather than silently pass.jso ai compat-scan — companion sub-command for projects that want to run the gate separately from obfuscation (pre-commit hooks, IDE integrations, ad-hoc audits). Reads jso.config.json, walks the resolved input tree, exits non-zero per --fail-on. Supports --max-files to cap quota burn on huge repos.- Coverage:
packages/jso-protector/test/ai-precheck.test.js and compat-scan.test.js — 7 subprocess-based smoke tests using a unified mock server that routes /v1/ai/compat-check.ashx and /HttpApi.ashx separately so the contract (gate fails ⇒ obfuscation API call count is zero) is verifiable. - Docs updated:
Docs/AIQuickStart.aspx (new step 6), Docs/AIApi.aspx (folded-into-CLI note under compat-check), Docs/AIClients.aspx (CLI block now leads with --ai-precheck), homepage hero, and the AI round-up blog post.
Added - Python client AI bindings (2026-05-26)
packages/jso-protector-python/jso_protector/ai.py (new, ~160 lines, stdlib only — no new dependencies). Mirrors the npm ai namespace: - ai.preset_suggest(description, ...) → suggested config. - ai.compat_check(source, framework=...) → findings report. - ai.explain_error(error, ...) → diagnosed transform + fix. - ai.usage() → quota counters.jso_protector/__init__.py re-exports as `from jso_protector import ai`.- Same env-var fallback as the protect path:
JSO_API_KEY / JSO_API_PASSWORD. Endpoint override via JSO_BASE_URL or endpoint= kwarg. AiError exception with .code / .status / .body. HTTP 4xx/5xx raise; business-logic ok: false returns normally.packages/jso-protector-python/tests/test_ai.py — 11 unittest cases via http.server mock, mirroring the Node test surface. Full suite 19/19 PASS (11 ai + 8 existing protect).- Live smoke against localhost: all four functions return identical numbers to the npm client (10 actions remaining, balanced preset, 2 signals, 1 error + 1 warning, name-mangling cause with high confidence).
Changed - AIClients.aspx leads with jso CLI + library (2026-05-26)
Docs/AIClients.aspx — restructured tab order so the npm-package paths come first: 1. jso CLI — default-active. jso ai usage --pretty one-liners; pipe to jq for monitoring. Notes the CI exit codes. 2. Node (library) — const { ai } = require("jso-protector"), full TypeScript types, programmatic access. 3. curl, Python, Node raw HTTP, Go, .NET, Java, Ruby, PHP, Rust, Kotlin — the 10 original hand-rolled HTTP examples, unchanged.- New lede paragraph spells out the shortcut: "if you already use jso-protector from npm for obfuscation, the AI surface is built in." Customers who installed the package for obfuscation don't need to write fetch/urllib code.
- Page renders 200, 12 tabs total. The hand-rolled examples stay accurate for languages without a JSO client yet.
Added - jso ai CLI subcommand (2026-05-26)
packages/jso-protector/ai-cli.js (new, ~180 lines) — subcommands usage, preset-suggest, compat-check, explain-error. Each calls the matching ai.* method, pretty-prints the result with --pretty, or emits JSON by default (machine-friendly, pipes to jq).bin/jso-protector.js — intercepts argv[0] === "ai" at the top of main() and delegates to ai-cli.main. Preserves the existing protect-side parseArgs surface intact; the AI surface has its own minimal flag parser.- Auth resolves the same as the npm library:
--api-key / --api-password flags, or JSO_API_KEY / JSO_API_PASSWORD env vars. Same credentials as obfuscation. - Exit codes: 0 = ok, 1 = business-logic / HTTP error, 2 = argument / usage error. Suitable for CI scripts.
- Examples that now work:
`bash jso ai usage --pretty jso ai preset-suggest "React SaaS, balanced, lock to example.com" > jso.config.json jso ai compat-check src/app.js --framework react --pretty jso ai explain-error "Uncaught TypeError: api.charge is not a function" ` - Verified live: all four subcommands return correctly against localhost; unknown-subcommand exits with code 2; existing test suite remains 162/162 PASS (no regression on the protect-side parser).
Added - Unit tests for the npm ai client (2026-05-26)
packages/jso-protector/test/ai.test.js — 10 cases using the same node:test + http.createServer mock pattern the obfuscation tests use: - request body shape for each of the four methods (APIKey / APIPwd / per-method extra fields) - response envelope unpacked correctly (tier, suggestion, report, explanation) - sync input_invalid throw when required fields missing - auth_missing throw when neither opts nor env vars carry credentials - env-var fallback (JSO_API_KEY / JSO_API_PASSWORD) - HTTP 4xx surfaces as a rejected promise with .status + .body - business-logic ok: false comes back as a normal resolved value (HTTP 200, branch on ok) - User-Agent header advertises jso-protector-node/ai for upstream observabilitypackage.json — test script chains both files. Full suite: 162/162 PASS (152 obfuscation + 10 ai).
Added - jso-protector npm client: AI method bindings (2026-05-26)
packages/jso-protector/ai.js (new, ~150 lines, zero deps). Wraps the four /v1/ai/* endpoints as first-class methods: - ai.presetSuggest({ description }) → suggested jso.config.json. - ai.compatCheck({ source, framework? }) → severity-graded findings. - ai.explainError({ error, config? }) → diagnosed transform + fix. - ai.usage() → quota counters (free to poll).- Auth: reads
JSO_API_KEY / JSO_API_PASSWORD env vars by default; can be overridden via { apiKey, apiPassword } opts. Endpoint override via JSO_BASE_URL or { endpoint }. Matches the existing CLI's behavior. packages/jso-protector/index.d.ts — full TypeScript declarations for the new ai namespace + envelope + tier enum + finding shapes.packages/jso-protector/index.js — exports ai on the package root.- Customers running CI scripts no longer have to hand-roll HTTP POSTs to the AI endpoints. The shape mirrors the existing protect/obfuscate API.
- Verified live: -
ai.usage() → FreeTrial, 10 actionsRemaining. - ai.presetSuggest() → ok, balanced preset, 2 signals. - ai.compatCheck() → ok, 1 error + 1 warning. - ai.explainError() → ok, name-mangling, high confidence. - Existing test suite 152/152 PASS — no regression in obfuscation paths.
Fixed - Account.Password / APIKey.Password 50-char limit blocked PBKDF2 hashes (2026-05-26)
- Customer report: register and recover-password both errored with "The max length of field Password is limited to 50." regardless of what the user typed.
- Root cause: when JSOAdminPassword got hashed to PBKDF2 (round 11), the matching column-size bump for
Accounts.Password and APIKeys.Password never happened. PBKDF2 output is PBKDF2-SHA256$120000$<24-char-b64>$<44-char-b64> = ~90 chars. The 50-char ORM check at _RETempCode/RuntimeEntity_JSOv2_AppCode.ascx.cs lines 249 (Accounts) and 4051 (APIKeys) threw on every hashed save. Bug lurked for months because legacy plaintext rows (always <50 chars) kept working; only register / password-change / password-reset surfaced it. - Fix: - ORM property setters bumped
>50 to >255 at both lines. - SQL Server columns altered: `ALTER TABLE dbo.Accounts ALTER COLUMN Password NVARCHAR(255); same for dbo.APIKeys`. - 255-char width gives ~165-char headroom over current PBKDF2 output so iteration/salt parameter tweaks won't trip it again.
- Affected flows now functional:
/register.aspx, /recover.aspx + /recovery-verification.aspx, /dashboard/profile.aspx password change, anywhere PasswordProtector.Hash is called.
Changed - Dashboard 12-month views route through UsageMonthly (2026-05-21)
- Follow-up to round 153. After Usages becomes a 90-day rolling buffer, dashboards querying
Usages over >90-day windows would silently truncate to the buffer size. Two views were affected. dashboard/analytics.aspx — both the monthly bar chart (12-month bytes) and the daily-calls line chart in the 12m period view now read from UsageMonthly. The 7/30/90d short-range views stay on Usages (still within retention). Schema-aware fallback: if UsageMonthly table doesn't exist yet (deploy before first protect call), falls back to the legacy direct-Usages query so the page never breaks. Also reordered the monthlyLabels declaration above both the line and bar charts so both can index into the same label array.dashboard/Home.aspx queries that read Usages are all "this month" scoped (using monthStart) -- still within the 90-day window, no migration needed.- Verified live: analytics 302 (auth), Javascript-Obfuscator 200, AI smoke 9/9 PASS, BOM gate 52/52 PASS.
Changed - Usages-table growth bounded by design (2026-05-21)
- Background:
dbo.Usages was an unbounded append-only audit table. ~9,300 rows/day, ~3.4M rows/year. Hit 9.7 GB and filled the PRIMARY filegroup. Round 152 stopped the audit failure from 500ing customers, but the table itself was still unbounded -- the next outage was a year out. App_Code/UsageRetention.cs (new) -- owns the table-growth problem: - UpsertMonthly(accountId, apiKeyId, bytes, files, lines) increments a new dbo.UsageMonthly rollup row. MERGE-based UPSERT keyed on (BillingMonth, APIKeyID). ~70K APIKeys * 12 months = max ~840K rows/year, vs. 3.4M on Usages. This becomes the authoritative source for "what did this APIKey do this month." - SweepOldDetailAsync(days) deletes detail rows older than the retention window in 10K-row batches with 1-second pauses. Runs on a background thread; single-instance lock; never throws. - StartSweepTimer(days) kicks a 24h System.Threading.Timer that fires the sweep daily. First fire is 5 minutes after app start so a midday deploy still does housekeeping without waiting until 03:00. - Lazy schema creation: UsageMonthly + secondary indexes on first call; no separate migration step.App_Code/APIService.cs -- the existing audit transaction now also calls UsageRetention.UpsertMonthly(...) after the detail INSERT. Still inside the round-152 try/catch, so a rollup failure doesn't 500 the customer either.Global.asax -- Application_Start now calls UsageRetention.StartSweepTimer(retentionDays). Retention defaults to 90 days (UsageRetention.DefaultDetailRetentionDays); overridable via Web.config appSettings["UsagesRetentionDays"] for deploys that need a tighter / looser window.- End state: Usages is now a rolling 90-day buffer (~840K rows steady-state, ~1.5 GB);
UsageMonthly is the permanent reporting table (~70K-840K rows/year). The 9.7-GB-and-growing failure mode is structurally eliminated. - Verified live:
Javascript-Obfuscator.aspx (online demo) 200; homepage 200; AI smoke 9/9 PASS; BOM gate 52/52 PASS; schema auto-creates on first protect call after deploy.
Fixed - Obfuscator resilient to audit/billing DB failures (2026-05-21)
- Customer report: every obfuscate call (including online demo) failed with: `Could not allocate space for object 'dbo.Usages'.'PK_Usages' in database 'javascriptobfuscator' because the 'PRIMARY' filegroup is full.`
- Root cause: in
APIService.cs (~line 810), the post- protect block ran the billing meter update (apikey.SpendBytes += p.totalbytes; apikey.Save();) and the Usages audit INSERT (usage.Save();) inside one transaction inside the outer try. When the audit INSERT hit the filegroup- full error, the whole transaction rolled back and the outer catch (Exception x) replaced the customer's successfully- protected output with an error envelope. Customer sees a 500- style failure despite protection having actually completed. - Fix: wrap the audit/billing transaction in its own try/catch. Audit failures are now traced (`TraceError "[APIService] audit/billing write failed for account=N apikey=N bytes=B files=F: <reason>"`) but the protected output flows back to the customer normally.
- Trade-off documented inline: during an audit-write outage individual calls may not increment SpendBytes (the meter). Acceptable; the alternative -- splitting
apikey.Save() into its own transaction so billing succeeds while audit fails -- is a riskier edit on the billing hot path and the outage type is operationally fixable. - Operational note for ops (not a code fix; requires DB admin access): - Short-term:
TRUNCATE TABLE Usages if audit history can be discarded, OR archive old rows to a secondary table. - Better: enable autogrowth on the PRIMARY data file in SQL Server Management Studio (Database properties → Files → Autogrowth). - Long-term: monthly partition + retention policy on Usages; the table grows fast (one row per protect call). - Verified:
Javascript-Obfuscator.aspx (the online demo page) returns 200 after the edit; AI smoke 9/9 PASS; BOM gate 52/52 PASS.
Fixed - Reset-password NullReferenceException on missing inputs (2026-05-21)
- Customer report: "reset password function has issue."
- Root cause:
recovery-verification.aspx ResetPassword called .Trim() on param.ActiveCode and .ToUpper() on param.CheckCode BEFORE the explicit IsNullOrEmpty checks below them. Any client request missing either field threw a NullReferenceException. JSOv2Ajax surfaces that as a generic "Network error" rather than the per-field "Please type the validation code" message the validation path was designed to return. - Same pattern in
recover.aspx GetPassword: param.UserName.Trim() before the RequireUserName check. - Fix: null-coalescing normalize --
(param.X ?? string.Empty).Trim() -- on both pages. Missing fields now fall through to the designed per-field error response with a friendly message pointing at the right input. - Pages compile + render:
recover.aspx 200, recovery-verification.aspx 302 (existing account-enumeration defense, intentional). - BOM gate 52/52 PASS, AI smoke 9/9 PASS — no regression on adjacent surfaces.
Changed - Docs/AI.aspx status block + tier row refresh (2026-05-21)
Docs/AI.aspx — the "Status" row said "Phase 1 ships 2026-Q3 · waitlist open." Replaced with the three-timeline summary: preview + Stripe today → LLM-backed 2026-Q3 → Resistance Score 2026-Q4.- Tier prices now read from
AIService.Tiers["..."].PriceCents rather than the previous hardcoded $19/$79/$299, matching the SSOT chain extended in rounds 67-68. - "Pricing preview" row renamed to "Pricing & subscribe" and now links the pricing page and the dashboard widget where the Subscribe buttons live.
- New "Wire format" row groups API reference, JSON Schema, and 10-language client examples in one place — previously scattered across the body copy.
- MetaDescription updated to drop "Phase 1 ships 2026-Q3" and add the explain-error endpoint + the "preview + Stripe live today" framing.
- Verified live: page returns 200, the new copy is in the rendered HTML, AI smoke 9/9 PASS.
Changed - humans.txt acknowledges JSO AI + new standards (2026-05-21)
humans.txt — new JSO AI section summarizing Phase 1 (preview today, LLM 2026-Q3), Phase 2 (Resistance Score 2026-Q4), Phase 3 (deobfuscation benchmark 2027-Q1), and the no-persistence customer-code policy.- Standards section gains three new entries: the AI JSON Schema (
ai-wire-format.schema.json alongside the obfuscation one), the UTF-8 BOM convention (enforced by check-aspnet-bom.js in the verify chain), and Prometheus textfile-collector exposition format (the exporter at /download/jso-ai-quota-exporter.js). - The Keep-a-Changelog mention now points readers at the live-rendered
/changelog.aspx + RSS at /feed.aspx. - Live verified: humans.txt returns 200, new sections render.
Changed - Homepage hero JSO AI copy + CTA (2026-05-21)
Default.aspx — the homepage hero paragraph and the text-link CTA both predated the Stripe subscribe flow and said "JSO AI ships 2026-Q3 as a separate add-on." Refreshed to distinguish three timelines: - Today: preview-mode endpoints + self-service Stripe subscriptions. - 2026-Q3: LLM-backed mode flips on (same wire format). - 2026-Q4: Resistance Score on Corporate.- The hero CTA changed from "JSO AI — shipping Q3" pointing at
/Docs/AI.aspx to "JSO AI — 5-minute quick start" pointing at /Docs/AIQuickStart.aspx. First-time visitors land on the on-ramp, not the strategy overview. - Live 200.
Changed - Docs landing AI card refreshed (2026-05-21)
Docs/Default.aspx — the JSO AI card was last touched before AIQuickStart, AIClients, AIPhase1Deploy, the JSON Schema, the round-up blog post, and the two new browser previews (CompatCheck, ExplainError) existed. Card refreshed to list: - QuickStart as the lead (bolded; first link a customer should click). - API reference + JSON Schema side-by-side. - All three browser previews on one line. - Phase 1 deploy doc (operator-facing). - Three blog posts grouped on one line. - Pricing + Subscribe link (replacing the stale "waitlist" label).- Wording shifted from "Phase 1 ships 2026-Q3 (preview only)" to "Preview-mode endpoints live today, LLM-backed mode ships 2026-Q3" — matches the actual shipped state.
- Live 200; 9/9 smoke retained.
Changed - JSON Schema now covers all 6 endpoints (2026-05-21)
Docs/ai-wire-format.schema.json — added four new $defs: CheckoutCreateRequest, CheckoutCreateResponse, PortalCreateRequest, PortalCreateResponse. The schema previously stopped at the four read-side endpoints (preset-suggest, compat-check, explain-error, usage) and didn't cover the Stripe-side endpoints (checkout-create, portal-create, stripe-webhook).- Now 18
$defs total; clients validating responses can fail loudly on drift across the entire 6-endpoint surface, not just the four AI-action endpoints. The webhook is consumed by Stripe and signature-verified, so its body shape is set by Stripe rather than by us — intentionally not schema'd. - Mock smoke 8/8 PASS, live smoke 9/9 PASS — the fixture-vs-required-fields cross-check is still satisfied.
Added - Smoke coverage for /v1/health.ashx ai block (2026-05-21)
packages/polyglot-smoke/ai-smoke-live.js — ninth case. GET /v1/health.ashx; asserts ai.globallyEnabled boolean, ai.provider in the (preview|claude|openai) enum, ai.previewMode boolean. Locks the round-58 health surface against regression — if anyone refactors the handler and drops the AI block, or renames a field, the smoke fails.- Harness now 9/9 PASS.
Changed - Pricing page CTAs point at real subscribe flow (2026-05-21)
premium-membership.aspx — the three JSO AI tier cards' bottom CTAs no longer point at /contactus.aspx?subject=ai-*-waitlist (written when the Stripe flow didn't exist). They now link directly to /dashboard/AIUsage.aspx where the Subscribe-now buttons live, with label "Subscribe to AI Basic | Corporate | Enterprise."- Footnote updated: removed "the waitlist forms above feed into..." copy, added pointer to the AIQuickStart 5-minute walkthrough and kept the enterprise-pilot contact link for paid pre-Phase-2 pilots specifically. Closes the customer funnel gap where a prospect on the pricing page could not actually buy.
- AI smoke 8/8 PASS retained.
Verified - 33-route wire-surface smoke + admin nav wired (2026-05-21)
jsomanage/Admin.master — AIConfigCheck added to the admin top-nav alongside AISubscriptions / StripeWebhook / RateLimits, so the operator can reach it in one click from any admin page.- Final wire-surface smoke: 33 routes hit, every one responding as designed. - 13 AI doc pages: 200 × 13. - 7 AI endpoints (GET to a POST-only handler): 405 × 7 (correct method-check rejection). - 10 site core pages (home, blog, changelog, feed, roadmap, pricing, contactus, health, sitemap): 200 × 10. - 3 auth-gated (dashboard widget, AISubscriptions admin, AIConfigCheck admin): 302 × 3 (correct login redirect).
- Every shipped surface is reachable from outside; every protocol invariant (method check / auth gate / 200 envelope) holds. The 74-round Phase 1 buildout is verifiable end-to-end from a clean curl session.
Added - Admin AIConfigCheck page (2026-05-21)
jsomanage/AIConfigCheck.aspx — admin-only config dashboard. Reads all 11 AI/Stripe <appSettings> keys, masks secrets (4-char prefix + suffix), reports REQUIRED/OK/unset per row, and shows a single green-pill summary when all required keys are present. Mirrors the AIPhase1Deploy.aspx checklist live: deployer can answer "did I configure everything?" in one page-load instead of grepping Web.config.- Also surfaces runtime-computed state (
AIService.IsGloballyEnabled, AIService.Provider, AIService.Tiers.Count) so a misalignment between appSetting presence and the runtime view (e.g., a typo in the key name) becomes obvious. - Linked from
AIPhase1Deploy.aspx as new step 6 ("Confirm via the config-check page") before step 7's live smoke. Returns 302 to admin login when unauthenticated. - AI smoke 8/8 PASS retained.
Fixed - Dashboard widget HeadContent casing + Phase 2 spawn (2026-05-21)
dashboard/AIUsage.aspx — corrected the ContentPlaceHolderID="headContent" casing to match the canonical HeadContent declared in Dashboard.master. Consistent with the round-41 BodyContent fix. Dashboard still returns 302 (login redirect); no behavioral change but the page is now resilient against any future case-strict ASP.NET upgrade or alternate runtime.- Phase 2 spawn task raised — a separable task chip for "Scaffold Phase 2 Resistance Score" captures the next-phase intent outside this session: new
App_Code/ResistanceScorer.cs, v1/ai/resistance-score.ashx, schema extensions, smoke coverage, doc updates. Briefed with the encoding hygiene + .ashx context constraints learned in rounds 42-69. - Live AI smoke 8/8 PASS retained.
Added - AIQuickStart.aspx — 5-minute customer quick start (2026-05-21)
Docs/AIQuickStart.aspx — customer-facing zero-to- first-AI-call walkthrough. Five steps: 1. Subscribe — prices/caps render from AIService.Tiers (single source of truth chain extends to the quick-start page). 2. Grab your API key — clarifies that the same JSO API key works for AI (no separate key). 3. First call — curl + Python copy-paste snippets against preset-suggest. 4. Understand the response — envelope shape, ok-vs-HTTP-status branching rule. 5. Check your quota — usage endpoint + Prometheus exporter pointer.- Linked from the
Docs/AI.aspx landing as the "New?" on-ramp before the three browser-preview pages. - Page renders 200 live, UTF-8 BOM in place; added to sitemap with priority 0.8.
Verified - Full npm run verify chain green end-to-end (2026-05-21)
- After 70 rounds of AI-surface work, ran the entire
packages/jso-protector verify chain to confirm no upstream regressions: - CLI tests: PASS - Package metadata: PASS - Publish metadata: PASS - CI-template validity: PASS - Polyglot smoke: node + python + dotnet PASS (curl SKIP -- no POSIX shell on this Windows host). Cross-client schema check: 9 clients reference "Succeed"; Type enum 6 values -- consistent. - AI mock smoke: 8/8 PASS (includes the schema cross-check that locks ai-wire-format.schema.json against fixture coverage). - check-aspnet-bom: 52/52 PASS. - The cross-language wire-format contract, the AI envelope spec, and the runtime-compiler encoding invariant are all gated pre-publish. Adding a new AI endpoint, a new client, or a schema field without keeping the others in sync will fail
npm run verify before it ships.
Added - check-aspnet-bom CI script + sweep latent files (2026-05-21)
packages/polyglot-smoke/check-aspnet-bom.js — captures round-69's encoding learning as a runnable check. Scans App_Code/*.cs + v1/**/*.ashx for non-ASCII bytes without a UTF-8 BOM, fails non-zero with a remediation message.- Initial run flagged 12 pre-existing files with latent cp1252-mojibake risk (BlockScopeDisambiguator, ProtectorV2.*, PaypalRest, StripeRest, RuleBasedPresetMatcher, etc.). All compiled today, but a near-edit could trigger the bug class. BOMs added; check now passes 52/52.
packages/jso-protector/package.json — new verify:aspnet-bom npm target, added to the default verify chain so the encoding contract becomes a release-time gate. Pre-publish builds will fail if any new App_Code or .ashx file slips through without the BOM.- Live verified: AI smoke 8/8 PASS after the sweep; site renders no regressions.
Fixed - UTF-8 BOM on all this-session source files (2026-05-21)
- Investigation: pre-existing App_Code files with em-dashes / ellipses compile fine; mine didn't (rounds 42-46). Found the pattern — every pre-existing file with non-ASCII has a UTF-8 BOM (
EF BB BF); none of the files I added in this session did. The ASP.NET runtime csc reads BOM-less files as the system ANSI code page (cp1252), mojibake'ing the bytes. - Added UTF-8 BOMs to all 20 new files I introduced in this session: 4 App_Code, 7 v1/ai/, 4 Docs/, 1 dashboard/, 1 Blog/, 3 root .aspx (changelog/feed) and 1 .ashx (health, touched).
- Behaviorally transparent (the bytes are now correctly tagged as UTF-8); future Unicode additions to any of these files won't repeat the cp1252-decode mojibake class.
- Verified live: AI smoke 8/8 PASS, 9 doc-page routes return 200, no regression.
Changed - Pricing page sourced from AIService.Tiers (2026-05-21)
premium-membership.aspx — the three JSO AI tier cards no longer hardcode $19/$79/$299 + `50/500/5,000 actions + 200K/2M/20M tokens. Inline <%= ... %>` reads pull each value from AIService.Tiers["Basic"|"Corporate"|"Enterprise"], matching the dashboard Subscribe buttons (round 67).- Same drift-prevention rationale: the
Tiers dictionary that enforces caps in CheckQuota, prices subscriptions in checkout-create, sizes the Stripe webhook upserts, and populates the usage envelope — now also writes the customer-facing pricing copy. One number per tier, in one place, ground truth for everything. - Live verified: page returns 200 with prices rendered correctly; AI smoke 8/8 PASS retained.
Changed - Dashboard Subscribe-button labels driven by AIService.Tiers (2026-05-21)
dashboard/AIUsage.aspx — Subscribe-button labels no longer hardcode $19 / $79 / $299. A small WebForms <% foreach %> over AIService.Tiers["Basic|Corporate|Enterprise"].PriceCents renders the price string. Single source of truth: the same Tiers dictionary that enforces the caps in CheckQuota now drives the customer-facing copy. Price changes propagate automatically; no risk of dashboard saying "$19/mo" while the server bills $25.- Live smoke 8/8 PASS retained; dashboard returns 302 (login redirect) as expected.
Added - portal-create docs + smoke parity (2026-05-21)
Docs/AIApi.aspx — new #portal-create section with request / response shape, full error-code matrix (including stripe_customer_missing for manually-seeded subs and portal_not_configured for pre-config), notes that the webhook captures the Customer ID on first event.packages/polyglot-smoke/ai-smoke-live.js — new case asserting portal-create: no auth -> auth_failed. Harness now 8/8 PASS.sitemap.xml + Docs/AIPhase1Deploy.aspx — portal-create registered as a first-class member of the Stripe-side surface alongside checkout-create and stripe-webhook.
Added - Stripe billing-portal session + Manage button (2026-05-21)
v1/ai/portal-create.ashx — new endpoint creates a Stripe Customer Portal session for the authenticated account and returns the URL. Same dual auth (APIKey body OR session cookie) as checkout-create. Self-service subscription management: update payment method, view invoices, cancel.App_Code/AIService.cs — new GetStripeCustomerId(accountId) and SetStripeCustomerId(accountId, stripeId) helpers. Lazy ALTER TABLE AISubscription ADD StripeCustomerId NVARCHAR(64) on first use, so existing deploys don't need a migration step.v1/ai/stripe-webhook.ashx — now captures data.object.customer from each lifecycle event and persists it via SetStripeCustomerId. portal-create reads what the webhook wrote — no separate Stripe API round-trip needed.dashboard/AIUsage.aspx — active-subscription state now renders a "Manage subscription" button that POSTs to portal-create and redirects to the returned Stripe portal URL. Fallbacks: stripe_customer_missing (manually-seeded subscription) → "contact us for changes" link; portal_not_configured (pre-config env) → "contact support" link. Never dead-ends.- Verified: portal-create routes correctly through method-check → rate-limit → auth → (would-be 503 / 404 / Stripe call). Live AI smoke 7/7 PASS after rate-limit window reset.
Changed - AIPhase1Deploy doc resynced with the full shipped surface (2026-05-21)
Docs/AIPhase1Deploy.aspx — the operator hand-off doc previously said the Stripe webhook and checkout-link generator were "needs to land." Both shipped in rounds 59 & 60. Pre-flip state now lists them, plus the Subscribe buttons, the post-checkout success banner, the health AI block, and the 7-case live smoke.- New deploy step 4: "Configure Stripe billing" — spells out the five
appSettings keys (StripeApiKey + three Price IDs + StripeAiWebhookSecret), the Stripe dashboard products to create at $19/$79/$299, and the webhook configuration. Calls out the graceful "checkout_not_configured 503 -> contact-support fallback" path so deploying before Stripe is provisioned is safe. - Smoke target updated from 4/4 to 7/7 PASS expectation.
- TL;DR rewritten to reflect the longer-but-still-trivial ritual: SQL → LLM key → flag → Stripe → smoke.
Added - Post-checkout success banner on AIUsage (2026-05-21)
dashboard/AIUsage.aspx — when Stripe redirects back with ?subscribed=1, the widget renders a green confirmation banner explaining that payment landed and the dashboard refreshes once the webhook fires (Stripe is async). Independent of the hasSubscription branch so it shows up immediately even if the AISubscription INSERT hasn't completed yet.- Closes the round-trip: Subscribe button → Stripe Checkout → payment →
?subscribed=1 redirect → clear UX feedback → webhook writes row → reload shows new tier. - Live smoke retains 7/7 PASS; admin tool at
/jsomanage/AISubscriptions.aspx still returns 302 to login (no regression on the manual-provisioning fallback path).
Added - Subscribe-now buttons + session-cookie auth (2026-05-21)
dashboard/AIUsage.aspx — the "Upgrade to JSO AI" static link is replaced with three real Subscribe-now buttons (Basic / Corporate / Enterprise, with their monthly prices on the button label). Each posts `fetch('/v1/ai/checkout-create.ashx', { credentials: 'same-origin', body: '{"tier":"..."}' })` and redirects to the returned Stripe Session URL. Pre-config fallback: if the endpoint returns checkout_not_configured, the UI renders an inline "contact support for early access" link instead of throwing the user into a dead end.v1/ai/checkout-create.ashx + App_Code/AIService.cs — new AccountIdFromCurrentSession() helper resolves the AccountID from the dashboard session cookie when the body lacks an APIKey. The endpoint now accepts either authentication mode — APIKey for headless / CLI flows, session cookie for the dashboard Subscribe button.- Live smoke 7/7 retained.
Added - checkout-create + stripe-webhook public docs + smoke (2026-05-21)
Docs/AIApi.aspx — new #checkout-create and #stripe-webhook sections under the API reference. Documents the request/response shape, error-code matrix, idempotency semantics, and integration round-trip. Customers writing their own AI subscription UIs now have the API contract in one place.packages/polyglot-smoke/ai-smoke-live.js — two new cases: - checkout-create: unknown APIKey -> auth_failed — asserts the handler routes correctly through to the auth gate. - stripe-webhook: GET (always) -> 405 — required adding method: "GET" + expectStatus: 405 overrides to the case-runner, plus a getJson helper alongside postJson. Total harness now 7/7 PASS.sitemap.xml — both endpoints registered.
Added - Stripe checkout-link generator (2026-05-21)
v1/ai/checkout-create.ashx — companion to stripe-webhook.ashx. Takes {APIKey, APIPwd, tier, returnUrl?}, resolves accountId, looks up the matching Stripe Price ID in appSettings, POSTs to api.stripe.com/v1/checkout/sessions, and returns the Session URL the dashboard can redirect to. Same SDK-free pattern: form-encoded HTTP, no Stripe.net at the .ashx layer.- Sets
metadata[account_id] + metadata[ai_tier] on both the Session and subscription_data so the webhook can read either on its various event types. - Failure modes: bad JSON → 400; unknown APIKey →
auth_failed; bad tier → input_invalid; missing StripeApiKey / StripeAi<Tier>PriceId → 503 checkout_not_configured; Stripe 4xx → 502 upstream_error; per-IP rate limit 5/min → 429. - AIPhase1Deploy.aspx updated: the "Stripe checkout link generator" item that was tagged "needs to land" is shipped. The remaining configuration is purely a deploy-time setting (StripeApiKey + three price IDs in Web.config, + webhook configuration in the Stripe dashboard).
- Live smoke 5/5 PASS retained; the new endpoint correctly 405s GET, 400s bad JSON, surfaces
auth_failed for fake APIKeys, and falls through to checkout_not_configured 503 when the Stripe settings are absent.
Added - Stripe webhook handler for AISubscription lifecycle (2026-05-21)
v1/ai/stripe-webhook.ashx — ingests Stripe events, signature-verifies via HMAC-SHA256 over t.payload with the StripeAiWebhookSecret appSetting, and routes to AIService helpers. Stripe.net-free at the handler layer (the runtime ANSI compiler doesn't resolve Stripe.net types reliably from .ashx context, same constraint as JSODB); 20 lines of inline HMAC verification + constant-time comparison instead.- Events handled:
checkout.session.completed, customer.subscription.created, customer.subscription.updated (all upsert AISubscription); customer.subscription.deleted (set CanceledUtc). All other event types acknowledged + ignored. AccountID + tier read from metadata.account_id + metadata.ai_tier. App_Code/AIService.cs — four new helpers: UpsertSubscription(accountId, tier), CancelSubscription(accountId), WebhookAlreadyProcessed(eventId), RecordWebhookProcessed(id, type). All schema-aware (lazy-create AIWebhookEvent ledger table on first use) and idempotent. Webhook replays are no-ops.- Failure modes: no secret → 503 (Stripe retries with backoff so events aren't lost during secret rotation). Bad signature → 400. Bad JSON → 400. DB write failure → 500 (Stripe retries; next attempt hits idempotency).
- Verified live: GET returns 405; POST without secret returns 503 with structured JSON. Live AI smoke still 5/5 PASS.
- AIPhase1Deploy.aspx updated — webhook is no longer "not in Phase 1"; the remaining open item is the checkout-link generator that populates the session metadata.
Changed - /v1/health.ashx now surfaces AI subsystem state (2026-05-21)
v1/health.ashx — response gains an ai block: `{ "globallyEnabled": false, "provider": "preview", "previewMode": true }`. Lets monitoring tools and integrating clients answer "is AI live?" without hitting one of the endpoints and parsing the envelope. Strictly public information — no API keys, no account numbers, no usage counts.- Same handler stays credential-less and cacheless. Live response verified.
- Companion doc page (Docs/HealthEndpoint.aspx) can be updated in a follow-up to mention the new fields; consumers that read the raw JSON pick it up immediately because
additionalProperties was never asserted on health.
Changed - usage.ashx now surfaces actionsRemaining/tokensRemaining/costRemainingCents (2026-05-21)
v1/ai/usage.ashx — envelope now carries three pre-computed headroom fields alongside used/cap pairs. actionsRemaining = Max(0, actionsCap - actionsUsed), same for tokens and cost. Saves every client from doing the arithmetic (and getting unlimited-tier edge cases wrong).Docs/ai-wire-format.schema.json — UsageResponse required list extended with the three new fields. `additional Properties: false` is preserved, so clients that validate against the schema will fail loudly on stale server builds.packages/polyglot-smoke/ai-smoke.js — mock fixture updated; schema cross-check still passes.packages/polyglot-smoke/ai-smoke-live.js — live usage assertion strengthened: each new field must be numeric, and on FreeTrial-with-zero-usage the remaining must equal the cap.download/jso-ai-quota-exporter.js — three new Prometheus metrics: jso_ai_actions_remaining, jso_ai_tokens_remaining, jso_ai_cost_remaining_cents. Each falls back to computed Max(0, cap - used) if the server is older than the field. Customers can alert on "remaining < 10% of cap" instead of writing their own subtraction in PromQL.Docs/AIApi.aspx — response example updated.- Verified: mock 8/8 + live 5/5 PASS.
Added - 429 regression test in ai-smoke-live (2026-05-21)
packages/polyglot-smoke/ai-smoke-live.js — added a localhost-guarded rate-limit regression test. Fires 12 quick POSTs at compat-check.ashx and asserts at least one 429 surfaces. Cleanly SKIPs on non-localhost targets so production deploys don't burn a real visitor's per-IP rate-limit bucket just to test the limiter. Live run: PASS with codes=200,200,200,200,200,200,200,200,200,429,429,429. Total harness now 5/5.
Added - Per-IP rate limit on AI endpoints (2026-05-21)
- All four
/v1/ai/* handlers now check SiteUtility.CheckGenericIp before any work. Excess returns HTTP 429 + Retry-After header + structured JSON body ({"ok":false,"error":"rate_limited","message":"..."}). - Limits: -
preset-suggest / compat-check / explain-error: 10 req/min/IP (LLM-cost endpoints --- tight to deter scraping the preview-mode envelope). - usage: 60 req/min/IP (read-only; cron-friendly). - The per-account quota in
CheckQuota is the real authoritative cap; this is just an unauthenticated-burst-abuse backstop. Closes the "per-IP rate limit" item that AIPhase1Deploy.aspx flagged as "not in Phase 1" --- it is now in Phase 1. - Verified live: 12 quick POSTs to
preset-suggest return 200 200 200 200 200 200 200 200 200 429 429 429 --- 10 succeed, 3 rejected with Retry-After. Single-shot smoke retains 4/4 PASS.
Changed - AIService.RecordSuccess + RecordFailure write real rows (2026-05-21)
App_Code/AIService.cs — RecordSuccess now does the real INSERT INTO AIUsage (...) VALUES (...) with Outcome = 'succeeded', capturing AccountID, BillingMonth, Action, ActionCost, TokensIn/Out, ApproxCostCents, Provider, ProviderRequestID, BuildID. Idempotent on (Provider, ProviderRequestID): a retry of the same upstream call (network blip, client retry) is detected and skipped so the customer isn't double-charged.RecordFailure writes a row with Outcome = 'rejected:<AIError>' for quota-class failures only (ActionQuotaExhausted / TokenQuotaExhausted / CostCapHit). Provider errors stay trace-only -- they don't represent customer-visible quota pressure and shouldn't inflate the QuotaRejections counter the dashboard / exporter surfaces.- Both writes are wrapped in try/catch with a
sys.tables schema check first — partial-schema deploys and DB outages never 500 the customer's call. The LLM response has already gone out by the time these run; audit failures get traced for the sweeper. - Live smoke 4/4 PASS retained. New code only activates when
accountId > 0 and the AIUsage table exists; dev env has neither, so the path remains preview-mode. - With this in, the in-code Phase 1 pipeline is end-to-end: request → ResolveAccountId → CheckQuota → SendChat → RecordSuccess → counters tick in vw_AIMonthlyUsage → dashboard widget + Prometheus exporter + next quota check see the new numbers. No remaining no-op stubs in the AIService pipeline.
Changed - Dashboard widget routed through AIService.GetMonthlyUsage (2026-05-21)
dashboard/AIUsage.aspx — removed the duplicated inline ISqlProvider walk and rerouted onto the shared AIService.GetMonthlyUsage helper. The widget now reads the same data, via the same code path, as /v1/ai/usage.ashx, jso-ai-quota-exporter.js, and AIService.CheckQuota. One source of truth for "current-month AI counters for this account." Cut 40 lines of duplicated DAL. Live IIS Express returns 302 (login redirect, expected); endpoints still 4/4 PASS via npm run verify:ai-live.
Added - AIPhase1Deploy.aspx — operator hand-off doc (2026-05-21)
Docs/AIPhase1Deploy.aspx — single-page deploy hand-off for flipping Phase 1 GA on: SQL schema provision (idempotent script), two appSettings keys (AIProvider + ClaudeApiKey / OpenAIApiKey), one global enable flag (AIGloballyEnabled), optional manual subscription seeding for the early-access cohort, the live-smoke ritual via `npm run verify:ai-live`. Three-tier rollback plan (remove key → flag-off → per-account AIDisable insert), with recovery times. Observability hooks list (dashboard widget, Prometheus exporter, RSS feed). "Not in Phase 1" section flags the Stripe webhook + per-IP rate limit as Phase 1.1 work. Linked from Docs/AIApi.aspx and the sitemap (76 URLs now). Build EXIT=0 (live IIS Express returned 200 on the new page immediately).
Changed - LlmProviderClient.SendChat — real Claude + OpenAI calls wired (2026-05-21)
App_Code/LlmProviderClient.cs — replaced the claude-unconfigured / openai-unconfigured placeholder branches with full HTTP client implementations. - Claude path: POST api.anthropic.com/v1/messages, x-api-key from ClaudeApiKey appSettings, model from ClaudeModel (default claude-3-5-sonnet-latest), anthropic-beta: prompt-caching-2024-07-31 for system-prompt cache hits. Parses content[0].text + usage.input_tokens / output_tokens. Costs computed from current Claude 3.5 Sonnet pricing. - OpenAI path: POST api.openai.com/v1/chat/completions, Authorization: Bearer from OpenAIApiKey, model from OpenAIModel (default gpt-4o). Sets `response_format: {type: json_object} when request.ResponseFormat == "json"`. Parses choices[0].message.content + usage.prompt_tokens / completion_tokens. Costs computed from GPT-4o pricing. - Config gate: missing API key returns the preview envelope rather than failing. Provider switching via the existing AIService.Provider config (preview / claude / openai) still works as before. - Error path: WebException and generic Exception both return LlmResponse { Error = "provider_..._error: ..." } so callers can branch on response.Error != null and fall back to the rule-based engines if the provider 5xxes.- GA flip is now Web.config-only. Drop
<add key="ClaudeApiKey" value="sk-ant-..."/> and <add key="AIProvider" value="claude"/> into appSettings; set IsGloballyEnabled=true. No further C# changes needed. Live smoke still 4/4 PASS (config defaults to preview so the new code paths don't activate until intended).
Changed - AIService.CheckQuota full enforcement wired (2026-05-21)
App_Code/AIService.cs — replaced the scaffold-mode early-return in CheckQuota with the full four-step enforcement path: 1. Account-resolution guard (returns NoActiveSubscription for accountId ≤ 0). 2. AIDisable table lookup (table-existence-aware so a partial schema deploy doesn't 500); returns AccountDisabled with the configured Reason if present. 3. Tier + counters from the shared GetMonthlyUsage helper, so enforcement reads identical data to what the dashboard widget and /v1/ai/usage.ashx show the customer. 4. Cap comparisons in priority order — ActionQuotaExhausted first (most user-visible), then TokenQuotaExhausted, then CostCapHit (defense in depth). Each error carries a concrete upgrade-suggestion message pointing at /premium-membership.aspx#ai-pricing.- On success the
QuotaCheck envelope returns the resolved tier and the three "remaining" counters, so callers can surface actionsRemaining in their responses without a separate DB round-trip. IsGloballyEnabled is still off by default, so this code only activates when the global flag flips on. Live smoke 4/4 PASS retained — endpoints short-circuit to preview mode as before.- The Phase 1 engineering hand-off now has exactly one item left: swap the
LlmProviderClient.SendChat body to call Claude or OpenAI. All the wire format, auth, DAL, quota enforcement, and observability around it is already in production code.
Changed - APIKey base64 -> accountId resolution wired (2026-05-21)
App_Code/AIService.cs — new AIService.ResolveAccountId(string base64ApiKey) helper parses the body's APIKey field (same base64 format as HttpApi.ashx), looks up the APIKey row via JSODB.APIKey.LoadRow, returns the AccountID, or -1 on any failure. Never throws.v1/ai/usage.ashx — replaced the hardcoded long accountId = -1 stub with a call to the resolver. Real APIKey from the dashboard now scopes the response to the caller's actual account; the preview-mode envelope is preserved for invalid / missing / locked keys.- Verified end-to-end: live POST with a syntactically valid but nonexistent base64 key (
fakecode:999999) correctly falls back to the FreeTrial-preview envelope without erroring. Live smoke 4/4 PASS retained. - This closes the second-to-last Phase 1 GA gap (auth wiring). The remaining items in the engineering hand-off are
AIService.CheckQuota (enforcement) and LlmProviderClient.SendChat (actual LLM calls), both of which the existing stubs already mark with TODOs at the integration points.
Changed - Real DAL walk in AIService.GetMonthlyUsage (2026-05-21)
App_Code/AIService.cs — replaced the stub GetMonthlyUsage with a real ISqlProvider walk over sys.views + AISubscription + vw_AIMonthlyUsage. Three-stage graceful degradation: schema absent → SchemaPresent=false + FreeTrial defaults; subscription absent → FreeTrial tier with real usage counters; both present → tier + counters from the subscription. Every read is in its own try/catch so a partial schema deployment never 500s the endpoint. Added `using JScriptProtector; + using CuteSoftFrameworks;` to surface the DAL types matching the pattern in APIService.cs.- The /v1/ai/usage.ashx handler and /dashboard/AIUsage.aspx widget both already go through this helper. When the schema is provisioned and the auth wiring lands, real customer numbers start flowing with zero further code changes — the contract is locked.
- Live POST smoke still 4/4 PASS via
npm run verify:ai-live — the FreeTrial-defaults envelope is correctly returned because the dev SQL Server has no AI schema yet.
Added - verify:ai-live npm script + nav casing fix (2026-05-21)
packages/jso-protector/package.json — added verify:ai-live script alias for ai-smoke-live.js. Post-deploy validation is now a one-liner: JSO_BASE_URL=https://www.javascriptobfuscator.com npm run verify:ai-live. Deliberately not added to the default verify chain since it requires a running server; intended as a manual post-deploy ritual rather than CI gate.dashboard/navigation.xml — corrected href casing from aiusage.aspx to AIUsage.aspx to match the file on disk. Windows NTFS was forgiving the mismatch; case-sensitive filesystems (or some CDN/proxy configurations) would not.
Fixed - Preventative Unicode-punctuation sweep across the AI surface (2026-05-21)
- Replaced fancy Unicode punctuation (em-dash
—, en-dash –, ellipsis …, smart quotes ' ' " ") with ASCII equivalents (--, --, ..., ', ") in: App_Code/CompatibilityAnalyzer.cs (1 site), v1/ai/compat-check.ashx (1 site), v1/ai/preset-suggest.ashx (2 sites), v1/ai/usage.ashx (2 sites), dashboard/AIUsage.aspx (2 sites). All were inside comments where the compiler was happy today, but any future code change near them could re-trigger the ANSI-decode-mojibake bug class that surfaced in rounds 42-43. Eight live routes plus the four AI POST endpoints still green after the sweep.
Fixed - contactus.aspx jQuery ScriptResourceMapping (2026-05-21)
Global.asax — broader smoke surfaced a pre-existing 500 on /contactus.aspx: "WebForms UnobtrusiveValidationMode requires a ScriptResourceMapping for 'jquery'." Caused by RequiredFieldValidator on the form needing the unobtrusive validation jQuery shim, which .NET 4.5+ enables by default but doesn't auto-register. Added an Application_Start registration pointing the jquery name at the existing ~/assets2/global/plugins/jquery.min.js. Page now returns 200. No regression in the other tested routes.
Added - ai-smoke-live.js — live POST harness (2026-05-21)
packages/polyglot-smoke/ai-smoke-live.js — companion to ai-smoke.js. POSTs the four AI endpoint fixtures against a real running JSO website (IIS Express on localhost by default, configurable via JSO_BASE_URL). Catches the bug class the mock harness cannot — .ashx compilation context misses, UTF-8 source byte mojibake from the runtime ANSI compiler, C# 7+ syntax unsupported by the runtime compiler, master-page placeholder typos. Includes a CJK-character regex check that flips FAIL if the response body contains the mojibake signature of an em-dash decoded as cp1252-then-UTF-8. 4/4 PASS against localhost as of round-43.packages/polyglot-smoke/README.md — documented the two AI harnesses side-by-side: ai-smoke.js for the wire contract, ai-smoke-live.js for the live-server reality check after every deploy. Captures the manual smoke ritual that surfaced rounds 42 and 43's bugs so the next iteration runs it as one command instead of remembering five curl lines.
Fixed - End-to-end smoke against the real AI handlers (2026-05-21)
v1/ai/usage.ashx — the in-handler DB walk referenced ISqlProvider and RuntimeEntity directly, but .ashx files are compiled standalone by ASP.NET and do not see the JSODB namespace. Replaced the inline walk with a call to a new helper on AIService (which lives in App_Code and has full type context).App_Code/AIService.cs — added MonthlyUsage class + GetMonthlyUsage(accountId) helper that encapsulates the DB walk and returns a zero-filled SchemaPresent=false envelope today. Phase 1 GA replaces the body with the real walk; the handler contract doesn't change. Also fixed em-dash literals that mangled to 鈥 in the runtime ANSI-decoded compile path.App_Code/ErrorExplainer.cs — replaced 8 UTF-8 em-dashes with ASCII -- so the explain-error response body reads cleanly. Previously `"...by its original name 鈥? typically because..."` --- mojibake in the customer-facing field. Now "...by its original name -- typically because...".- End-to-end POST smoke against http://localhost:52582 now passes 4/4: preset-suggest returns React-detected balanced config, compat-check reports
errors: 1, warnings: 1 for an eval+document.write fixture, explain-error correctly diagnoses lock-tripped for "License has expired", and usage returns the FreeTrial envelope with tier, actionsCap: 10, and a valid ISO-8601-Z asOfUtc. First time the real handlers (not the mock-server harness) have been exercised by an HTTP client in this session.
Fixed - Live-server runtime bugs across the AI surface (2026-05-21)
App_Code/CompatibilityPatternMatcher.cs, App_Code/LlmProviderClient.cs — replaced UTF-8 ellipsis literal "…" with ASCII "...". ASP.NET's runtime csc reads source files as the system ANSI code page when no BOM is present, mangling the e2 80 a6 byte sequence and triggering CS1010: Newline in constant. MSBuild reads UTF-8 by default so this never surfaced in the static build — only the live IIS Express compile.Docs/AIClients.aspx, Docs/ExplainErrorPreview.aspx — ContentPlaceHolderID was MainContent but Doc.master exposes BodyContent. Both pages were 500ing with "Cannot find ContentPlaceHolder 'MainContent' in the master page".feed.aspx — restructured the C# scriptlet into a <script runat="server"> block with Page_Load so helper methods can sit at class scope (local functions inside <% %> scriptlet aren't supported by the runtime compiler). Also switched the regex's [-—] character class to ASCII - only; the em-dash got cp1252-mangled and produced Unterminated [] set at regex compile time.dashboard/AIUsage.aspx — replaced C# 7 digit separators 1_000_000 with 1000000 since the runtime compiler doesn't enable that language version..vs/config/applicationhost.config — corrected stale physicalPath that pointed at a 2015-era project directory.- Live smoke against IIS Express on http://localhost:52582 now returns 200 for all twenty test routes (home, changelog, feed, AIClients, ExplainErrorPreview, PresetAssistantPreview, CompatCheckPreview, AIApi, AIErrorRouting, AIVsCompetitors, AI landing, the new blog post, Resistance Score blog, /v1/health.ashx, the JSON Schema, the Prometheus exporter, the Cookbook, the roadmap, the sitemap, plus the dashboard returning the expected 302 to the login page).
Added - Round-up blog post: JSO AI preview surface shipped (2026-05-21)
Blog/jso-ai-preview-surface-shipped.aspx — marketing surface for the entire Phase 1 AI delivery: four endpoints, three browser previews, Prometheus exporter, JSON Schema, 10- language client snippets, RSS feed, changelog. Ties the individual shipped pieces together so prospects and customers can see the full surface at a glance. Featured as the lead secondary card on Blog/Default.aspx, added to sitemap, and picked up automatically by the RSS feed (next regex tick) once the heading lands. Build EXIT=0.
Added - AIClients.aspx — 10-language call examples (2026-05-21)
Docs/AIClients.aspx — copy-paste worked example for calling /v1/ai/usage.ashx in **curl, Python, Node 18+, Go, .NET, Java, Ruby, PHP, Rust, Kotlin** — one tabbed UI, ten panels. Same auth as the obfuscation API, so existing client users get an immediate AI on-ramp without picking up a new SDK. Adapt-to-the- other-three-endpoints section spells out the URL-and-body deltas for preset-suggest, compat-check, explain-error. Documented the full error-code enum (input_invalid, quota_exhausted, auth_failed, etc.) so client code can map them. Linked from AIApi.aspx's JSON-Schema section and the sitemap. Build EXIT=0.
Added - RSS feed + sitemap refresh (2026-05-21)
/feed.aspx — RSS 2.0 feed generated server-side from CHANGELOG.md. Each ### Added/Changed/Fixed/... heading with the trailing (YYYY-MM-DD) date becomes one <item> with stable GUID, pubDate, and bullet body as description. Cached 15 min. 39 headings detected in current changelog — feed emits the latest 30. Wired into <head> site-wide via `<link rel="alternate" type="application/rss+xml" href="/feed.aspx" />`, so feed readers auto-discover it from any page on the site.sitemap.xml — refreshed: 5 new entries for Docs/ExplainErrorPreview.aspx, Docs/ai-wire-format.schema.json, changelog.aspx, and feed.aspx (the ExplainErrorPreview entry closes the gap left when the preview triplet shipped without a sitemap entry of its own). 73 URLs total.changelog.aspx — lede block calls out the RSS feed as a subscription option for customers who'd rather watch via a reader than refresh the page.
Added - Public changelog page (2026-05-20)
/changelog.aspx — server-side renders this CHANGELOG.md file as HTML so customers and search crawlers can see the shipping cadence without cloning the repo. Hand-rolled markdown subset (H1/H2/H3, hyphen lists with continuation lines, fenced and inline code, bold/italic, bare URL auto-link, text links, horizontal rules); enough to render the existing entries faithfully without adding a markdown library to the WebForms project. Linked from the site footer next to Roadmap. Build verified EXIT=0.
Added - JSON Schema for AI endpoint envelopes (2026-05-20)
Docs/ai-wire-format.schema.json — JSON Schema draft 2020-12 formal spec for all four /v1/ai/* endpoint envelopes (preset-suggest, compat-check, explain-error, usage) plus the shared ErrorEnvelope. 14 $defs covering request shapes, response shapes, and the Confidence / Severity / Tier / Provider enums. Customers can validate parsed responses with any standard JSON Schema validator (ajv, jsonschema, jsonschema.net, etc.) and fail loudly when the API changes rather than silently misinterpret.ai-smoke.js — eighth test added: `schema cross-check: smoke fixtures cover schema-required fields`. Loads the schema, walks each endpoint, asserts the smoke fixture's response carries every field the schema lists as required. Locks the wire format from a second angle — the schema and the harness can't drift independently. Verified locally: 8/8 PASS.Docs/AIApi.aspx — new "JSON Schema" section pointing at the schema file and noting the harness cross-check.
Added - ExplainErrorPreview browser page (2026-05-20)
Docs/ExplainErrorPreview.aspx — third no-auth browser preview, completing the AI preview triplet alongside PresetAssistantPreview and CompatCheckPreview. Paste a protected-output runtime error; the diagnosed JSO transform, confidence (high/medium/low badge), what-happened, fix, and docs link render live. Ten quick-fill example chips covering the full rule table (renamed function, renamed member, ES-target mismatch, CSP / Self-Defending eval, webpack runtime, lock tripped, debug trap, moved resource, source-map leak, flat-transform recursion). Pure client-side — the regex engine is a line-for-line port of App_Code/ErrorExplainer.cs. Linked from Docs/AIApi.aspx#explain-error, Docs/AI.aspx landing, and the API-reference footer note (which now references all three previews). Build verified EXIT=0.
Added - Prometheus textfile exporter for JSO AI quota (2026-05-20)
download/jso-ai-quota-exporter.js — ~100-line MIT-licensed Node script, zero npm dependencies. Polls /v1/ai/usage.ashx, writes Prometheus textfile-collector exposition format. Emits jso_ai_scrape_success, jso_ai_actions_used / _cap, jso_ai_tokens_used / _cap, jso_ai_cost_cents / _cap_cents, jso_ai_quota_rejections_total. All metrics labeled with tier, preview_mode, billing_month. Atomic write via .tmp + rename so node_exporter never observes a partial file. Scrape failure flips jso_ai_scrape_success to 0 so Alertmanager can fire on prolonged exporter outage instead of metrics silently disappearing. Smoke-tested locally against a mock usage endpoint; valid Prometheus exposition format confirmed. Linked from Cookbook recipe 13 + Docs/AIApi.aspx #usage section.
Added - /v1/ai/usage.ashx + smoke + docs (2026-05-20)
v1/ai/usage.ashx — fourth JSO AI endpoint. Returns the calling account's current-month AI usage as JSON: tier, billingMonth, actionsUsed / actionsCap, tokensUsed / tokensCap, approxCostCents / costCapCents, quotaRejections, asOfUtc. Same numbers /dashboard/AIUsage.aspx shows, exposed as a structured endpoint so customers can wire quota into their own observability stack (Datadog, Prometheus, PagerDuty) without scraping the dashboard HTML. POST-only; 405 + Allow: POST for other methods. Schema-aware via sys.views existence check — serves preview-mode FreeTrial defaults when AI schema not yet provisioned. previewMode flag tells the client whether numbers reflect actual usage or default capacity. Polling does not count against actionsCap.packages/polyglot-smoke/ai-smoke.js — extended with a seventh test (`usage: envelope shape + tier/billingMonth/asOfUtc formats`). Verified locally: 7/7 PASS. Asserts tier enum membership, billing-month YYYY-MM-DD shape, asOfUtc ISO-8601-Z shape, and number-typing on every counter / cap pair.Docs/AIApi.aspx — new #usage section with request / response shape, tier enum, previewMode semantics, curl example. MetaDescription / MetaKeywords updated to four-endpoint inventory.Docs/Cookbook.aspx — recipe 13 rewritten to use the real endpoint instead of the dashboard-scrape placeholder. New example shows Datadog metric emission + a 90% threshold mail alert.JSO-Website.csproj — new content entry for v1\ai\usage.ashx.
Added - Compat-check browser preview + AI cookbook recipes (2026-05-20)
Docs/CompatCheckPreview.aspx — second working AI preview page. Paste JS, click Scan, see findings rendered live with severity grading (red error / amber warning / blue info), category tags, line + column, snippet, message, suggested fix. Four quick-fill example chips (risky function, React class component, webpack runtime, clean code). Pure client-side — the regex engine is a faithful mirror of <code>App_Code/CompatibilityPatternMatcher.cs</code>; both stay in sync via the polyglot AI smoke harness in <code>verify:ai</code>. The page works as a free tool today and as a UX prototype for the LLM-backed version that ships Phase 1 GA (2026-Q3). Sitemap priority 0.8; linked from the Docs landing AI card next to PresetAssistantPreview.Docs/Cookbook.aspx — four new AI-flavored recipes appended after the existing 10: <strong>(11)</strong> "Block a CI build on compatibility findings" — <code>curl</code> to <code>/v1/ai/compat-check</code> + jq parse + non-zero exit on <code>severity:error</code>. <strong>(12)</strong> "Sentry beforeSend with auto-diagnose" — the production triage composition (symbolicate + explain- error in the same callback), one-liner with the AI-additive caveat. <strong>(13)</strong> "Monitor AI quota from your own dashboard" — the today-pattern (dashboard scrape) plus the early-access invitation for the dedicated <code>/v1/ai/usage</code> JSON endpoint coming with Phase 1 GA. <strong>(14)</strong> "Generate jso.config from a one-line description" — <code>curl</code> to <code>preset-suggest</code> + pipe to <code>jq</code> > jso.config.json. Each recipe runs against preview mode today, against the LLM-backed version from 2026-Q3 with no config change.
Added - Production triage doc + AI competitive comparison (2026-05-20)
Two substantive docs converting the existing AI surface into customer
answers for the two highest-frequency sales/support conversations:
"how do I use this end-to-end in production?" and "how does it compare
to obfuscator.io / Jscrambler's similar marketing?"
Docs/AIErrorRouting.aspx — composition guide for <code>jso-symbolicate</code> + <code>/v1/ai/explain-error</code>. Four-stage pipeline (crash → reporter SDK → demangle → AI diagnose), inline Sentry <code>beforeSend</code> sketch wiring both steps in the same callback, offline two-command path for support triage from saved logs, before/after example showing the same crash rendered as <code>"_0xa3f.b is not a function"</code> vs <code>"calculateLicenseHash is not a function" + jso_diagnosis_transform: "Name Mangling"</code>, table of all 8 reporter integrations with the symbolicate + explain-error hook positions. Explicit on quota / cost / sampling strategy for high-traffic sites. The AI augmentation is additive, never gating.Docs/AIVsCompetitors.aspx — honest comparison against the two most-cited competitor AI claims. obfuscator.io "anti-LLM defenses" gets an 8-row capability table; Jscrambler "AI-resistant code integrity" gets a 10-row capability table. Each table is followed by an explicit "where competitor wins" / "where JSO wins" paragraph — no taglines, just specifics. Sections on the three claims JSO will never make (AI-proof, reverses CASCADE, anonymized LLM testing) and three questions a customer can ask any vendor (named attacker? reproducible? scoring on weak profiles?). The "ask competitors the same questions" framing turns the page from competitive marketing into a customer-tool.- Both pages linked from sitemap (priority 0.8 each) and from the "JSO AI — preview" card on <code>Docs/Default.aspx</code>.
Added - Dashboard nav + admin AI tool + docs subsection (2026-05-20)
The discovery and operations surface for JSO AI. Customers can now find
the usage widget from the dashboard nav; admins/QA can drive the widget's
three states against real DB rows; the docs landing groups all AI pages
into a dedicated card.
dashboard/navigation.xml — added "JSO AI" entry between Usages and Monitoring (icon: <code>ki-abstract-26</code>). Customers who land on the dashboard now see the AI link in the left rail. The AIUsage page already rendered three states correctly (no schema / no subscription / active) so the link works in every deployment state.jsomanage/AISubscriptions.aspx — admin tool. Three sections: a Grant form (AccountID + Tier dropdown → cancels any existing active row, inserts a fresh one with the in-code Tier table's caps), a Cancel form (sets CanceledUtc on the active row, history preserved), and a grid showing all subscriptions ordered by ID descending. Pre- built dropdown lists all four tiers including FreeTrial. Renders a clear "AI schema not applied" banner when the tables don't exist yet (so partially-deployed environments don't throw). Linked from Admin.master's main nav. Purpose: QA can exercise the dashboard widget against real DB data before the Stripe webhook handler is wired; once GA, the tool remains as a support/refund/bug-triage surface.Docs/Default.aspx gains a dedicated "JSO AI — preview" card grouping the six AI-related entry points: overview, browser preview, API reference, CASCADE blog, Resistance Score blog, pricing & waitlist. Visitors landing on /Docs/ no longer have to hunt across multiple cards to find the AI surface.
Added - Third AI endpoint + API reference doc (2026-05-20)
Phase 1's third flagship feature lands. The three Phase 1 user-facing
capabilities (preset assistant, compatibility checker, config-help chat)
all now have working endpoints in preview mode.
App_Code/ErrorExplainer.cs — deterministic rule-based diagnoser. Ten regex rules covering the most common protected-output runtime errors and mapping them to the JSO transform that caused them: name-mangling ("is not a function"), member-rename ("Cannot read property X of undefined"), ES target mismatch ("Unexpected token"), Self-Defending vs CSP ("eval is not allowed"), webpack runtime integration breaks, lock-tripped messages, debug-protection halt, output-path mismatches, source-map leakage, and FlatTransform recursion. Each result carries cause + transform + confidence + explanation + concrete fix + docs URL./v1/ai/explain-error.ashx — third AI endpoint. POST an error string (plus optional jso.config snippet), get a structured diagnosis. Same architecture: AIService quota check, preview-mode rule-based / production LLM, RecordSuccess/Failure, provider-failure fallback to rule-based. 64KB hard cap on the error message, 32KB on the config snippet so a pasted megabyte log can't burn unbounded CPU.packages/polyglot-smoke/ai-smoke.js extended — two new test cases (explain-error envelope + confidence-enum check + missing- error input-invalid). 6/6 PASS locally; the harness now covers all three Phase 1 endpoints. Wired into the same verify:ai npm-run-verify chain.Docs/AIApi.aspx — customer-facing API reference. Common envelope (ok / previewMode / result / provider / tokens), error envelope (ok:false / error code / message), one section per endpoint with the full request/response shape and a copy-pasteable curl example, quota-and-pricing summary, the preview-mode vs LLM-backed delta explained explicitly (provider value changes, tokens populated, result quality goes up on novel inputs; wire format unchanged). Sitemap priority 0.8.
Phase 1's three customer-facing features are now all exercise-able end-
to-end against preview mode. The engineering team's punch-list from the
prior round (JSODB DAL walk + Claude/OpenAI body swap + Stripe webhook
SKU insert) is unchanged; the dashboard widget that was item #4 shipped
the round before.
Added - Second AI endpoint + dashboard widget + smoke harness (2026-05-20)
Continuing the Phase 1 server-side scaffold from the prior entry, this
round adds the second AI endpoint, the customer-facing usage widget that
reads the metering view, and a smoke harness that locks the AI wire
format the same way <code>verify:polyglot</code> locks the protect wire
format.
App_Code/CompatibilityPatternMatcher.cs — deterministic regex scanner for patterns that historically break under Maximum-mode obfuscation. Eight categories: dynamic-eval, reflective property access, framework runtime markers (Webpack symbols, React lifecycle methods, Vue hooks), explicit-keep pragmas, inline HTML injection, source-map references, debug leaks. Each finding carries severity (error / warning / info), line+column, snippet, message, and a suggested fix. Doubles as the circuit-breaker fallback when the real LLM is wired but rate-limits./v1/ai/compat-check.ashx — second JSO AI endpoint. POSTs JS source + optional framework hint, returns a structured findings report. Same architecture as <code>preset-suggest.ashx</code>: <code>AIService.CheckQuota</code> → preview-mode regex scan or <code>LlmProviderClient.SendChat</code> → <code>RecordSuccess</code>/<code>RecordFailure</code>. 256KB hard cap on the snippet size so a malicious input cannot exhaust regex CPU. Provider-failure path falls back to the rule-based scan rather than erroring — customers always get an answer.dashboard/AIUsage.aspx — customer-facing usage widget. Three rendering states: (1) AI schema not yet provisioned → preview banner + waitlist CTAs, (2) schema present but no subscription → free-trial banner + upgrade CTAs, (3) active subscription → three live progress tiles (actions used/cap, tokens used/cap, approx cost/cap) with green/amber/red graded bars at 70% / 90% thresholds, plus the quota-rejection counter. Reads <code>vw_AIMonthlyUsage</code> via the existing JSODB <code>sp.ExecuteReader</code> pattern; checks for the view's existence in <code>sys.views</code> so a partially-deployed environment renders the preview banner cleanly instead of throwing.packages/polyglot-smoke/ai-smoke.js — mock-server-based smoke harness for both AI endpoints. Asserts envelope shape (ok, previewMode, suggestion/report subobject, signals/findings array, finding.severity in enum, all required fields present and typed), plus error-path coverage (missing description / source → input_invalid). Wired into <code>npm run verify:ai</code> in jso-protector and into the <code>verify</code> + <code>prepublishOnly</code> chains. 4/4 PASS locally. Locks the AI wire format so that when the Phase 1 implementation team swaps the rule-based body for the real LLM call, an accidental envelope-shape regression fires the build before publish.
Added - Phase 1 AI backend scaffold (2026-05-20)
First server-side AI scaffold — compiles clean, integrates with the
existing build, exposes the quota-check + provider-call contracts the
engineering team will fill in. Nothing here actually calls an LLM yet;
everything is end-to-end exercisable in "preview" mode (deterministic
rule-based responses) so handlers, dashboards, and tests can be wired
before the real provider lands.
App_Data/CreateAITables.sql — idempotent schema: <code>AISubscription</code> (tier, caps, billing day, started/canceled), <code>AIUsage</code> (append-only audit, one row per action), <code>AIDisable</code> (per-account opt-out switch), <code>vw_AIMonthlyUsage</code> (helper view aggregating per-account monthly action / token / cost totals plus quota-rejection count).App_Code/AIService.cs — Tier table in code (FreeTrial / Basic / Corporate / Enterprise with matching action caps, token caps, cost-cap cents, price cents). <code>QuotaCheck</code> contract with <code>AIError</code> enum (None / GloballyDisabled / AccountDisabled / NoActiveSubscription / ActionQuotaExhausted / TokenQuotaExhausted / CostCapHit / ProviderError / InputInvalid / CircuitBreakerOpen). <code>CheckQuota</code> / <code>RecordSuccess</code> / <code>RecordFailure</code> stubs returning <code>GloballyDisabled</code> until the JSODB DAL walk is wired in the next engineering iteration.App_Code/LlmProviderClient.cs — <code>LlmRequest</code>/ <code>LlmResponse</code> final-shape stub. <code>SendChat</code> switches on the configured provider (<code>claude</code>, <code>openai</code>, <code>preview</code>) and returns a deterministic preview envelope until the real Anthropic / OpenAI bodies are dropped in. The final wiring is one function-body swap; the signature, error model, and token-counting fields are already final.App_Code/RuleBasedPresetMatcher.cs — server-side mirror of the client-side rule engine in <code>Docs/PresetAssistantPreview.aspx</code>. Same keyword detection, same JSON shape. Doubles as the circuit-breaker fallback for when the real LLM is wired but goes down or rate-limits./v1/ai/preset-suggest.ashx — first JSO AI endpoint wired end-to-end. POST-only (405 otherwise), Newtonsoft.Json body parse, <code>AIService.CheckQuota</code> call, branch to <code>LlmProviderClient.SendChat</code> or <code>RuleBasedPresetMatcher.Suggest</code> depending on global enable state, <code>RecordSuccess</code>/<code>RecordFailure</code> for audit, response envelope <code>{ok, previewMode, suggestion, provider, tokensIn, tokensOut}</code>. The handler-side code path is the final one; flipping to real LLM mode is a Web.config change plus the <code>SendChat</code> body swap.Web.config gains <code>JSOAI.Enabled</code> (default False), <code>JSOAI.Provider</code> (default <code>preview</code>), <code>JSOAI.ClaudeApiKey</code> / <code>JSOAI.OpenAIApiKey</code> (commented placeholders), <code>JSOAI.CostCapDailyCents</code> (default 10000 = $100/day deployment-wide hard cap). The wiring path is documented inline in the comment block.
The engineering team's remaining work to ship Phase 1:
(1) wire <code>AIService.CheckQuota</code> against JSODB,
(2) drop the Claude / OpenAI bodies into
<code>LlmProviderClient.SendChat</code>,
(3) wire the Stripe webhook handler to insert
<code>AISubscription</code> rows on tier purchase,
(4) build the dashboard widget reading <code>vw_AIMonthlyUsage</code>.
Added - Preset assistant preview (2026-05-20)
Docs/PresetAssistantPreview.aspx — fully working client-side preset assistant. Type a description like "React SaaS frontend with balanced performance, lock to example.com, protect the license-check function strongly", click Generate, see a live <code>jso.config.json</code> with the matching preset, LockDomain entry, suggested VM-virtualize comment, and framework-specific exclusion signals. Five quick-fill example chips. Output is editable in place; copy-to-clipboard button. A "detected signals" panel below the output explains what the rule-based mapping picked up so customers can see the reasoning.- Honest preview banner explains: today is deterministic keyword matching; from 2026-Q3 the same UX is backed by Claude, with smarter reasoning over ambiguous descriptions, framework-specific defaults, and explicit trade-off naming. The promise of the AI version is honest reasoning, not just keyword matching — the rule-based preview is the UX prototype, the real feature is the conversation.
- Strategic value: this is the first user-facing "JSO AI" thing. Visitors can feel the experience now; the engineering team has a fixed UX target before they wire the LLM; the page captures interest with the AI Basic waitlist CTA at the bottom. Cross-linked from <code>Docs/AI.aspx</code> as the "Try it now" entry point.
Added - Resistance Score blog post + homepage AI announcement (2026-05-20)
Blog/resistance-score-verifiable-ai-resistance.aspx — substantive piece on the Phase 2 Resistance Score feature. Explains why a numeric AI-resistance metric is needed in 2026 (CASCADE makes "trust us" unacceptable), what three properties make the metric useful (customer-specific, repeatable, attacker-disclosed), the actual scoring methodology including the per-category breakdown and the named-attacker fingerprint, the three honesty trade-offs we deliberately made (attacker is named not anonymized, score can go down when we upgrade the in-house attacker, score is a probe not a proof), what the score doesn't measure (runtime tampering, performance overhead, in-runtime threats), and how the selective-obfuscation suggester reads the breakdown to recommend targeted upgrades. The companion piece to the CASCADE post: that one frames the threat; this one frames the artifact that lets customers verify our response to it. Sitemap priority 0.9.- Homepage hero refresh — copy now explicitly announces JSO AI shipping 2026-Q3 with a one-sentence description of the three flagship Phase 1/2 features (preset assistant, compatibility checker, Resistance Score against CASCADE-class attackers). New "JSO AI — shipping Q3" CTA link in the hero action row pointing at <code>/Docs/AI.aspx</code>. Badge row's fourth slot replaced with an AI ship-date + waitlist link.
Added - JSO AI customer-facing surface (2026-05-20)
Customer-facing pieces that let sales conversations start before any AI
implementation code lands. None of these requires the AI backend to exist
yet; they all describe what's coming and capture demand.
Docs/AI.aspx — single-page customer overview of JSO AI: what features are coming, why a separate subscription, how usage is metered, where the data goes, the three-phase roadmap with ship targets, the position ("AI-resistant, AI-aware, threat-model-honest" rather than "AI-proof"), waitlist links. Linked from sitemap at priority 0.9.FAQs.aspx gains five new entries covering AI ship dates and pricing, "can I use JSO AI without buying obfuscation", what's sent to the LLM provider and the no-persistence policy, the account-level Disable AI toggle, and what happens at quota exhaustion (hard quota, no surprise overages). Both the rendered Q&A list and the JSON-LD schema updated for SEO.contactus.aspx waitlist capture — when reached with <code>?subject=ai-basic-waitlist</code>, <code>?subject=ai-corporate-waitlist</code>, or <code>?subject=ai-enterprise-pilot</code> (the CTAs from the pricing cards on premium-membership.aspx), the page renders a tailored "JSO AI waitlist" banner above the form and pre-fills the message body with the relevant intent. The Submit handler sanitizes the subject parameter against a strict whitelist (a-zA-Z0-9-, max 64 chars) and prepends a <code>[Routing tag: ai-corporate-waitlist]</code> line to the outbound email so support can prioritise without asking the customer where they came from.
Added - JSO AI strategic plan + preview pricing + roadmap (2026-05-20)
The CASCADE / LLM-deobfuscation trend (see prior entry) combined with the
2025-26 customer expectation that paid tools ship AI-augmented UX means
JSO needs an AI surface — but the wrong move is shipping ten AI
features at once. This entry adds the planning artifact, the public
pricing commitment, and the roadmap entries.
AI_PLAN.md — full strategic plan as a repo doc. Owner-decision-ready: surface separation (UX vs at-protect-time AI), three-tier add-on subscription (AI Basic $19 / Corporate $79 / Enterprise $299) parallel to the existing obfuscation tiers, dual metering (AI actions + protection token pool), three-phase delivery (Q3-26 UX, Q4-26 Differentiation, Q1-27 Moat), LLM provider choice (Claude primary, OpenAI fallback), hard-quota-no-overage default, per-account cost cap, risk matrix, open team questions.premium-membership.aspx — new "JSO AI" section with the three-card pricing grid as preview/waitlist pricing. Each card lists monthly AI-action quota, protection token pool, and the included features. Waitlist CTAs feed /contactus.aspx?subject=ai-{tier}-... so the development priority queue is customer-weighted.roadmap.aspx — three "Next" cards for the three AI phases. Phase 1 (UX foundations) targets 2026-Q3; Phase 2 (Resistance Score + Selective Obfuscation) 2026-Q4; Phase 3 (Deobfuscation Benchmark + continuous anti-LLM evolution) 2027-Q1. Each card has a link to the pricing preview and to the threat-model blog post.- Strategic position documented in the plan: not "AI-proof" obfuscation (that claim doesn't survive any competitor blog post), but "AI-resistant, AI-aware, threat-model-honest." The Resistance Score and Deobfuscation Benchmark are the customer-verifiable artifacts that make the position credible.
Added - CASCADE threat-model response (2026-05-20)
The Google CASCADE paper (arXiv:2507.17691) shipped July 2025: a hybrid
Gemini + JavaScript-IR deobfuscator that identifies <em>prelude functions</em>
(string-array decoder, control-flow dispatcher) without per-vendor
hardcoded rules, then symbolically inverts them. This raises the bar on
what "per-build polymorphism is enough" can mean, and we owe customers
a straight reckoning.
Blog/cascade-and-the-llm-deobfuscator-question.aspx — substantive write-up. Walks the CASCADE architecture, names which JSO marketing claims survive (polymorphism still raises per-build cost) and which don't ("polymorphism makes the output unreadable to AI" was always over-stated and is now visibly false). Explains three lines of defense that erode CASCADE's structural prerequisite: prelude/body interleaving, non-extractable runtime-bound decoders, and the VM bytecode beta. Concrete hot-path vs cold-path protection guidance.Docs/VMProtection.aspx lead paragraph rewritten to cite the CASCADE paper as the published evidence that motivated raising VM bytecode protection from "nice-to-have" to a roadmap priority. The pitch is concrete: the IR pass that inverts a decoder doesn't know how to invert an opcode dispatch.FAQs.aspx gains a new entry "Does Google's CASCADE deobfuscator break JSO's protected output?" with the honest answer (polymorphism alone no longer holds the line; use VM bytecode + runtime-bound decoders for high-value code paths). Both the rendered Q&A list and the JSON-LD schema updated.roadmap.aspx Research section gains three CASCADE-motivated entries: prelude-interleaving transform, non-extractable decoder pattern (one-flag wiring of RuntimeFingerprint/SessionToken into the string-array decoder), and symbolic-execution detection at runtime (adjacent to <code>DetectHeadlessBrowser</code>).- Sitemap entry added for the new blog post at priority 0.9.
Added - humans.txt + live status widget + a11y pass (2026-05-20)
humans.txt at site root — standard companion to security.txt. Team / location / philosophy / tech stack / published-package inventory / standards-we-care-about / acknowledgments. Inventory currently lists the four npm packages, PyPI, NuGet, RubyGems, Packagist, crates.io, Maven Central (Java + Kotlin), VS Code Marketplace, JetBrains Marketplace, GitHub Marketplace Action.Docs/StatusCheck.aspx — live in-browser polling widget for /v1/health.ashx. Polls every 10 seconds via fetch(), shows the OK/FAIL status with a colored dot, the round-trip time, reported version, reported uptime, and a 20-entry recent-checks table. Check-now and Pause/Resume buttons. All metrics are computed in the browser; nothing is sent anywhere except the same <code>/v1/health</code> GET any monitoring tool already issues. Includes the runbook callout for "endpoint failing while the rest of the site works" (= almost always a routing / WAF / CDN issue).- Accessibility pass on Scorecard + ToolingMatrix — the Scorecard tile grid gets
role=list + per-tile role=listitem with an aria-label that combines the number and the category so screen readers announce <em>"9 first-class language clients: Node, Python..."</em> instead of just "9". The big numeric digits are marked aria-hidden to avoid being read twice. Every one of the nine tables in ToolingMatrix gets a descriptive aria-label and scope="col" on every <th>. StatusCheck's status card carries role=region + aria-live=polite so the OK/FAIL transitions announce to assistive tech.
Added - Docs reorganization + security.txt (2026-05-20)
Docs/Default.aspx landing page reorganized — the rapidly- growing flat link list (~17 entries) split into six purpose-grouped cards: Quick start (5 links, the canonical CI/automation path), Cross- language clients & API (4), Stack-trace symbolication & runtime defense (3), Recipes (2), Migrate to JSO (2), Procurement & evaluation (5 links to Scorecard / ToolingMatrix / build-integrations / security-and-trust / roadmap). Visitors hitting <code>/Docs/</code> can now find the right entry point in one card scan.- RFC 9116 security.txt at both <code>/.well-known/security.txt</code> (canonical, per spec) and <code>/security.txt</code> (root, for the scanners that only check root). Contact email + form, expiry 2027-05-20, policy link to security-and-trust.aspx, acknowledgments link, explicit in-scope and out-of-scope sections covering the website, the HTTP API, the npm/PyPI/NuGet/RubyGems/Packagist/crates.io/Maven Central packages, and the marketplace IDE plugins. Response targets documented (5 business days to first reply, 90 days to coordinated disclosure). Enterprise vendor-review checklist item. Sitemap entry added.
Added - Health-endpoint doc + Scorecard + Kotlin schema cross-check (2026-05-20)
Docs/HealthEndpoint.aspx — ops-facing doc for the new /v1/health.ashx endpoint. Response shape, HEAD-probe support, deliberate omissions (no SQL probe, no quota state, no rate metrics), recommended Pingdom / BetterStack / UptimeRobot / Datadog Synthetics configurations, "what if the endpoint itself is the problem" runbook note. Links to /roadmap.aspx for the upcoming status page.Docs/Scorecard.aspx — quantified procurement / security-review summary. Big-number tile grid (9 clients, 13 CIs, 8 reporters, 11 build plugins, 2 IDE families, 4 k8s patterns, 240+ tests, 3 verify-chain gates), full supported-runtime matrix with per-language test counts, security-posture bullet list (PBKDF2, MachineKey encryption, ESLint credential-leak rule, polymorphism fingerprint, beacon webhook security), verifiability section linking every claim to a publicly inspectable source. Designed to fit a vendor-evaluation spreadsheet.- Polyglot smoke schema cross-check extended to Kotlin — the new jso-protector-kotlin client now appears in the source-grep array, so a Kotlin Type-literal drift breaks
verify:polyglot just like the other eight clients. Live output: "schema cross-check: OK (9 client(s) reference \"Succeed\"; schema Type enum has 6 values)". Docs/Default.aspx quick-start updated with Scorecard + Health- endpoint links; Clients link now correctly says "Node, Python, Go, .NET, Ruby, PHP, Rust, Java, Kotlin".
Added - Kotlin client + /v1/health endpoint + hero refresh (2026-05-20)
jso-protector-kotlin Kotlin Maven artifact (packages/jso-protector-kotlin, v0.1.0). Idiomatic Kotlin client distinct from the Java one: suspend protect() on Dispatchers.IO, sealed-class ProtectResult with Success / Failure branches (compiler-enforced exhaustive when), data class ProtectRequest, kotlinx-serialization-json (smaller than Jackson). JDK 11+. Removes the "wrap CompletableFuture in runBlocking { }" awkwardness Kotlin shops would otherwise inherit from the Java client. Seven JUnit 5 tests via a custom HttpClient stub. Brings the language-client count to nine./v1/health.ashx endpoint — tiny public unauthenticated health-check returning {ok, service, version, uptimeSeconds, now, docs}. GET/HEAD only; 405 on anything else. Never-cache headers, X-Content-Type-Options: nosniff. Suitable for Pingdom / BetterStack / UptimeRobot. Hand-rolled JSON (no Newtonsoft.Json dependency in the request path for a 6-field document). HEAD requests return the status line only for cheap probes.- Homepage hero refresh — copy updated to advertise nine language clients (added Kotlin), thirteen CI templates (added Argo), plus the ESLint plugin and Slack/Discord beacon forwarder. New "Browse The Tooling Matrix" link in the hero CTA row. Badge row gains a "Hygiene + ops" entry calling out the credential-leak and tamper-alert stories.
Added - Tooling matrix + credential-leak pre-commit + compare-page refresh (2026-05-20)
Docs/ToolingMatrix.aspx — single-page comprehensive table of every shipped artifact, grouped by purpose: 8 language clients (Node, Python, Go, .NET, Ruby, PHP, Rust, Java) + spec-only "anything else", 2 IDE plugins, 11 build-tool plugins, 13 CI templates, 4 Kubernetes- native deployment patterns, symbolicator + 8 reporter integrations, 3 developer-hygiene tools (ESLint plugin + pre-commit hooks + polyglot smoke), runtime defense capabilities + beacon forwarder, 2 migration guides. Status-tagged Shipping / Beta / Reference. Procurement-friendly.jso-credential-leak pre-commit hook added to .pre-commit-hooks.yaml. Runs eslint-plugin-jso-protector against staged JS/TS, auto-fixes hardcoded JSO tokens to process.env. Example .pre-commit-config.yaml updated to recommend enabling it by default for any repo that touches the JSO API.compare-javascript-obfuscators.aspx ecosystem grid extended with three new cards: Credential Hygiene (ESLint plugin), Ops Integration (beacon forwarder + Slack/Discord), Polyglot Reach (eight clients + CI smoke harness). Grid now totals 9 ecosystem-surface cards.Docs/Default.aspx quick-start gains a Tooling-matrix link.
Added - ESLint plugin + Slack/Discord beacon forwarder (2026-05-20)
eslint-plugin-jso-protector (packages/eslint-plugin-jso-protector, v0.1.0). Two rules: no-hardcoded-jso-credentials (error) catches JSO API key literals via base-64-shape detection (length ≥40 + base-64 alphabet + decodes to a <uuid>:<account-id> pattern) and string assignments to credential-named keys (apiKey, APIPwd, JSO_API_KEY, etc.); prefer-env-credentials (warn) flags empty-string credential placeholders. Both auto-fix to process.env.JSO_API_KEY / JSO_API_PASSWORD. Recommended config wired (flat-config + legacy .eslintrc). 3 unit tests pass under ESLint 9's RuleTester (valid + invalid + auto-fix output).jso-beacon-slack (packages/jso-beacon-slack, v0.1.0). Forwards JSO Runtime Defense beacon webhooks to Slack incoming-webhooks and Discord webhooks. Three deployment shapes: forwardBeacon() programmatic API for embedding in your own server, bin/serve.js single-file HTTP server for a dedicated host (with constant-time token-gated /beacon endpoint + /healthz), and examples/lambda.js for AWS Lambda + API Gateway. Slack Block Kit + Discord embeds shapes; partial beacon payloads still produce useful messages. Opaque 200 on token mismatch to deny attackers probe signal; never retries downstream failures so a flaky Slack doesn't cascade. 8 unit tests pass under node --test with fake-fetch injection.- Combined effect: ops teams now have a one-step path from "JSO Runtime Defense beacon" to "Slack message in our #frontend-alerts channel", while ESLint catches credential leaks before they enter source control.
Added - Cookbook + Type-enum cross-check + Docs landing-page sync (2026-05-20)
Docs/Cookbook.aspx — ten copy-pasteable recipes for real customer questions: tag with git tag instead of SHA, protect-only- sensitive-files, blue-green API-key rotation, BuildId runtime injection for crash reports + Sentry release, prerelease channel via separate preset, dry-run gate pattern, beacon routing into Datadog/PagerDuty, polyglot-monorepo iteration, cache-buster via forced new BuildId, and separate retention policies for reports vs protected dists.polyglot-smoke.js schema cross-check — every one of the 8 language clients is now grep-checked for a literal "Succeed" reference, and the JSON Schema's Type enum is validated as non-empty. A drift — new server-side Type added without updating clients, or someone deleting the schema enum — breaks the polyglot harness and therefore verify/prepublishOnly.Docs/Default.aspx quick-start updated to surface the wire-format spec, the JSON Schema, and the new Cookbook in the landing-page link list.
Added - Polyglot smoke verify-chain + Argo + JSON Schema (2026-05-20)
npm run verify:polyglot wired into the jso-protector verify chain and prepublishOnly. A cross-client Name/Items/ReleaseLabel/preset drift now fails before the next CLI release ships. Passes locally (Node + Python + .NET; other runtimes skip gracefully when toolchain absent).- Argo Workflows CI template —
ci/argo-workflows.yaml. Workflow with VolumeClaimTemplate-backed workspace, three-step checkout→build→protect graph, output artifacts. Brings the CI template count to thirteen. verify-templates.js extended to cover it; all 13 still pass under the verify:ci gate. - JSON Schema for the wire format —
Docs/wire-format.schema.json. Draft 2020-12 schema covering ProtectRequest / ProtectResponse / Report / IdentifierMapEntry. Documented in Docs/WireFormat.aspx with a quicktype invocation example for generating typed clients in any language we don't already ship. Sitemap + csproj wired.
Added - Polyglot smoke harness + k8s/Helm examples + hero refresh (2026-05-20)
packages/polyglot-smoke/smoke.js — cross-language smoke harness. Spins up a single mock JSO server, round-trips a fixture against every available language client (Node, Python, .NET, Go, Ruby, PHP, Rust, Java, curl — whichever toolchains are on PATH), captures each outgoing request body, and asserts every required field is identical across runtimes. Caught a real per-language <code>Name</code> default mismatch on its first run; the harness is now the canonical gate against that class of cross-client drift. 3/3 PASS locally (Node + Python + .NET).packages/jso-protector/examples/kubernetes/job.yaml — standalone Kubernetes Job manifest with companion Secret, init-container git clone, npm-based protect step, configurable resource requests. kubectl-apply-able without any chart framework.packages/jso-protector/examples/helm/ — full Helm chart with conditional Job-vs-CronJob switch via <code>cron.schedule</code>, <code>existingSecret</code> support for production credential management, override hooks for air-gapped registries.- Homepage hero updated to advertise the ecosystem reality: "8 language clients, 12 CI templates, 2 IDE plugins, 8 error reporters". The hero badge row rewritten to enumerate the eight clients and twelve CI systems by name.
Added - Java client + wire-format spec + curl reference + roadmap sync (2026-05-20)
jso-protector Java Maven artifact (packages/jso-protector-java, v0.1.0). JDK 11+. Uses the built-in java.net.http.HttpClient — no third-party HTTP dependency. Jackson for JSON. ProtectOptions.builder() fluent API, constructor-injectable HttpClient for proxies / TLS pinning / tests. Seven JUnit 5 tests via a custom HttpClient stub.Docs/WireFormat.aspx new protocol-level specification of the HTTP API: request shape, preset constants, success/failure response shapes, Type/ErrorCode taxonomy, recommended headers, a 25-line bash + curl + jq reference implementation inline. Lets anyone in any language write a JSO client without cribbing from a shipped one.packages/jso-protector/examples/curl/ — runnable protect.sh shell script + README. Multiple input/output pairs, three presets via JSO_PRESET, label/endpoint env overrides, BuildId + fingerprint reported on stdout. Smoke-checked with bash -n.roadmap.aspx synced with reality. CI templates moved from "six systems" to twelve; the six non-Node language clients, pre-commit hooks, eight error-reporter integrations, two migration guides, and the build-integrations / symbolicate-demo pages all listed in the Shipped section.Docs/Clients.aspx matrix extended to seven languages + a "Anything else — wire format spec" row pointing to the new doc.build-integrations.aspx "Non-Node language clients" grid extended to eight cards: Python, Go, .NET, Ruby, PHP, Rust, Java, and a wire format spec card for everything else.
A coordinated set of fixes across credentials handling, paywall enforcement,
obfuscator scope correctness, payment integrations, compatibility analyzer
coverage, and marketing-page polish. Every change in this entry is
backward-compatible with existing data — credentials auto-upgrade on first
use, paywall gates are tightened (legitimate paid usage unaffected), and
the new obfuscator pre-passes are conservative on edge cases they can't
prove correct.
Added - CI integration templates (2026-05-19)
- **CircleCI, Jenkinsfile, Bitbucket Pipelines, Drone CI, Buildkite, Woodpecker** templates added under
packages/jso-protector/ci/. Same conventions as the existing GitHub Actions / GitLab CI / Azure Pipelines templates: tag every protection run with the commit SHA via --label, write the API report via --report, upload as a build artifact for later symbolication. Nine total CI systems covered. - Existing
github-actions.yml, gitlab-ci.yml, azure-pipelines.yml templates updated to use the new --label and --report flags. ci/verify-templates.js added — smoke test that checks every shipped template exists, has no YAML indentation tabs, calls jso-protector with both --label and --report (so audit / symbolication conventions stay uniform), and (for Jenkinsfile) has balanced braces and a top-level pipeline { ... } block. Wired into npm run verify and prepublishOnly so a broken template can never ship.
Improved - Docs to match new CLI flags (2026-05-19)
Docs/NpmCli.aspx gains three new sections: "Tag every build with a release label" (--label + JSO_LABEL env var + all six CI variable defaults), "Archive the API report for later symbolication" (--report, full report shape, jso-symbolicate invocation, link to reporter integrations), and "Drop-in CI templates" (six-template table).FAQs.aspx gains six new Q&As covering CI automation, stack-trace symbolication, the BuildId/PolymorphismFingerprint pair, IDE plugins, migration paths, and Runtime Defense beacon webhook routing. Both the rendered Q&A list and the JSON-LD schema are updated.
Added - Error-reporter integrations (2026-05-19)
- Airbrake integration in
jso-symbolicate/airbrake — createAirbrakeFilter(lookup) returns a callback for airbrake.addFilter(...). Demangles errors[].type, errors[].message, every backtrace[].function, and the inline code source-context object. Always returns the notice (never null) so symbolication errors cannot drop a report. - AppSignal integration in
jso-symbolicate/appsignal — createAppSignalDecorator(lookup) returns a decorator for appsignal.addDecorator(...). Demangles action, namespace, error.name, error.message, backtrace strings, and tag string values. Pushes data back via setError/setAction/setTags when the span exposes those setters. - Symbolicate now covers eight reporters: Sentry, Bugsnag, Rollbar, Datadog, Honeybadger, Raygun, Airbrake, AppSignal. 15 unit tests assert field rewrites + safe null-lookup + swallowed-transform-error behavior across all of them.
- Honeybadger integration in
jso-symbolicate/honeybadger — createHoneybadgerBeforeNotify(lookup) returns a callback for Honeybadger.configure({ beforeNotify }). Demangles message, name, class, and backtrace[] entries (handles both raw-string and structured-array backtrace shapes). - Raygun integration in
jso-symbolicate/raygun — createRaygunBeforeSend(lookup) returns a callback for rg4js("onBeforeSend", ...). Demangles error.Message, error.ClassName, error.StackString, and every StackTrace[] frame — covers both Raygun4JS and Raygun4Node payload shapes. - Rollbar integration in
jso-symbolicate/rollbar — createRollbarTransform(lookup) returns a Rollbar transform callback that demangles payload.data.body.trace, trace_chain[], message body, and telemetry breadcrumbs in-place. - Datadog Browser SDK integration in
jso-symbolicate/datadog — createDatadogBeforeSend(lookup) returns a callback for datadogRum.init({ beforeSend }) and datadogLogs.init({ beforeSend }). Demangles error.message, error.type, error.stack, action names, and Log message bodies. - Symbolicate now covers Sentry, Bugsnag, Rollbar, Datadog, Honeybadger, and Raygun out of the box. TypeScript declarations included.
Added - Migration & integration pages (2026-05-19)
Docs/JscramblerMigration.aspx — opinionated one-page migration guide for Jscrambler Code Integrity customers. Feature mapping table, pricing comparison, honest gap list (PCI v4 third-party tag attestation, hosted threat-monitoring dashboard, mobile-specific telemetry), and a 9-step migration checklist. Linked from sitemap and Docs/Default.aspx.build-integrations.aspx — one landing page that surfaces every build/IDE/CI integration: VS Code, JetBrains, jso-protector CLI, 8 bundler plugins, 6 CI templates, 8 error-reporter wires, plus quick links to both migration guides and the interactive symbolicate demo. Linked from the homepage hero, sitemap, and roadmap.roadmap.aspx — JetBrains plugin and CI templates moved from "Coming" to "Shipped" (both have actually shipped). Error-reporter list updated to reflect the full 8-reporter set.compare-javascript-obfuscators.aspx — IDE Integration card expanded to call out both VS Code and JetBrains.
Improved - Documentation (2026-05-19)
Docs/NpmMigration.aspx gains a "What JSO adds on top of the OSS package" section comparing JSO vs javascript-obfuscator (npm OSS) across polymorphism, VM beta, Runtime Defense, compatibility analyzer, BuildId audit metadata, local symbolication, marketplace integrations, and hosted credential/audit history.
Added - PHP + Rust client libraries + unified clients doc (2026-05-20)
javascriptobfuscator/jso-protector PHP Composer package (packages/jso-protector-php, v0.1.0). PHP ≥7.4 with typed properties. ext-curl when present, stream-context fallback for installs without curl. Custom transport callable for tests. JsoProtector\Client + Result + Exception namespaced. Laravel/Symfony-friendly. Eight PHPUnit tests written.jso-protector Rust crate (packages/jso-protector-rust, v0.1.0). Sync HTTP via ureq (no async runtime), serde_json for JSON, thiserror-derived typed Error enum. Four unit tests in src/lib.rs plus three mockito-backed integration tests.Docs/Clients.aspx new doc page — one matrix table covering all seven language clients (Node, Python, Go, .NET, Ruby, PHP, Rust) with install command, min version, transport choice, and locally-verified test status. Includes "same protect() in seven languages" side-by-side code examples. Linked from Docs/Default.aspx, the build-integrations hub, and the sitemap.build-integrations.aspx "Non-Node language clients" grid extended to seven entries plus a Docs/Clients.aspx link card.
Added - .NET + Ruby client libraries (2026-05-19)
JsoProtector .NET NuGet package (packages/jso-protector-dotnet, v0.1.0). Targets .NET Standard 2.0 — ships across .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5/6/7/8+, Mono, Unity, Xamarin. HttpClient-injectable for IHttpClientFactory + Polly retry; uses System.Text.Json. ProtectOptions + OptionOverrides Dictionary-based flexibility. Built clean under TreatWarningsAsErrors=true and Nullable=enable. 8/8 xUnit tests pass on net6.0: label propagation, options override, env fallback, missing creds, unknown preset, empty files, non-Succeed Type/ErrorCode mapping, preset table integrity.jso_protector Ruby gem (packages/jso-protector-ruby, v0.1.0). Pure stdlib (net/http + json), no third-party dep. Keyword-argument API matching the JSO conventions. JsoProtector::Result / JsoProtector::Error with type + error_code. Eight Minitest tests written; Ruby toolchain not installed locally so tests remain to be exercised on first Ruby CI run.build-integrations.aspx "Non-Node language clients" grid now lists five clients (Node, Python, Go, .NET, Ruby) + pre-commit hooks.- Five language clients in total means JSO's ecosystem now hits .NET, Python, Ruby, Go, and Node shops with the exact same
protect() shape — no longer a Node-only product.
Added - Go client library + pre-commit hooks (2026-05-19)
jso-protector-go Go module (packages/jso-protector-go, v0.1.0). Pure stdlib (net/http + encoding/json), no third-party deps. Same Protect() surface and three-preset table as the Node and Python clients. Env-var-first credentials (JSO_API_KEY / JSO_API_PASSWORD). Typed *Error with Type / ErrorCode fields; messages are safe to log (API key / password never interpolated). Eight httptest-based tests written; Go toolchain not installed in this session so tests remain to be exercised on first CI run.- pre-commit hooks (
packages/jso-protector/.pre-commit-hooks.yaml). Two hooks: jso-release-check (catches config drift before commit) and jso-dry-run (catches "I added a new vendor file nobody intended to protect" mistakes). Deliberately do NOT POST source on every commit — pre-commit is the wrong place for the full protection round-trip; CI is. Example .pre-commit-config.yaml shipped under packages/jso-protector/examples/pre-commit/. build-integrations.aspx updated to surface the 12 CI templates (was 6), plus a new "Non-Node language clients" section linking the Python client, the Go client, and the pre-commit hooks.
Added - Python client library (2026-05-19)
jso-protector Python package (packages/jso-protector-python, v0.1.0). Pure-stdlib client — uses urllib + json, no third-party dependencies. Mirrors the protect() surface of the npm CLI so behavior stays in lockstep across runtimes. Returns a ProtectResult dataclass with files, build_id, polymorphism_fingerprint, report, raw. Three presets + options-override pattern, env-var-first credentials (JSO_API_KEY / JSO_API_PASSWORD), ProtectError with safe-to-log messages (API key / password never interpolated). Eight unit tests passing.- Opens up Python-shop integration: teams whose build pipeline is Python (Bazel, scripted releases, ML deploy systems) can now run JSO without bringing Node into the build environment.
Added - More CI templates (2026-05-19)
- Tekton Pipelines (
ci/tekton.yaml) — Task + Pipeline manifests for Kubernetes-native CI. Credentials wired through a jso-credentials Secret. - TeamCity Kotlin DSL (
ci/teamcity.kts) — .teamcity/settings.kts with a ProtectedRelease build type. Credentials as password parameters. - GoCD (
ci/gocd.yaml) — .gocd.yaml with build + protect stages, secure_variables for credentials, artifact publishing. - CI templates now cover twelve systems: GitHub Actions, GitLab CI, CircleCI, Jenkinsfile, Azure Pipelines, Bitbucket Pipelines, Drone CI, Buildkite, Woodpecker, Tekton, TeamCity, GoCD.
ci/verify-templates.js extended to handle .yaml and .kts — Kotlin brace balance + BuildType object presence required, same --label and --report invariants required across all 12 templates.
Added - Interactive symbolicate demo (2026-05-19)
symbolicate-demo.aspx — client-side demangler page. Paste a JSO identifier-map JSON and a stack trace, click Demangle, see the result. The substitution runs entirely in the browser (no network call); same algorithm as the jso-symbolicate npm package, inlined so the page has no runtime dependency. Supports replace mode, annotate mode, sample data, and detects wrong-map-vs-trace pairings (zero hits with non-empty map). Linked from Docs/Symbolication.aspx and the sitemap.
Added - Test coverage (2026-05-19)
- 13 new tests for
jso-symbolicate reporter integrations. Every Sentry/Bugsnag/Rollbar/Datadog/Honeybadger/Raygun helper now has at least one test asserting field rewrites in a representative event, plus cross-reporter tests for safe null-lookup behavior and swallowed transform errors (Proxy-based forced failure). Tests live at packages/jso-symbolicate/test/reporters.test.js; npm test wired up.
Added - Marketing surface (2026-05-19)
- Public Roadmap page (
roadmap.aspx). Categorises every roadmap item as Shipped / Beta / Next / Research, plus a deliberate "things we won't do" section so procurement can read the strategic stance directly. Linked from the homepage hero and the sitemap. - Per-transform diff page (
transforms-side-by-side.aspx). Real before/after JavaScript for seven representative transforms (name mangling, string array, encryption, control-flow flattening, dead-code insertion, member rename, self-defending). Closes the marketing-demo gap versus JShaman.
Added - Ecosystem packages (2026-05-19)
- jso-symbolicate Sentry / Bugsnag integrations (0.2.0). New subpath exports
jso-symbolicate/sentry and jso-symbolicate/bugsnag ship Event Processor / onError callbacks that demangle Sentry and Bugsnag events in-place before they leave the customer's runtime. No source-map upload, no Sentry artifact management; the demangling lookup stays on the customer machine. TypeScript declarations included.
- VS Code extension (
packages/vscode-extension, 0.1.0). Marketplace-ready extension exposing JSO: Obfuscate Current File / JSO: Obfuscate Selection commands, with editor and explorer context-menu integration for JS/TS files, three protection presets, and credentials read from JSO_API_KEY / JSO_API_PASSWORD env vars before falling back to settings. Closes the JShaman / PreEmptive parity gap on IDE integration. - JetBrains IDE plugin scaffold (
packages/jetbrains-plugin, 0.1.0). Kotlin + Gradle, marketplace-ready. Targets IntelliJ Platform 2024.1 and ships in WebStorm, IDEA Ultimate, PhpStorm, PyCharm Professional, RubyMine, GoLand, and Rider (build 232+). Right-click <em>Obfuscate File with JSO</em> and <em>Obfuscate Selection with JSO</em> on JS/TS files, three presets, env-var-first credentials, dashboard quick link from Tools menu. Same HTTP wire format as jso-protector and the VS Code extension so behavior stays in lockstep across editors. Uses HttpURLConnection so the plugin has no okhttp / retrofit Maven dependency. jso-symbolicate npm package (packages/jso-symbolicate, 0.1.0). Local demangler that turns obfuscated stack traces back into readable ones using the identifier map from the API report. Pure JS, zero deps, no network calls — your traces and your map never leave the machine. Accepts five map source shapes (full report JSON, bare report, array form, object form, block text). Replace, annotate, and JSON-stats modes. Detects wrong-map cases (zero hits over a non-empty map = mismatched build). Companion documentation at Docs/Symbolication.aspx.- GitHub Action wrapper (
packages/jso-github-action, 0.1.0). Composite action.yml that runs jso-protector in any GitHub workflow, auto-tags the build with ${{ github.sha }}, surfaces build-id and polymorphism-fingerprint as step outputs, and writes the API report for later symbolication. Pinnable cli-version for reproducible builds.
Added - API surface
Report.BuildId and Report.PolymorphismFingerprint on JSOHttpReport. The former is a stable build identifier (aliased from the existing ReleaseID); the latter is a SHA-256-derived 16-hex-char fingerprint over the concatenated output SHA-256s, so auditors can prove build-to-build divergence with a single short token. Two consecutive obfuscations of identical input must produce different fingerprints when polymorphism is engaged; identical fingerprints are now an observable regression signal.
Security (2026-05-19)
- Admin password supports PBKDF2 hash instead of plaintext.
Web.config now reads JSOAdminPasswordHash (preferred, format PBKDF2-SHA256$<iter>$<salt-b64>$<hash-b64> from PasswordProtector.Hash) before falling back to the legacy plaintext JSOAdminPassword. The cookie binding hash uses constant-time comparison; TryLogin uses PasswordProtector.Verify (also constant-time) when the hashed form is configured. Plaintext-fallback comparison upgraded to constant-time too. Rotation playbook documented inline in Web.config. Existing deployments with JSOAdminPassword=... keep working without change.
Fixed - Obfuscator correctness (2026-05-19)
- Optional chaining preserved across member-access rewrite.
ProtectorV2.Names.cs::MoveMembers was lowering obj?.member to obj?[member] (invalid JavaScript) by failing to propagate the ?. postfix when collapsing CodeMemberAccessExpression into CodeBracketCallExpression. Customer-reported via a re-parse failure on the obfuscated output: "Unexcepted Token : Semicolon, Requires Colon" at context _0x21F8A[0]?[_$_a46c[235]]. Fix: detect a CodeUnaryOperatorExpression("?", InFront=false) LHS and set bce.IsDotCall = true so the writer emits obj?.[member]. - ES6 transpiler brace-counting false positives.
ProtectorV2.ES6.cs::ES6CountBraceBalance counted { and } literally inside Other token text — string literals and comments containing braces caused legitimate Class transpilation passes to be reverted with "ES6 pass 'Class' broke brace balance" log lines. Fixed by routing Other token text through a new ES6CountBraceBalanceInRawText state machine that skips chars inside //, / /, and single/double/backtick string literals before counting.
Added - Documentation
- Runtime Defense documentation added. New
Docs/RuntimeDefense.aspx documents the already-supported active runtime controls that were previously hard to discover: callbacks, beacon alerts, debug protection, console suppression, self-defending integrity checks, session locks, fingerprint allow-lists, challenge locks, headless detection, and signed release envelopes. The docs navigation, sitemap, npm options page, and comparison page now point to this capability so the public site no longer undersells runtime alert/lock coverage versus commercial competitors.
Security
- User passwords migrated from plaintext to PBKDF2-SHA256. New
App_Code/PasswordProtector.cs hashes with 120k iterations (OWASP 2026 floor), 16-byte random salt, 32-byte hash, format PBKDF2-SHA256$<iter>$<salt-b64>$<hash-b64>. signin.aspx, register.aspx, dashboard/profile.aspx, and recovery-verification.aspx all wired through the new protector. Existing accounts keep working via a legacy plaintext fallback in Verify() and auto-upgrade on first successful login. Empty-stored-password authentication closed (Verify fails fast on either IsNullOrEmpty(stored) or IsNullOrEmpty(supplied)). - API-key passwords migrated from plaintext to MachineKey-encrypted-at-rest. New
App_Code/ApiKeyPasswordProtector.cs uses MachineKey.Protect/Unprotect (AES-CBC + HMAC under the hood, built-in ASP.NET 4.5+). Format ENC$MK$<base64>. Same auto-upgrade story — first successful API call rewrites the row to encrypted form. Six call sites (HttpModel.cs verify, JSOService.cs ×3 demo & handshake, SiteUtility.cs credential download, dashboard/APIKey.aspx display) now decrypt-on-read. JOBDB_Partials.cs::CreateApiKey and APIKey.ResetPassword encrypt on write. Verification path takes microseconds (AES decrypt) — no per-API-call latency hit unlike PBKDF2. - Admin Account page no longer displays passwords.
jsomanage/Account.aspx previously rendered acc.Password as a literal string in the admin pane. Replaced with a format indicator ("PBKDF2-SHA256 hash" / "legacy plaintext (will upgrade on next login)") so admins can audit migration progress without exposing the credential. - Plan-tier paywall bypasses closed. Three places in
App_Code/Plans.cs::Validate() had gates that didn't match the protector's actual code paths, letting Free-tier requests run paid features: - MixedServer (Enterprise-only) — was never gated. Now gated. - AddDeadCode (Corporate-only) — was only gated when ReorderCode=true even though ProtectorV2.cs:394 reads AddDeadCode unconditionally. Gate hoisted out of the ReorderCode conditional. - RenameGlobals (Corporate-only) — was only gated when ReplaceNames=true even though ProtectorV2.cs:399 reads it unconditionally. Gate hoisted out of the ReplaceNames conditional.
Fixed — Obfuscator correctness
Customer report on 2026-05-04:
function test(type) {
const result = { success: true }
switch (type) {
default: {
const result = 123
console.log('inner:', result)
}
}
console.log('outer:', result) // expected the object, got 123
}
The renamer tracked identifiers at function-body scope only, so the inner
block-scoped result collided with the outer one and both ended up
mapped to the same short name. Fixed by a new pre-pass
App_Code/BlockScopeDisambiguator.cs that runs after parsing and before
name analysis, renaming block-scoped let/const declarations to
globally-unique placeholders and rewriting references within the scope.
Across follow-on loops the pre-pass grew to cover four AST shapes with
shadow-aware closure rewriting:
- Braced blocks (
{ ... } / CodeBlockStatement) — direct-child let/const declarations renamed; references rewritten within the block and any nested blocks that don't shadow. for (let X = 0; …; …) heads (CodeForStatement) — declarations scoped to Initialize, Condition, Finally, and Block.for (let X of/in …) heads (CodeForInOfStatement) — declarations scoped to Block only; Expression (the iterable) is outer-scope.- **
switch (cond) { case A: let X = …; … } shared scope (CodeSwitchStatement)** — case-level let/const declarations share the switch body's lexical scope across all cases; rewritten across every case's Block (but never the switch's Condition). - Closure-aware function-expression descent. When a disambiguated block contains a function expression that doesn't itself declare the renamed name (parameter, function-name binding, hoisted
var X, or top-level let/const X at the function-body level), the rewriter walks into the function body so closure references to the renamed declaration follow correctly. - Class-expression handling.
class Foo extends BaseName { … } — BaseName always traversed (it evaluates in the outer scope), Body traversed unless the class's own Name shadows the renamed name. Methods inside the body get the same per-CodeFunctionExpression shadow analysis. - Top-level identity guard. When a caller passes a leaf
CodeIdentityExpression directly (a class BaseName that is just an identifier, a for-loop Condition that is just an identifier), the rewrite is now applied to the leaf itself — previously the loop inspected only children and the leaf was silently missed.
Fixed — Compatibility analyzer
- **
export default function X() / export default class X now produce a module-esm-contracts finding.** The EsModuleNamedExportRegex required a binding keyword (const/let/var/function/class) immediately after export, which misses the default-export form where default sits between export and function/class. New EsModuleDefaultExportRegex matches \bexport\s+default\s+(?:async\s+)?(?:function\s\?|class)\s+(?<name>…) and is wired into ExtractEsModuleExports. Customers using named default exports now see the same stable-name suggestion that named non-default exports get. - Dead code removed.
warningSet (declared in AnalyzeProjectDetailed but never written) and the unused EsModuleExportRegex deleted.
Fixed — PayPal payment integration
The PayPal button on dashboard/buynow.aspx was charging customers
$0.01 for every plan regardless of selection. Root cause: the JS
SDK used PayPal v2 API (createOrder / onApprove) but our server
was creating v1 Payment objects and returning a PAY-xxx token —
which the v2 SDK refused to interpret as an order ID, so it fell
back to its own minimal $0.01 default transaction.
- Server migrated to PayPal v2 Orders API. New helper methods in
App_Code/PaypalRest.cs: V2CreateOrder (with item-level breakdown so the plan name and SKU appear on the buyer's receipt + merchant dashboard), V2GetOrder, V2PatchOrderAddTax, V2CaptureOrder. Uses HttpWebRequest directly so no new assembly references are required in Web.config. - OAuth token now minted from the same environment as the v2 endpoint. Previously the v1 SDK's
OAuthTokenCredential read its sandbox/live mode from <paypal><settings> in Web.config, which could drift from PaypalRestSandbox in <appSettings> and produce invalid_token / Token signature verification failed when a sandbox token was sent to the live API. New GetV2AccessToken() hits {V2BaseUrl}/v1/oauth2/token directly with cached expiry. - Canadian HST patched as a separate
tax_total line in the v2 PATCH amount block, instead of overwriting the order amount. The item total and breakdown stay consistent. - JSON error responses from
ProcessAction(). Previously a server exception fell into the customErrors 404.html rewrite, which the PayPal SDK fetch tried to parse as JSON ("Unexpected token '<'"). ProcessAction() now catches non-thread-abort exceptions, writes { "error": "...", "type": "...", "details": "..." }, and the JS createOrder handler surfaces the actual server error in alert() + console.error() for fast debugging. paypal-complete.aspx calls acc.UpdateOrderStatus() defensively so the plan upgrade is visible on the success page even when the user lands via refresh/back/direct-link instead of the normal paypal-review.aspx → capture path.- Client-side migration to v2 button callbacks —
createPayment → createOrder, onAuthorize → onApprove, data.paymentToken → data.orderID.
Fixed — Stripe / Alipay
- **Legacy Stripe Sources API for Alipay was deprecated by Stripe in 2022.** Removed the old
InitOrder_Alipay server endpoint and client PayWithAlipay() from dashboard/buynow.aspx, then rebuilt on Stripe PaymentIntents: - New App_Code/StripeRest.cs (CreatePaymentIntent_Alipay, GetPaymentIntent) with proper error-detail propagation (throw new Exception(...) without an inner WebException so JSOv2Ajax surfaces the Stripe response body rather than the generic "(400) Bad Request"). - Server CreatePaymentIntent_Alipay AJAX method creates a PI restricted to payment_method_types=['alipay'], saves a pending StripePayment row with StripeSource=pi_xxx, returns { ClientSecret, PaymentIntentId, PaymentId }. - Client calls stripe.confirmAlipayPayment(clientSecret, { return_url }). - dashboard/stripe-cb-alipay.aspx rewritten to look up the StripePayment row, server-side-verify the PaymentIntent status via the Stripe API (never trusts redirect_status from the URL), and call the existing DoCheckout() if status === "succeeded". Idempotent against refresh/re-entry. - Currency restriction handled. Stripe Alipay on a Canadian-registered merchant only accepts
cny or cad. USD plan price is converted to CNY at a fixed 7.30 rate in CreatePaymentIntent_Alipay; the OrderInfo* fields stay in USD so the JSODB.Order plan-tier logic is unaffected. Receipt shows both amounts ("USD 49.00 billed as CNY 357.70").
Added — UI surfaces
- Top announcement bar above the home-page hero highlighting "Anti-LLM Maximum mode in every paid tier — per-build polymorphic decoding from $29/mo" with an anchor to
#pricing. - Sticky mobile CTA on the home page (visible only
<640px), reserving 76px on the right so it doesn't sit on top of the embedded MyLiveChat widget. - Visible FAQ accordion on the home page mirroring the JSON-LD schema + two new questions (CI/CD integration, runtime performance). Pure CSS
<details>/<summary>, no JS. - "From the engineering team" research strip above the FAQ surfacing the three substantive blog posts (vendor comparison, VM roadmap, can-AI-reverse-engineer) with topic-color accent stripes — competitor-parity move matching Jscrambler / JSDefender / Verimatrix research surfacing.
- Docs landing page gained a role-based quick-path panel (Evaluating / Security review / CI wiring) above the existing topic-organized cards.
- Blog landing page restructured: featured article hero (gradient + glow + star badge + animated arrow), 2-column secondary grid with topic-color (amber/indigo/emerald) left accent stripes.
- Tour page rewritten from a thin 71-line marketing stub to a substantive product tour: 5 protection layers, before/after code preview, 4-surface comparison, dark gradient final CTA.
- Anchor images on 8 marketing pages (
tour.aspx, aboutus.aspx, runtime-defense.aspx, security-and-trust.aspx, modern-javascript-protection.aspx, vm-bytecode-obfuscation.aspx, javascript-build-integration.aspx, javascript-obfuscator-vs-npm-package.aspx) with consistent treatment: 21:9 cinematic frame, rounded 1.6rem, sky-blue accent border, bottom gradient mask for caption legibility, proper alt text, <figure>/<figcaption> semantics, loading="lazy" + decoding="async".
Changed — UI rewrites
aboutus.aspx — full rewrite. Fixed broken h2→h4→h5 heading hierarchy, removed dead stickyBlockEndPoint JS-hook leftovers, consolidated redundant images, replaced inconsistent font weights with the site's design tokens. Added a stat strip, principles grid, milestone timeline (2004 / 2010s / 2024 / 2026), and a "Where we work" Toronto/Canada visual panel.contactus.aspx — full rewrite. Server-side ASP.NET controls preserved verbatim. Address corrected from "198 Kennedy St West" to "201 Consumers Rd" matching the rest of the site and the JSON-LD on home. Added a quick-contact tile strip (Support / Sales / General) and a modern form panel.downloads.aspx — removed the WinUI 3 v3.1.1 launch-crash troubleshooting section after the desktop-app pre-init bug was fixed in JSOWin3 release.
Removed — UI redundancy
The home page had been collecting near-duplicate sections. Total
removed across this session: ~140 lines of redundant content while
preserving all unique value.
- "Buyer's Question" section (~70 lines) — said the same thing as the "Evaluation Review Block" further down with the same blog/compare links. The Evaluation Review version is stronger because it shows a concrete code preview.
- In-hero stats row — duplicated the trust ribbon's four facts (
2004 / $29 = 1GB / Per-build / 4 paths). hero-feature-list under the code window — explained the same protection layers the code preview already shows.home-hero-note (3 chips: "Online preview", "Desktop app", "API + npm CLI") — covered by the stats row below.- Premium-membership "Pricing Overview" panel — pre-summarized the actual pricing cards directly below with four micro-cards. Cut so visitors hit the actual plans immediately after the hero.
- Awkwardly-long Section 6 headline on
premium-membership.aspx ("Operationally simple for teams that need repeatable protection, not a procurement-heavy rollout") → "Repeatable protection, no procurement-heavy rollout". - Meta-marketing copy on home page social-proof band caption and Evaluation Review block trailing "— and that's the point" writer's shrug.
Fixed — Marketing-page data integrity
privacy.aspx and terms.aspx — hero copy was placeholder text leaked from a different page ("Batch processing. Protect JavaScript source code that's embedded in HTML, PHP, ASP, ASPX, JSP and similar files"). Replaced with proper Legal / Privacy Policy and Legal / Terms of Service heroes.clients.aspx — "Industry breadth since 2008" → "since 2004" to match the rest of the site and the JSON-LD organization founding date.protect-javascript.aspx — six feature-card headings promoted from <h4> to <h3> so they sit one level below the section <h2> instead of skipping a level.Web.config <compilation> reverted to the original self-closing form after a brief detour that added a <system.codedom> entry — caused other pages with C# 7 syntax to fail compile on the runtime compiler. App_Code/PaypalRest.cs was rewritten to use HttpWebRequest (in System.Net, auto-referenced) so no System.Net.Http assembly entry is needed.
Fixed — Compile/build hygiene
dashboard/Orders.aspx — if (!int.TryParse(strpage, out int parsedPage) || …) used C# 7 inline out syntax, which is not supported by the default ASP.NET runtime compiler. Rewritten to the C# 5-compatible form (int parsedPage; if (!int.TryParse(strpage, out parsedPage) || …)).Global.asax CSP script-src extended to include https://static.cloudflareinsights.com so Cloudflare Web Analytics' auto-injected beacon stops being blocked. connect-src similarly extended for the metric POST-back endpoints.JSO-Website.csproj updated to include the new App_Code files (PasswordProtector.cs, ApiKeyPasswordProtector.cs, StripeRest.cs, BlockScopeDisambiguator.cs) under explicit <Compile Include="…"/> entries — required for the Web Application Project build path to pick them up.
Notes on backward compatibility
- Existing user-password rows continue to authenticate; first successful login transparently rewrites to
PBKDF2-SHA256. - Existing API-key rows continue to authenticate; first successful API call transparently rewrites to
MachineKey-encrypted form. - Plan-tier gate fixes only affect requests that were previously bypassing — any legitimate paid usage at the correct tier is unchanged.
- Obfuscator pre-passes are conservative: destructuring
const {a, b} declarations and pathological scope mixes are skipped rather than rewritten incorrectly. Customer-reported repro is fixed; no pre-existing protected output is invalidated. - Marketing-page changes don't touch master pages, shared assets, or persistent URLs — every change is scoped to a single page or per-page CSS block.
Files added
App_Code/PasswordProtector.csApp_Code/ApiKeyPasswordProtector.csApp_Code/StripeRest.csApp_Code/BlockScopeDisambiguator.csCHANGELOG.md (this file)