Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
39 changes: 12 additions & 27 deletions src/Mvc/Controller/KytePageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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] : '';
Expand Down Expand Up @@ -1006,12 +1006,8 @@ private static function buildJavaScript($page) {
// Start script tag
$js_parts[] = $page['is_js_module'] == 1 ? '<script type="module">' : '<script>';

// Add kyte connect
if ($page['site']['application']['obfuscate_kyte_connect'] == 1) {
$js_parts[] = $page['site']['application']['kyte_connect_obfuscated'] . "\n\n";
} else {
$js_parts[] = $page['site']['application']['kyte_connect'] . "\n\n";
}
// Add kyte connect (JS obfuscation removed in v4.7.0 — always serve plain source; see KYTE-#191)
$js_parts[] = $page['site']['application']['kyte_connect'] . "\n\n";

// Add web components
$js_parts[] = self::buildWebComponentsJS($page);
Expand All @@ -1025,12 +1021,8 @@ private static function buildJavaScript($page) {
$js_parts[] = 'if (k.isSession()) { ' . "\n";
}

// Add custom JS
if ($page['obfuscate_js'] == 1) {
$js_parts[] = $page['javascript_obfuscated'] . "\n";
} else {
$js_parts[] = $page['javascript'] . "\n";
}
// Add custom JS (JS obfuscation removed in v4.7.0 — always serve plain source; see KYTE-#191)
$js_parts[] = $page['javascript'] . "\n";

// Close protection logic
if ($page['protected'] == 1) {
Expand Down Expand Up @@ -1263,20 +1255,13 @@ private static function buildSideNavigationJS($page) {
private static function buildHeaderFooterJS($page) {
$js_parts = [];

// JS obfuscation removed in v4.7.0 — always serve plain source; see KYTE-#191
if (!empty($page['header'])) {
if ($page['header']['obfuscate_js'] == 1) {
$js_parts[] = $page['header']['javascript_obfuscated'] . "\n";
} else {
$js_parts[] = $page['header']['javascript'] . "\n";
}
$js_parts[] = $page['header']['javascript'] . "\n";
}

if (!empty($page['footer'])) {
if ($page['footer']['obfuscate_js'] == 1) {
$js_parts[] = $page['footer']['javascript_obfuscated'] . "\n";
} else {
$js_parts[] = $page['footer']['javascript'] . "\n";
}
$js_parts[] = $page['footer']['javascript'] . "\n";
}

return implode('', $js_parts);
Expand Down Expand Up @@ -1619,7 +1604,7 @@ private function markPreviousVersionsAsNotCurrent($pageId) {
private function addChangedFieldsToVersion(&$versionData, $changes, $pageObj, $newData) {
// Add changed 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) {
Expand All @@ -1629,7 +1614,7 @@ private function addChangedFieldsToVersion(&$versionData, $changes, $pageObj, $n
}

// Add changed content fields (compressed)
$contentFields = ['html', 'stylesheet', 'javascript', 'javascript_obfuscated', 'block_layout'];
$contentFields = ['html', 'stylesheet', 'javascript', 'block_layout'];

foreach ($contentFields as $field) {
if (isset($changes[$field]) && isset($newData[$field])) {
Expand Down
11 changes: 5 additions & 6 deletions src/Mvc/Controller/KyteScriptController.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private function detectScriptChanges($scriptObj, $currentData, $newData): array
$changes = [];

// Check script metadata fields
$metadataFields = ['name', 'description', 's3key', 'script_type', 'obfuscate_js', 'is_js_module', 'include_all', 'state'];
$metadataFields = ['name', 'description', 's3key', 'script_type', 'is_js_module', 'include_all', 'state'];

foreach ($metadataFields as $field) {
if (isset($newData[$field]) && $scriptObj->$field != $newData[$field]) {
Expand All @@ -196,7 +196,7 @@ private function detectScriptChanges($scriptObj, $currentData, $newData): array
}

// Check content fields
$contentFields = ['content', 'content_js_obfuscated'];
$contentFields = ['content'];

foreach ($contentFields as $field) {
$oldContent = $currentData[$field] ?? '';
Expand Down Expand Up @@ -398,7 +398,7 @@ private function markPreviousScriptVersionsAsNotCurrent($scriptId): void {
*/
private function addChangedFieldsToScriptVersion(&$versionData, $changes, $scriptObj, $newData): void {
// Add changed metadata fields
$metadataFields = ['name', 'description', 's3key', 'script_type', 'obfuscate_js', 'is_js_module', 'include_all', 'state'];
$metadataFields = ['name', 'description', 's3key', 'script_type', 'is_js_module', 'include_all', 'state'];

foreach ($metadataFields as $field) {
if (isset($changes[$field])) {
Expand Down Expand Up @@ -483,9 +483,8 @@ private function handleScriptPublication($o, $r, $d): void {
$credential = new \Kyte\Aws\Credentials($r['site']['region'], $app->aws_public_key, $app->aws_private_key);
$s3 = new \Kyte\Aws\S3($credential, $r['site']['s3BucketName']);

// write script to file
$content = ($o->script_type == 'css') ? $r['content'] :
($o->obfuscate_js ? $r['content_js_obfuscated'] : $r['content']);
// write script to file (JS obfuscation removed in v4.7.0 — always serve plain source; see KYTE-#191)
$content = $r['content'];

$s3->write($o->s3key, $content);

Expand Down
3 changes: 2 additions & 1 deletion src/Mvc/Model/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,13 @@
'protected' => false,
],

// JS obfuscation removed in v4.7.0 (KYTE-#191); column retained until v4.8.0 drop. Default off for new apps.
'obfuscate_kyte_connect' => [
'type' => 'i',
'required' => false,
'size' => 1,
'unsigned' => true,
'default' => 1,
'default' => 0,
'date' => false,
],

Expand Down
Loading