Skip to content

Commit b7a1670

Browse files
committed
fix(cep): sanitize imported panel preferences
Validate imported local panel settings before writing localStorage, preserve the shipped-locale i18n baseline, and surface storage failures with a warning toast.
1 parent 9a8cb1a commit b7a1670

5 files changed

Lines changed: 99 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ record also lives in the git commit messages.
1010
- Settings no longer exposes non-English UI language choices before matching
1111
locale files ship, preventing a selection that cannot persistently localize
1212
the panel.
13+
- Settings import now sanitizes local panel preferences before writing
14+
`localStorage`, ignoring unknown keys and unsupported preference values while
15+
surfacing storage failures as a warning toast.
1316

1417
### Changed — audio privacy
1518

extension/com.opencut.panel/client/locales/en.json

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -836,16 +836,7 @@
836836
"language.swedish": "Swedish",
837837
"language.thai": "Thai",
838838
"language.turkish": "Turkish",
839-
"language.ui_chinese": "中文",
840839
"language.ui_english": "English",
841-
"language.ui_french": "Français",
842-
"language.ui_german": "Deutsch",
843-
"language.ui_italian": "Italiano",
844-
"language.ui_japanese": "日本語",
845-
"language.ui_korean": "한국어",
846-
"language.ui_portuguese": "Português",
847-
"language.ui_russian": "Русский",
848-
"language.ui_spanish": "Español",
849840
"language.vietnamese": "Vietnamese",
850841
"issue.report_title": "OpenCut issue report from panel",
851842
"interview.batch_button": "Batch {current}/{total}…",
@@ -1311,6 +1302,7 @@
13111302
"settings.import_preset_file": "Import Preset",
13121303
"settings.import_failed": "Couldn't import settings",
13131304
"settings.import_invalid_file": "This file doesn't contain valid OpenCut settings",
1305+
"settings.import_local_failed": "Settings imported, but local panel preferences could not be saved.",
13141306
"settings.import_settings": "Import Settings",
13151307
"settings.import_settings_file_aria": "Import settings file",
13161308
"settings.imported": "Settings imported: {items}",

extension/com.opencut.panel/client/main.js

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8073,14 +8073,16 @@
80738073
try { _recomputeEffectiveOutputDir(); } catch (e) {}
80748074
}
80758075

