fix(starlark): code review fixes - security, robustness, and schema parsing

## Security Fixes
- manager.py: Check _update_manifest_safe return values to prevent silent failures
- manager.py: Improve temp file cleanup in _save_manifest to prevent leaks
- manager.py: Fix uninstall order (manifest → memory → disk) for consistency
- api_v3.py: Add path traversal validation in uninstall endpoint
- api_v3.py: Implement atomic writes for manifest files with temp + rename
- pixlet_renderer.py: Relax config validation to only block dangerous shell metacharacters

## Frontend Robustness
- plugins_manager.js: Add safeLocalStorage wrapper for restricted contexts (private browsing)
- starlark_config.html: Scope querySelector to container to prevent modal conflicts

## Schema Parsing Improvements
- pixlet_renderer.py: Indentation-aware get_schema() extraction (handles nested functions)
- pixlet_renderer.py: Handle quoted defaults with commas (e.g., "New York, NY")
- tronbyte_repository.py: Validate file_name is string before path traversal checks

## Dependencies
- requirements.txt: Update Pillow (10.4.0), PyYAML (6.0.2), requests (2.32.0)

## Documentation
- docs/STARLARK_APPS_GUIDE.md: Comprehensive guide explaining:
  - How Starlark apps work
  - That apps come from Tronbyte (not LEDMatrix)
  - Installation, configuration, troubleshooting
  - Links to upstream projects

All changes improve security, reliability, and user experience.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-02-19 16:58:22 -05:00
parent 5d213b5747
commit 441b3c56e9
8 changed files with 667 additions and 43 deletions

View File

@@ -1,6 +1,43 @@
// ─── LocalStorage Safety Wrappers ────────────────────────────────────────────
// Handles environments where localStorage is unavailable or restricted (private browsing, etc.)
const safeLocalStorage = {
getItem(key) {
try {
if (typeof localStorage !== 'undefined') {
return safeLocalStorage.getItem(key);
}
} catch (e) {
console.warn(`safeLocalStorage.getItem failed for key "${key}":`, e.message);
}
return null;
},
setItem(key, value) {
try {
if (typeof localStorage !== 'undefined') {
safeLocalStorage.setItem(key, value);
return true;
}
} catch (e) {
console.warn(`safeLocalStorage.setItem failed for key "${key}":`, e.message);
}
return false;
},
removeItem(key) {
try {
if (typeof localStorage !== 'undefined') {
localStorage.removeItem(key);
return true;
}
} catch (e) {
console.warn(`localStorage.removeItem failed for key "${key}":`, e.message);
}
return false;
}
};
// Define critical functions immediately so they're available before any HTML is rendered
// Debug logging controlled by localStorage.setItem('pluginDebug', 'true')
const _PLUGIN_DEBUG_EARLY = typeof localStorage !== 'undefined' && localStorage.getItem('pluginDebug') === 'true';
// Debug logging controlled by safeLocalStorage.setItem('pluginDebug', 'true')
const _PLUGIN_DEBUG_EARLY = safeLocalStorage.getItem('pluginDebug') === 'true';
if (_PLUGIN_DEBUG_EARLY) console.log('[PLUGINS SCRIPT] Defining configurePlugin and togglePlugin at top level...');
// Expose on-demand functions early as stubs (will be replaced when IIFE runs)
@@ -865,7 +902,7 @@ window.currentPluginConfig = null;
// Store filter/sort state
const storeFilterState = {
sort: localStorage.getItem('storeSort') || 'a-z',
sort: safeLocalStorage.getItem('storeSort') || 'a-z',
filterVerified: false,
filterNew: false,
filterInstalled: null, // null = all, true = installed only, false = not installed only
@@ -873,7 +910,7 @@ window.currentPluginConfig = null;
filterCategories: [],
persist() {
localStorage.setItem('storeSort', this.sort);
safeLocalStorage.setItem('storeSort', this.sort);
},
reset() {
@@ -898,7 +935,7 @@ window.currentPluginConfig = null;
};
// Installed plugins sort state
let installedSort = localStorage.getItem('installedSort') || 'a-z';
let installedSort = safeLocalStorage.getItem('installedSort') || 'a-z';
// Shared on-demand status store (mirrors Alpine store when available)
window.__onDemandStore = window.__onDemandStore || {
@@ -1251,8 +1288,8 @@ const pluginLoadCache = {
}
};
// Debug flag - set via localStorage.setItem('pluginDebug', 'true')
const PLUGIN_DEBUG = typeof localStorage !== 'undefined' && localStorage.getItem('pluginDebug') === 'true';
// Debug flag - set via safeLocalStorage.setItem('pluginDebug', 'true')
const PLUGIN_DEBUG = typeof localStorage !== 'undefined' && safeLocalStorage.getItem('pluginDebug') === 'true';
function pluginLog(...args) {
if (PLUGIN_DEBUG) console.log(...args);
}
@@ -5269,7 +5306,7 @@ function setupStoreFilterListeners() {
installedSortSelect.value = installedSort;
installedSortSelect.addEventListener('change', () => {
installedSort = installedSortSelect.value;
localStorage.setItem('installedSort', installedSort);
safeLocalStorage.setItem('installedSort', installedSort);
const plugins = window.installedPlugins || [];
if (plugins.length > 0) {
sortAndRenderInstalledPlugins(plugins);