diff --git a/dashboard/src/routes/apps/[slug]/+page.svelte b/dashboard/src/routes/apps/[slug]/+page.svelte index 6021538..46c1fd4 100644 --- a/dashboard/src/routes/apps/[slug]/+page.svelte +++ b/dashboard/src/routes/apps/[slug]/+page.svelte @@ -412,10 +412,24 @@ let editTopicAuthMode = $state('public'); let savingTopic = $state(false); let editTopicError = $state(null); - // Flipping internal → external is the security-sensitive change. - const editFlipToExternal = $derived( - !!topicToEdit && !topicToEdit.external_subscribable && editTopicExternal - ); + // F-S-014: warn whenever the resulting (external, auth_mode) pair is + // strictly more permissive than the current one — not just on the + // internal → external flip. The order least → most permissive is: + // internal (any auth) < external+session < external+token < external+public + const editPermissivenessChange = $derived.by(() => { + if (!topicToEdit) return false; + const rank = (external: boolean, mode: TopicAuthMode) => { + if (!external) return 0; + if (mode === 'session') return 1; + if (mode === 'token') return 2; + return 3; // public + }; + const before = rank(topicToEdit.external_subscribable, topicToEdit.auth_mode); + const after = rank(editTopicExternal, editTopicAuthMode); + return after > before; + }); + // Kept for narrow callers; semantics now covered by editPermissivenessChange. + const editFlipToExternal = $derived(editPermissivenessChange); // Delete confirm. let topicToRemove = $state(null); let removingTopic = $state(false);