8076-
function loadLocalSettings() {
8077-
try {
8078-
var saved = localStorage.getItem(LOCAL_SETTINGS_KEY);
8079-
if (saved) {
8080-
var settings = JSON.parse(saved);
8081-
if (settings.autoImport !== undefined && el.settingsAutoImport) el.settingsAutoImport.checked = settings.autoImport;
8082-
if (settings.autoOpen !== undefined && el.settingsAutoOpen) el.settingsAutoOpen.checked = settings.autoOpen;
8083-
if (settings.showNotifications !== undefined && el.settingsShowNotifications) el.settingsShowNotifications.checked = settings.showNotifications;
8076+
function loadLocalSettings() {
8077+
try {
8078+
var saved = localStorage.getItem(LOCAL_SETTINGS_KEY);
8079+
if (saved) {
8080+
var settings = PanelUtils.normalizeLocalSettings
8081+
? PanelUtils.normalizeLocalSettings(JSON.parse(saved))
8082+
: JSON.parse(saved);
8083+
if (settings.autoImport !== undefined && el.settingsAutoImport) el.settingsAutoImport.checked = settings.autoImport;
8084+
if (settings.autoOpen !== undefined && el.settingsAutoOpen) el.settingsAutoOpen.checked = settings.autoOpen;
8085+
if (settings.showNotifications !== undefined && el.settingsShowNotifications) el.settingsShowNotifications.checked = settings.showNotifications;
80848086
if (settings.outputDir && el.settingsOutputDir) el.settingsOutputDir.value = settings.outputDir;
80858087
if (settings.defaultModel && el.settingsDefaultModel) el.settingsDefaultModel.value = settings.defaultModel;
80868088
if (settings.theme && el.settingsTheme) {
@@ -11959,16 +11961,23 @@
1195911961
if (!file) return;
1196011962
var reader = new FileReader();
1196111963
reader.onload = function (e) {
11962-
try {
11963-
var data = JSON.parse(e.target.result);
11964-
api("POST", "/settings/import", data, function (err, result) {
11965-
if (err) { showToast(t("settings.import_failed", "Couldn't import settings"), "error"); return; }
11966-
if (data.localStorage) {
11967-
localStorage.setItem("opencut_settings", JSON.stringify(data.localStorage));
11968-
loadLocalSettings();
11969-
}
11970-
showToast(t("settings.imported", "Settings imported: {items}").replace("{items}", (result.imported || []).join(", ")), "success");
11971-
if (typeof initPresets === "function") initPresets();
11964+
try {
11965+
var data = JSON.parse(e.target.result);
11966+
api("POST", "/settings/import", data, function (err, result) {
11967+
if (err) { showToast(t("settings.import_failed", "Couldn't import settings"), "error"); return; }
11968+
if (data.localStorage) {
11969+
try {
11970+
var localSettings = PanelUtils.normalizeLocalSettings
11971+
? PanelUtils.normalizeLocalSettings(data.localStorage)
11972+
: data.localStorage;
11973+
localStorage.setItem(LOCAL_SETTINGS_KEY, JSON.stringify(localSettings));
11974+
loadLocalSettings();
11975+
} catch (storageError) {
11976+
showToast(t("settings.import_local_failed", "Settings imported, but local panel preferences could not be saved."), "warning");
11977+
}
11978+
}
11979+
showToast(t("settings.imported", "Settings imported: {items}").replace("{items}", (result.imported || []).join(", ")), "success");
11980+
if (typeof initPresets === "function") initPresets();
1197211981
});
1197311982
} catch (ex) {
1197411983
showToast(t("settings.import_invalid_file", "This file doesn't contain valid OpenCut settings"), "error");

extension/com.opencut.panel/client/panel-utils.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,35 @@
5252
});
5353
}
5454

55+
function _copyBooleanSetting(source, target, key) {
56+
if (typeof source[key] === "boolean") target[key] = source[key];
57+
}
58+
59+
function _copyEnumSetting(source, target, key, allowed) {
60+
if (typeof source[key] !== "string") return;
61+
if (allowed.indexOf(source[key]) === -1) return;
62+
target[key] = source[key];
63+
}
64+
65+
function normalizeLocalSettings(value) {
66+
var source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
67+
var settings = {};
68+
_copyBooleanSetting(source, settings, "autoImport");
69+
_copyBooleanSetting(source, settings, "autoOpen");
70+
_copyBooleanSetting(source, settings, "showNotifications");
71+
_copyEnumSetting(source, settings, "outputDir", ["source", "project", "custom"]);
72+
_copyEnumSetting(source, settings, "theme", ["auto", "dark", "light"]);
73+
_copyEnumSetting(source, settings, "lang", ["en"]);
74+
_copyEnumSetting(source, settings, "defaultModel", [
75+
"tiny", "tiny.en", "base", "base.en", "small", "small.en",
76+
"medium", "medium.en", "large", "large-v1", "large-v2",
77+
"large-v3", "turbo", "large-v3-turbo", "distil-large-v2",
78+
"distil-large-v3", "distil-large-v3.5", "distil-medium.en",
79+
"distil-small.en",
80+
]);
81+
return settings;
82+
}
83+
5584
function getCommandPaletteItemKey(item) {
5685
if (!item) return "";
5786
return [item.name || "", item.tab || "", item.sub || ""].join("::");
@@ -279,6 +308,7 @@
279308
escapeJsxDoubleQuotedString: escapeJsxDoubleQuotedString,
280309
createLazyDomProxy: createLazyDomProxy,
281310
normalizePaletteText: normalizePaletteText,
311+
normalizeLocalSettings: normalizeLocalSettings,
282312
formatPaletteLabel: formatPaletteLabel,
283313
getCommandPaletteItemKey: getCommandPaletteItemKey,
284314
scoreCommandPaletteItem: function (item, options) {

extension/com.opencut.panel/tests/panel-utils.test.mjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,44 @@ describe("CEP lazy DOM proxy", () => {
7272
});
7373
});
7474

75+
describe("CEP local settings normalization", () => {
76+
it("keeps only supported local panel preference keys and values", () => {
77+
expect(utils.normalizeLocalSettings({
78+
autoImport: true,
79+
autoOpen: false,
80+
showNotifications: true,
81+
outputDir: "project",
82+
defaultModel: "large-v3",
83+
lang: "en",
84+
theme: "light",
85+
unexpected: "<script>",
86+
nested: { value: "custom" },
87+
})).toEqual({
88+
autoImport: true,
89+
autoOpen: false,
90+
showNotifications: true,
91+
outputDir: "project",
92+
defaultModel: "large-v3",
93+
lang: "en",
94+
theme: "light",
95+
});
96+
});
97+
98+
it("drops malformed or unsupported imported panel preference values", () => {
99+
expect(utils.normalizeLocalSettings({
100+
autoImport: "yes",
101+
autoOpen: 1,
102+
showNotifications: null,
103+
outputDir: "C:/Users/me",
104+
defaultModel: "malicious",
105+
lang: "es",
106+
theme: "solarized",
107+
})).toEqual({});
108+
expect(utils.normalizeLocalSettings(null)).toEqual({});
109+
expect(utils.normalizeLocalSettings([])).toEqual({});
110+
});
111+
});
112+
75113
describe("CEP command palette indexer", () => {
76114
it("builds deduplicated browse sections from recent, favorite, and current commands", () => {
77115
const sections = utils.buildCommandPaletteSections(paletteOptions({

0 commit comments

Comments
 (0)