From 262616936d237d1e4f1f75c2fbb998b6e55f9857 Mon Sep 17 00:00:00 2001 From: Kenneth Hough Date: Sat, 30 May 2026 03:45:51 -0500 Subject: [PATCH] =?UTF-8?q?Drop=20JS=20obfuscation=20(Phase=201,=20behavio?= =?UTF-8?q?ral)=20=E2=80=94=20resolves=20#188,=20#191=20(Phase=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop producing/serving obfuscated JS without any schema change. Obfuscated columns are left inert and dropped later (Phase 2, expand/contract). - Publish always serves plain source: KytePageController::buildJavaScript() (page JS, kyte_connect block, header/footer) and KyteScriptController::handleScriptPublication() ignore the obfuscate_js / obfuscate_kyte_connect flags. Lossless — plain source was always stored. - Fix #188 version-bloat: remove javascript_obfuscated / content_js_obfuscated from the content-field sets and obfuscate_js from the metadata sets in detectChanges/addChangedFieldsToVersion (KytePage) and detectScriptChanges/addChangedFieldsToScriptVersion (KyteScript). A publish of unchanged source no longer spawns a content-identical version. - Application.obfuscate_kyte_connect now defaults to 0 for new apps. No migration. Storage hooks still accept the *_obfuscated columns, so the change is forward/backward compatible with any Shipyard version during rollout. PHPStan clean; full phpunit runs in CI (MariaDB). Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 18 ++++++++++ src/Mvc/Controller/KytePageController.php | 39 +++++++-------------- src/Mvc/Controller/KyteScriptController.php | 11 +++--- src/Mvc/Model/Application.php | 3 +- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e93ca37d..4da98d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 4.7.0 + +### Change: drop JavaScript obfuscation (Phase 1 — behavioral; resolves KYTE-#188, KYTE-#191 Phase 1) + +JS obfuscation has been forced on every Kyte install since v1. It provides **no real security** (client JS runs in the browser and obfuscation is trivially reversible; it is not a recognized control under any compliance framework), it **bloats storage** (each obfuscated field is a stored duplicate of the source), and it has caused **operational breakage** — a WAF/firewall once blocked legitimate obfuscated JS. No customer ever asked for it. + +It also drove a **version-bloat bug (KYTE-#188)**: re-obfuscation is non-deterministic, so the publish/save path saw the obfuscated bytes change on every publish even when the human-authored source was byte-identical, spawning a content-identical `KytePageVersion`/`KytePageVersionContent` each time. + +This release stops obfuscation **behaviorally**, without a schema change. The obfuscated columns are left in place (inert) and will be dropped in a follow-up (Phase 2) once this is confirmed stable across installs — expand/contract so a code rollback never strands a dropped column. + +1. **Publish always serves plain source.** `KytePageController::buildJavaScript()` (page JS, the `kyte_connect` block, and header/footer JS via `buildHeaderFooterJS()`) and `KyteScriptController::handleScriptPublication()` now emit the plain `javascript` / `content` / `kyte_connect` regardless of the `obfuscate_js` / `obfuscate_kyte_connect` flags. The plain source has always been stored alongside the obfuscated copy, so this is **lossless**; existing pages de-obfuscate on their next publish. + +2. **Obfuscation removed from change-detection (the KYTE-#188 fix).** `detectChanges()`/`addChangedFieldsToVersion()` (KytePage) and `detectScriptChanges()`/`addChangedFieldsToScriptVersion()` (KyteScript) no longer include `javascript_obfuscated`/`content_js_obfuscated` in their content-field sets or `obfuscate_js` in their metadata sets. A publish of unchanged source no longer spawns a spurious version. (`generateContentHash()` already excluded the obfuscated field.) + +3. **New-app default.** `Application.obfuscate_kyte_connect` now defaults to `0` (was `1`). Moot behaviorally since publish ignores the flag, but keeps new rows honest. + +**No migration. No DB change. Backward/forward compatible during rollout:** the storage hooks still accept the `*_obfuscated` columns, so an older Shipyard that still sends an obfuscated blob keeps working (kyte-php just ignores it at publish), and a newer Shipyard that sends an empty obfuscated value + `obfuscate_js=0` also works. Phase 2 (a later release) removes the model fields, the remaining `bz*`/decompress handling, and drops the columns to reclaim the stored-duplicate bloat. + ## 4.6.1 ### Bug Fix (regression from 4.6.0): large MCP tool responses corrupt the session → `read_page` (and other large reads) fail diff --git a/src/Mvc/Controller/KytePageController.php b/src/Mvc/Controller/KytePageController.php index b1297bd1..c4a47427 100644 --- a/src/Mvc/Controller/KytePageController.php +++ b/src/Mvc/Controller/KytePageController.php @@ -431,7 +431,7 @@ private function detectChanges($pageObj, $currentData, $newData) { // Check page metadata fields $metadataFields = ['title', 'description', 'lang', 'page_type', 'state', 'sitemap_include', - 'obfuscate_js', 'is_js_module', 'use_container', 'protected', + 'is_js_module', 'use_container', 'protected', 'webcomponent_obj_name', 'header', 'footer', 'main_navigation', 'side_navigation']; foreach ($metadataFields as $field) { @@ -444,7 +444,7 @@ private function detectChanges($pageObj, $currentData, $newData) { } // Check content fields - $contentFields = ['html', 'stylesheet', 'javascript', 'javascript_obfuscated', 'block_layout']; + $contentFields = ['html', 'stylesheet', 'javascript', 'block_layout']; foreach ($contentFields as $field) { $oldValue = isset($currentData[$field]) ? $currentData[$field] : ''; @@ -1006,12 +1006,8 @@ private static function buildJavaScript($page) { // Start script tag $js_parts[] = $page['is_js_module'] == 1 ? '