@@ -80,6 +80,13 @@ initI18n();
8080// Only linkify url strings beginning with a proper protocol scheme.
8181linkify . set ( { fuzzyLink : false } ) ;
8282
83+ const pluginAssetBaseURL = `${ document . baseURI } api/plugins/` ;
84+
85+ const getPluginNameFromResourceURL = ( url ) =>
86+ url ?. startsWith ( pluginAssetBaseURL )
87+ ? url . substring ( pluginAssetBaseURL . length ) . split ( '/' ) [ 0 ]
88+ : null ;
89+
8390const EnhancedProvider = ( { provider : ContextProvider , useValueHook, children } ) => {
8491 const value = useValueHook ( ) ;
8592 return < ContextProvider value = { value } > { children } </ ContextProvider > ;
@@ -132,13 +139,64 @@ const App = (props) => {
132139 }
133140 } , [ ] ) ;
134141
142+ const onCSPViolation = React . useCallback ( ( event ) => {
143+ // eslint-disable-next-line no-console
144+ console . warn ( 'Content Security Policy violation detected' , event ) ;
145+
146+ // https://developer.mozilla.org/en-US/docs/Web/API/SecurityPolicyViolationEvent
147+ const cspReportObject = _ . pick ( event , [
148+ // The URI of the resource that was blocked because it violates a policy.
149+ 'blockedURI' ,
150+ // The column number in the document or worker at which the violation occurred.
151+ 'columnNumber' ,
152+ // Whether the user agent is configured to enforce or just report the policy violation.
153+ 'disposition' ,
154+ // The URI of the document or worker in which the violation occurred.
155+ 'documentURI' ,
156+ // The directive that was violated.
157+ 'effectiveDirective' ,
158+ // The line number in the document or worker at which the violation occurred.
159+ 'lineNumber' ,
160+ // The policy whose enforcement caused the violation.
161+ 'originalPolicy' ,
162+ // The URL for the referrer of the resources whose policy was violated, or null.
163+ 'referrer' ,
164+ // A sample of the resource that caused the violation, usually the first 40 characters.
165+ // This will only be populated if the resource is an inline script, event handler or style.
166+ 'sample' ,
167+ // If the violation occurred as a result of a script, this will be the URL of the script.
168+ 'sourceFile' ,
169+ // HTTP status code of the document or worker in which the violation occurred.
170+ 'statusCode' ,
171+ ] ) ;
172+
173+ // Attempt to infer Console plugin name from CSP report object
174+ const pluginName =
175+ getPluginNameFromResourceURL ( cspReportObject . blockedURI ) ||
176+ getPluginNameFromResourceURL ( cspReportObject . sourceFile ) ;
177+
178+ if ( pluginName ) {
179+ // eslint-disable-next-line no-console
180+ console . warn (
181+ `Content Security Policy violation seems to originate from plugin ${ pluginName } ` ,
182+ ) ;
183+ }
184+ } , [ ] ) ;
185+
135186 React . useEffect ( ( ) => {
136187 window . addEventListener ( 'resize' , onResize ) ;
137188 return ( ) => {
138189 window . removeEventListener ( 'resize' , onResize ) ;
139190 } ;
140191 } , [ onResize ] ) ;
141192
193+ React . useEffect ( ( ) => {
194+ document . addEventListener ( 'securitypolicyviolation' , onCSPViolation ) ;
195+ return ( ) => {
196+ document . removeEventListener ( 'securitypolicyviolation' , onCSPViolation ) ;
197+ } ;
198+ } , [ onCSPViolation ] ) ;
199+
142200 React . useLayoutEffect ( ( ) => {
143201 // Prevent infinite loop in case React Router decides to destroy & recreate the component (changing key)
144202 const oldLocation = _ . omit ( prevLocation , [ 'key' ] ) ;
0 commit comments