Skip to content

Commit b12ab1d

Browse files
JohnMcLearclaude
andcommitted
fix(privacy-banner): allow-list keys before sending to clientVars (Qodo)
storeSettings() merges nested objects with _.defaults() and preserves unknown nested keys, and TypeScript's Pick<> doesn't strip at runtime. The previous wire path forwarded settings.privacyBanner by reference into both clientVars and getPublicSettings(), so any extra keys an operator typed (or pasted) under privacyBanner — credentials, internal notes, anything — would have shipped to every browser on every pad load. Adds getPublicPrivacyBanner() in Settings.ts that returns a literal with only {enabled, title, body, learnMoreUrl, dismissal}, and uses it from both leak sites (PadMessageHandler.ts clientVars and getPublicSettings()). Single source of truth for the wire shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c206e2a commit b12ab1d

2 files changed

Lines changed: 19 additions & 2 deletions

File tree

‎src/node/handler/PadMessageHandler.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import padutils from '../../static/js/pad_utils';
3333
import readOnlyManager from '../db/ReadOnlyManager';
3434
import settings, {
3535
exportAvailable,
36+
getPublicPrivacyBanner,
3637
sofficeAvailable
3738
} from '../utils/Settings';
3839
import {anonymizeIp} from '../utils/anonymizeIp';
@@ -1157,7 +1158,10 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
11571158
enableDarkMode: settings.enableDarkMode,
11581159
enablePadWideSettings: settings.enablePadWideSettings,
11591160
padDeletionToken,
1160-
privacyBanner: settings.privacyBanner,
1161+
// Allow-listed copy — settings.privacyBanner could carry extra nested
1162+
// keys from a hand-edited settings.json; sending those by reference
1163+
// would leak them to every browser. See getPublicPrivacyBanner().
1164+
privacyBanner: getPublicPrivacyBanner(),
11611165
automaticReconnectionTimeout: settings.automaticReconnectionTimeout,
11621166
initialRevisionList: [],
11631167
initialOptions: pad.getPadSettings(),

‎src/node/utils/Settings.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,12 +733,25 @@ const settings: SettingsType = {
733733
skinName: settings.skinName,
734734
skinVariants: settings.skinVariants,
735735
enablePadWideSettings: settings.enablePadWideSettings,
736-
privacyBanner: settings.privacyBanner,
736+
privacyBanner: getPublicPrivacyBanner(),
737737
}
738738
},
739739
gitVersion: getGitCommit(),
740740
}
741741

742+
// Build the wire-shape of `privacyBanner` for clientVars / getPublicSettings().
743+
// The settings file is operator-controlled and `_.defaults()` (used by
744+
// storeSettings) preserves unknown nested keys at runtime. Returning a literal
745+
// instead of `settings.privacyBanner` itself stops a typo or copy-paste from
746+
// shipping arbitrary extra keys to every browser.
747+
export const getPublicPrivacyBanner = () => ({
748+
enabled: settings.privacyBanner.enabled,
749+
title: settings.privacyBanner.title,
750+
body: settings.privacyBanner.body,
751+
learnMoreUrl: settings.privacyBanner.learnMoreUrl,
752+
dismissal: settings.privacyBanner.dismissal,
753+
});
754+
742755
export default settings;
743756
// CJS compatibility: plugins use require('ep_etherpad-lite/node/utils/Settings')
744757
// and expect settings properties directly on the module object, not under .default

0 commit comments

Comments
 (0)