Skip to content

Commit cbc3373

Browse files
authored
Disable child proxying by default and add feature flag scaffolding (#2746)
* Add feature flag for enabling child messaging. * Add feature flag for disabling child origin response check. * Exported client functions for using feature flags. * Added changefile. * Fix import of resetBuildFeatureFlags * Export missing objects. * Added doc for runtime flags. * Remove export of default feature flags. * Removed extra import. * Properly separate tests with child proxying on/off. * Fix test naming.
1 parent 2a2a598 commit cbc3373

8 files changed

+691
-293
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Removed child messaging proxying by default to avoid security issues. If an app still needs this pattern, it can be activated through the feature flag function `activateChildProxyingCommunication` which enables child proxying for that app.",
4+
"packageName": "@microsoft/teams-js",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/teams-js/src/internal/childCommunication.ts

+30-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/ban-types */
22
/* eslint-disable @typescript-eslint/no-explicit-any */
33

4+
import { getCurrentFeatureFlagsState, isChildProxyingEnabled } from '../public/featureFlags';
45
import { UUID as MessageUUID } from '../public/uuidObject';
56
import { flushMessageQueue, getMessageIdsAsLogString } from './communicationUtils';
67
import { callHandler } from './handlers';
@@ -45,6 +46,9 @@ export function uninitializeChildCommunication(): void {
4546
* Limited to Microsoft-internal use
4647
*/
4748
export function shouldEventBeRelayedToChild(): boolean {
49+
if (!isChildProxyingEnabled()) {
50+
return false;
51+
}
4852
return !!ChildCommunication.window;
4953
}
5054

@@ -63,6 +67,10 @@ type SetCallbackForRequest = (uuid: MessageUUID, callback: Function) => void;
6367
* Limited to Microsoft-internal use
6468
*/
6569
export function shouldProcessChildMessage(messageSource: Window, messageOrigin: string): boolean {
70+
if (!isChildProxyingEnabled()) {
71+
return false;
72+
}
73+
6674
if (!ChildCommunication.window || ChildCommunication.window.closed || messageSource === ChildCommunication.window) {
6775
ChildCommunication.window = messageSource;
6876
ChildCommunication.origin = messageOrigin;
@@ -157,17 +165,32 @@ function sendChildMessageToParent(
157165
message.args,
158166
true, // Tags message as proxied from child
159167
);
168+
169+
// Copy variable to new pointer
170+
const requestChildWindowOrigin = ChildCommunication.origin;
160171
setCallbackForRequest(request.uuid, (...args: any[]): void => {
161-
if (ChildCommunication.window) {
162-
const isPartialResponse = args.pop();
172+
if (!ChildCommunication.window) {
173+
return;
174+
}
175+
176+
if (
177+
!getCurrentFeatureFlagsState().disableEnforceOriginMatchForChildResponses &&
178+
requestChildWindowOrigin !== ChildCommunication.origin
179+
) {
163180
handleIncomingMessageFromChildLogger(
164-
'Message from parent being relayed to child, id: %s',
165-
getMessageIdsAsLogString(message),
181+
'Origin of child window has changed, not sending response back to child window',
166182
);
167-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
168-
// @ts-ignore
169-
sendMessageResponseToChild(message.id, message.uuid, args, isPartialResponse);
183+
return;
170184
}
185+
186+
const isPartialResponse = args.pop();
187+
handleIncomingMessageFromChildLogger(
188+
'Message from parent being relayed to child, id: %s',
189+
getMessageIdsAsLogString(message),
190+
);
191+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
192+
// @ts-ignore
193+
sendMessageResponseToChild(message.id, message.uuid, args, isPartialResponse);
171194
});
172195
}
173196

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// All build feature flags are defined inside this object. Any build feature flag must have its own unique getter and setter function. This pattern allows for client apps to treeshake unused code and avoid including code guarded by this feature flags in the final bundle. If this property isn't desired, use the below runtime feature flags object.
2+
const buildFeatureFlags = {
3+
childProxyingCommunication: false,
4+
};
5+
6+
/**
7+
* This function enables child proxying communication for apps that still needs it.
8+
*
9+
* @deprecated Child proxying is considered an insecure feature and will be removed in future releases.
10+
*/
11+
export function activateChildProxyingCommunication(): void {
12+
buildFeatureFlags.childProxyingCommunication = true;
13+
}
14+
15+
/**
16+
* @hidden
17+
* @internal
18+
* Limited to Microsoft-internal use.
19+
*/
20+
export function isChildProxyingEnabled(): boolean {
21+
return buildFeatureFlags.childProxyingCommunication;
22+
}
23+
24+
/**
25+
* @hidden
26+
* @internal
27+
* Limited to Microsoft-internal use.
28+
*/
29+
export function resetBuildFeatureFlags(): void {
30+
buildFeatureFlags.childProxyingCommunication = false;
31+
}
32+
33+
/**
34+
* Feature flags to activate or deactivate certain features at runtime for an app.
35+
*/
36+
export interface RuntimeFeatureFlags {
37+
/**
38+
* Disables origin validation for responses to child windows. When enabled, this flag bypasses security checks that verify the origin of child window that receives the response.
39+
*
40+
* Default: false
41+
*/
42+
disableEnforceOriginMatchForChildResponses: boolean;
43+
}
44+
45+
// Default runtime feature flags
46+
const defaultFeatureFlags: RuntimeFeatureFlags = {
47+
disableEnforceOriginMatchForChildResponses: false,
48+
} as const;
49+
50+
// Object that stores the current runtime feature flag state
51+
let runtimeFeatureFlags = defaultFeatureFlags;
52+
53+
/**
54+
* @returns The current state of the runtime feature flags.
55+
*/
56+
export function getCurrentFeatureFlagsState(): RuntimeFeatureFlags {
57+
return runtimeFeatureFlags;
58+
}
59+
60+
/**
61+
* It sets the runtime feature flags to the new feature flags provided.
62+
* @param featureFlags The new feature flags to set.
63+
*/
64+
export function setFeatureFlagsState(featureFlags: RuntimeFeatureFlags): void {
65+
runtimeFeatureFlags = featureFlags;
66+
}
67+
68+
/**
69+
* It overwrites all the feature flags in the runtime feature flags object with the new feature flags provided.
70+
* @param newFeatureFlags The new feature flags to set.
71+
* @returns The current state of the runtime feature flags.
72+
*/
73+
export function overwriteFeatureFlagsState(newFeatureFlags: Partial<RuntimeFeatureFlags>): RuntimeFeatureFlags {
74+
setFeatureFlagsState({ ...runtimeFeatureFlags, ...newFeatureFlags });
75+
return getCurrentFeatureFlagsState();
76+
}

packages/teams-js/src/public/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ export * as chat from './chat';
5353
export { OpenGroupChatRequest, OpenSingleChatRequest } from './chat';
5454
export * as clipboard from './clipboard';
5555
export * as dialog from './dialog/dialog';
56+
export {
57+
activateChildProxyingCommunication,
58+
getCurrentFeatureFlagsState,
59+
overwriteFeatureFlagsState,
60+
RuntimeFeatureFlags,
61+
setFeatureFlagsState,
62+
} from './featureFlags';
5663
export * as nestedAppAuth from './nestedAppAuth';
5764
export * as geoLocation from './geoLocation/geoLocation';
5865
export { getAdaptiveCardSchemaVersion } from './adaptiveCards';

0 commit comments

Comments
 (0)