Skip to content

Commit 99e9c82

Browse files
committed
security: harden
1 parent c0b13c8 commit 99e9c82

File tree

4 files changed

+121
-66
lines changed

4 files changed

+121
-66
lines changed

src/WebView.android.tsx

+38-38
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from './WebViewTypes';
2828

2929
import styles from './WebView.styles';
30+
import validateProps from './validation'
3031

3132
const { getWebViewDefaultUserAgent } = NativeModules.RNCWebViewUtils;
3233

@@ -58,39 +59,41 @@ const setSupportMultipleWindows = true;
5859
const mixedContentMode = 'never'
5960
const hardMinimumChromeVersion = '100.0' // TODO: determinime a good lower bound
6061

61-
const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(({
62-
overScrollMode = 'always',
63-
javaScriptEnabled = true,
64-
thirdPartyCookiesEnabled = true,
65-
scalesPageToFit = true,
66-
saveFormDataDisabled = false,
67-
cacheEnabled = true,
68-
androidHardwareAccelerationDisabled = false,
69-
androidLayerType = "none",
70-
originWhitelist = defaultOriginWhitelist,
71-
deeplinkWhitelist = defaultDeeplinkWhitelist,
72-
setBuiltInZoomControls = true,
73-
setDisplayZoomControls = false,
74-
nestedScrollEnabled = false,
75-
startInLoadingState,
76-
onLoadStart,
77-
onError,
78-
onLoad,
79-
onLoadEnd,
80-
onMessage: onMessageProp,
81-
onOpenWindow: onOpenWindowProp,
82-
renderLoading,
83-
renderError,
84-
style,
85-
containerStyle,
86-
source,
87-
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
88-
validateMeta,
89-
validateData,
90-
minimumChromeVersion,
91-
unsupportedVersionComponent: UnsupportedVersionComponent,
92-
...otherProps
93-
}, ref) => {
62+
const WebViewComponent = forwardRef<{}, AndroidWebViewProps>((props, ref) => {
63+
const {
64+
overScrollMode = 'always',
65+
javaScriptEnabled = true,
66+
thirdPartyCookiesEnabled = true,
67+
scalesPageToFit = true,
68+
saveFormDataDisabled = false,
69+
cacheEnabled = true,
70+
androidHardwareAccelerationDisabled = false,
71+
androidLayerType = "none",
72+
originWhitelist = defaultOriginWhitelist,
73+
deeplinkWhitelist = defaultDeeplinkWhitelist,
74+
setBuiltInZoomControls = true,
75+
setDisplayZoomControls = false,
76+
nestedScrollEnabled = false,
77+
startInLoadingState,
78+
onLoadStart,
79+
onError,
80+
onLoad,
81+
onLoadEnd,
82+
onMessage: onMessageProp,
83+
onOpenWindow: onOpenWindowProp,
84+
renderLoading,
85+
renderError,
86+
style,
87+
containerStyle,
88+
source,
89+
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
90+
validateMeta,
91+
validateData,
92+
minimumChromeVersion,
93+
unsupportedVersionComponent: UnsupportedVersionComponent,
94+
...otherProps
95+
} = validateProps(props)
96+
9497
const messagingModuleName = useRef<string>(`WebViewMessageHandler${uniqueRef += 1}`).current;
9598
const webViewRef = useRef<NativeWebViewAndroid | null>(null);
9699

@@ -197,10 +200,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(({
197200
}
198201
}
199202

200-
if (typeof source === "object" && 'uri' in source && !passesWhitelist(source.uri)){
201-
// eslint-disable-next-line
202-
source = {uri: "about:blank"};
203-
}
203+
const safeSource = (typeof source === "object" && 'uri' in source && !passesWhitelist(source.uri)) ? { uri: 'about:blank' } : source;
204204

205205
const NativeWebView = RNCWebView;
206206

@@ -220,7 +220,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(({
220220

221221
ref={webViewRef}
222222
// TODO: find a better way to type this.
223-
source={source}
223+
source={safeSource}
224224
style={webViewStyles}
225225
overScrollMode={overScrollMode}
226226
javaScriptEnabled={javaScriptEnabled}

src/WebView.ios.tsx

+31-28
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from './WebViewTypes';
2727

2828
import styles from './WebView.styles';
29+
import validateProps from './validation'
2930

3031
const codegenNativeCommands = codegenNativeCommandsUntyped as <T extends {}>(options: { supportedCommands: (keyof T)[] }) => T;
3132

@@ -67,34 +68,36 @@ const enableApplePay = false;
6768
const dataDetectorTypes = 'none';
6869
const hardMinimumIOSVersion = '12.5.6 <13, 13.6.1 <14, 14.8.1 <15, 15.7.1'
6970

70-
const WebViewComponent = forwardRef<{}, IOSWebViewProps>(({
71-
javaScriptEnabled = true,
72-
cacheEnabled = true,
73-
originWhitelist = defaultOriginWhitelist,
74-
deeplinkWhitelist = defaultDeeplinkWhitelist,
75-
textInteractionEnabled= true,
76-
injectedJavaScript,
77-
injectedJavaScriptBeforeContentLoaded,
78-
startInLoadingState,
79-
onLoadStart,
80-
onError,
81-
onLoad,
82-
onLoadEnd,
83-
onMessage: onMessageProp,
84-
renderLoading,
85-
renderError,
86-
style,
87-
containerStyle,
88-
source,
89-
incognito,
90-
validateMeta,
91-
validateData,
92-
decelerationRate: decelerationRateProp,
93-
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
94-
minimumIOSVersion,
95-
unsupportedVersionComponent: UnsupportedVersionComponent,
96-
...otherProps
97-
}, ref) => {
71+
const WebViewComponent = forwardRef<{}, IOSWebViewProps>((props, ref) => {
72+
const {
73+
javaScriptEnabled = true,
74+
cacheEnabled = true,
75+
originWhitelist = defaultOriginWhitelist,
76+
deeplinkWhitelist = defaultDeeplinkWhitelist,
77+
textInteractionEnabled= true,
78+
injectedJavaScript,
79+
injectedJavaScriptBeforeContentLoaded,
80+
startInLoadingState,
81+
onLoadStart,
82+
onError,
83+
onLoad,
84+
onLoadEnd,
85+
onMessage: onMessageProp,
86+
renderLoading,
87+
renderError,
88+
style,
89+
containerStyle,
90+
source,
91+
incognito,
92+
validateMeta,
93+
validateData,
94+
decelerationRate: decelerationRateProp,
95+
onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
96+
minimumIOSVersion,
97+
unsupportedVersionComponent: UnsupportedVersionComponent,
98+
...otherProps
99+
} = validateProps(props)
100+
98101
const webViewRef = useRef<NativeWebViewIOS | null>(null);
99102

100103
const onShouldStartLoadWithRequestCallback = useCallback((

src/__tests__/validation-test.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import validateProps from '../validation'
2+
3+
describe('validateProps', () => {
4+
5+
test('throws when providing static html without origin whitelist', () => {
6+
expect(() => {
7+
validateProps({
8+
source: { html: '<h1>Wayne Foundation</h1>'}
9+
})
10+
}).toThrow('originWhitelist')
11+
})
12+
13+
test('throws when providing static html with wildcard whitelist', () => {
14+
expect(() => {
15+
validateProps({
16+
originWhitelist: ['*', 'http://localhost'],
17+
source: { html: '<h1>Wayne Foundation</h1>'}
18+
})
19+
}).toThrow('originWhitelist')
20+
})
21+
22+
test('throws when providing static html with empty whitelist', () => {
23+
expect(() => {
24+
validateProps({
25+
originWhitelist: [],
26+
source: { html: '<h1>Wayne Foundation</h1>'}
27+
})
28+
}).toThrow('originWhitelist')
29+
})
30+
31+
test('returns props when origin whitelist present', () => {
32+
const props = {
33+
originWhitelist: ['http://localhost'],
34+
source: { html: '<h1>Wayne Foundation</h1>'}
35+
}
36+
37+
expect(validateProps(props)).toBe(props)
38+
})
39+
})

src/validation.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import invariant from 'invariant'
2+
import { AndroidWebViewProps, IOSWebViewProps } from './WebViewTypes'
3+
4+
const validateProps = <P extends IOSWebViewProps | AndroidWebViewProps>(props: P): P => {
5+
if(props.source && 'html' in props.source){
6+
const { originWhitelist } = props
7+
invariant(originWhitelist && originWhitelist.length > 0 && !originWhitelist.includes('*'), 'originWhitelist is required when using html prop and cannot include *')
8+
}
9+
10+
return props
11+
}
12+
13+
export default validateProps

0 commit comments

Comments
 (0)