Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tools/applicationinsights-web-snippet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ let config = {connectionString: "InstrumentationKey=xxx", sri: true};

More details on web snippet, see [Web Snippet](https://github.com/microsoft/ApplicationInsights-JS#snippet-setup-ignore-if-using-npm-setup).

### Trusted Types Support (available since v1.2.2)
For restrictions like require-trusted-types-for 'script', check [url policy check](./trustedTypeSupport.md)
For restrictions like script-src 'self' ..., check [add nounce when inject script]()

## Build
```
npm install -g grunt-cli
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<meta http-equiv="Content-Security-Policy" content="img-src https://browser.events.data.microsoft.com;frame-src https://browser.events.data.microsoft.com;require-trusted-types-for 'script'">
<html>
<head>
<style>
/* Optional styling for buttons and messages */
button {
margin: 10px;
padding: 10px 20px;
font-size: 16px;
}
#messages {
margin: 20px;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
}
</style>
<script type="text/javascript">
const myTrustedTypePolicy = trustedTypes.createPolicy('myTrustedTypePolicy', {
createScriptURL: (url) => {
console.log('Trusted Type Policy: myTrustedTypePolicy called with URL:', url);
return url;
}
});
!(function (cfg){function e(){cfg.onInit&&cfg.onInit(n)}var x,D,E,t,L,C,n,U=window,O=document,b=U.location,I="script",j="ingestionendpoint",q="disableExceptionTracking",A="ai.device.";"instrumentationKey"[x="toLowerCase"](),D="crossOrigin",E="POST",t="appInsightsSDK",L=cfg.name||"appInsights",C=cfg.pn||"aiPolicy",(cfg.name||U[t])&&(U[t]=L),n=U[L]||function(u){var s=!1,p=!1,l={initialize:!0,queue:[],sv:"8",version:2,config:u};function d(e){var t,n,i,a,r,o,c,s;!0!==cfg.dle&&(o=(t=function(){var e,t={},n=u.connectionString;if("string"==typeof n&&n)for(var i=n.split(";"),a=0;a<i.length;a++){var r=i[a].split("=");2===r.length&&(t[r[0][x]()]=r[1])}return t[j]||(e=(n=t.endpointsuffix)?t.location:null,t[j]="https://"+(e?e+".":"")+"dc."+(n||"services.visualstudio.com")),t}()).instrumentationkey||u.instrumentationKey||"",t=(t=(t=t[j])&&"/"===t.slice(-1)?t.slice(0,-1):t)?t+"/v2/track":u.endpointUrl,t=u.userOverrideEndpointUrl||t,(n=[]).push((i="SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details)",a=e,c=t,(s=(r=f(o,"Exception")).data).baseType="ExceptionData",s.baseData.exceptions=[{typeName:"SDKLoadFailed",message:i.replace(/\./g,"-"),hasFullStack:!1,stack:i+"\nSnippet failed to load ["+a+"] -- Telemetry is disabled\nHelp Link: https://go.microsoft.com/fwlink/?linkid=2128109\nHost: "+(b&&b.pathname||"_unknown_")+"\nEndpoint: "+c,parsedStack:[]}],r)),n.push((s=e,i=t,(c=(a=f(o,"Message")).data).baseType="MessageData",(r=c.baseData).message='AI (Internal): 99 message:"'+("SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details) ("+s+")").replace(/\"/g,"")+'"',r.properties={endpoint:i},a)),e=n,o=t,JSON&&((c=U.fetch)&&!cfg.useXhr?c(o,{method:E,body:JSON.stringify(e),mode:"cors"}):XMLHttpRequest&&((s=new XMLHttpRequest).open(E,o),s.setRequestHeader("Content-type","application/json"),s.send(JSON.stringify(e)))))}function f(e,t){var n={},i="Browser";function a(e){e=""+e;return 1===e.length?"0"+e:e}return n[A+"id"]=i[x](),n[A+"type"]=i,n["ai.operation.name"]=b&&b.pathname||"_unknown_",n["ai.internal.sdkVersion"]="javascript:snippet_"+(l.sv||l.version),{time:(i=new Date).getUTCFullYear()+"-"+a(1+i.getUTCMonth())+"-"+a(i.getUTCDate())+"T"+a(i.getUTCHours())+":"+a(i.getUTCMinutes())+":"+a(i.getUTCSeconds())+"."+(i.getUTCMilliseconds()/1e3).toFixed(3).slice(2,5)+"Z",iKey:e,name:"Microsoft.ApplicationInsights."+e.replace(/-/g,"")+"."+t,sampleRate:100,tags:n,data:{baseData:{ver:2}},ver:undefined,seq:"1",aiDataContract:undefined}}var n,i,t,a,g=-1,h=0,m=["js.monitor.azure.com","js.cdn.applicationinsights.io","js.cdn.monitor.azure.com","js0.cdn.applicationinsights.io","js0.cdn.monitor.azure.com","js2.cdn.applicationinsights.io","js2.cdn.monitor.azure.com","az416426.vo.msecnd.net"],r=u.url||cfg.src,o=function(){return c(r,null)};function c(t,r){if((n=navigator)&&(~(n=(n.userAgent||"").toLowerCase()).indexOf("msie")||~n.indexOf("trident/"))&&~t.indexOf("ai.3")&&(t=t.replace(/(\/)(ai\.3\.)([^\d]*)$/,function(e,t,n){return t+"ai.2"+n})),!1!==cfg.cr)for(var e=0;e<m.length;e++)if(0<t.indexOf(m[e])){g=e;break}var n,o=function(e){var a;l.queue=[],p||(0<=g&&h+1<m.length?(a=(g+h+1)%m.length,i(t.replace(/^(.*\/\/)([\w\.]*)(\/.*)$/,function(e,t,n,i){return t+m[a]+i})),h+=1):(s=p=!0,d(t)))},c=function(e,t){p||setTimeout(function(){!t&&l.core||o()},500),s=!1},i=function(e){var n,i=O.createElement(I),e=(cfg.pl?cfg.ttp&&cfg.ttp.createScript?i.src=cfg.ttp.createScriptURL(e):i.src=(null==(n=window.trustedTypes)?void 0:n.createPolicy(C,{createScriptURL:function(e){try{var t=new URL(e);if(t.host&&"js.monitor.azure.com"===t.host)return e;a(e)}catch(n){a(e)}}})).createScriptURL(e):i.src=e,r&&(i.integrity=r),i.setAttribute("data-ai-name",L),cfg[D]);function a(e){d("AI policy blocked URL: "+e)}return!e&&""!==e||"undefined"==i[D]||(i[D]=e),i.onload=c,i.onerror=o,i.onreadystatechange=function(e,t){"loaded"!==i.readyState&&"complete"!==i.readyState||c(0,t)},cfg.ld&&cfg.ld<0?O.getElementsByTagName("head")[0].appendChild(i):setTimeout(function(){O.getElementsByTagName(I)[0].parentNode.appendChild(i)},cfg.ld||0),i};i(t)}cfg.sri&&(n=r.match(/^((http[s]?:\/\/.*\/)\w+(\.\d+){1,5})\.(([\w]+\.){0,2}js)$/))&&6===n.length?(T="".concat(n[1],".integrity.json"),i="@".concat(n[4]),S=window.fetch,t=function(e){if(!e.ext||!e.ext[i]||!e.ext[i].file)throw Error("Error Loading JSON response");var t=e.ext[i].integrity||null;c(r=n[2]+e.ext[i].file,t)},S&&!cfg.useXhr?S(T,{method:"GET",mode:"cors"}).then(function(e){return e.json()["catch"](function(){return{}})}).then(t)["catch"](o):XMLHttpRequest&&((a=new XMLHttpRequest).open("GET",T),a.onreadystatechange=function(){if(a.readyState===XMLHttpRequest.DONE)if(200===a.status)try{t(JSON.parse(a.responseText))}catch(e){o()}else o()},a.send())):r&&o();try{l.cookie=O.cookie}catch(k){}function e(e){for(;e.length;)!function(t){l[t]=function(){var e=arguments;s||l.queue.push(function(){l[t].apply(l,e)})}}(e.pop())}var v,y,S="track",T="TrackPage",w="TrackEvent",S=(e([S+"Event",S+"PageView",S+"Exception",S+"Trace",S+"DependencyData",S+"Metric",S+"PageViewPerformance","start"+T,"stop"+T,"start"+w,"stop"+w,"addTelemetryInitializer","setAuthenticatedUserContext","clearAuthenticatedUserContext","flush"]),l.SeverityLevel={Verbose:0,Information:1,Warning:2,Error:3,Critical:4},(u.extensionConfig||{}).ApplicationInsightsAnalytics||{});return!0!==u[q]&&!0!==S[q]&&(e(["_"+(v="onerror")]),y=U[v],U[v]=function(e,t,n,i,a){var r=y&&y(e,t,n,i,a);return!0!==r&&l["_"+v]({message:e,url:t,lineNumber:n,columnNumber:i,error:a,evt:U.event}),r},u.autoExceptionInstrumented=!0),l}(cfg.cfg),(U[L]=n).queue&&0===n.queue.length?(n.queue.push(e),n.trackPageView({})):e();})({
src: "https://js.monitor.azure.com/scripts/b/ai.3.gbl.min.js",
// name: "appInsights", // Global SDK Instance name defaults to "appInsights" when not supplied
// ld: 0, // Defines the load delay (in ms) before attempting to load the sdk. -1 = block page load and add to head. (default) = 0ms load after timeout,
// useXhr: 1, // Use XHR instead of fetch to report failures (if available),
// dle: true, // Prevent the SDK from reporting load failure log
crossOrigin: "anonymous", // When supplied this will add the provided value as the cross origin attribute on the script tag
// onInit: null, // Once the application insights instance has loaded and initialized this callback function will be called with 1 argument -- the sdk instance (DO NOT ADD anything to the sdk.queue -- As they won't get called)
// sri: false, // Custom optional value to specify whether fetching the snippet from integrity file and do integrity check
pl: true, // Optional value to specify whether to use Trusted Types for script URL
pn: "aiPolicy", // Optional value to specify the policy name for Trusted Types
ttp: myTrustedTypePolicy,
cfg: { // Application Insights Configuration
connectionString: "InstrumentationKey=814a172a-92fd-4950-9023-9cf13bb65696;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/"
}
});
</script>
</head>
<body>
<div id="messages"></div>
<button id="myButton">Send Telemetry</button>

<script nonce="randomNonceValue" >
function sendTelemetry() {
appInsights.trackEvent({name: 'test event'});
appInsights.flush();
}
</script>
<script nonce="randomNonceValue">
document.getElementById('myButton').addEventListener('click', sendTelemetry);
</script>

<script nonce="1234">

function displayMessage(message, type) {
var messageDiv = document.createElement('div');
messageDiv.className = type;
messageDiv.textContent = message;
document.getElementById('messages').appendChild(messageDiv);
}


</script>
<script>
var windowOnError = window.onerror;
window.onerror = function(message, source, lineno, colno, error) {
windowOnError && windowOnError(message, source, lineno, colno, error);
console.log('Error captured:', message, source, lineno, colno, error);

// Display the error message on the page
var errorMessage = document.createElement('div');
errorMessage.className = 'error';
errorMessage.textContent = 'Error: ' + message + ' at ' + source + ':' + lineno + ':' + colno;
document.getElementById('messages').appendChild(errorMessage);
};
</script>

</body>
</html>
38 changes: 37 additions & 1 deletion tools/applicationinsights-web-snippet/src/snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ declare var cfg:ISnippetConfig;
let strGetMethod = "GET";
let sdkInstanceName = "appInsightsSDK"; // required for Initialization to find the current instance
let aiName = cfg.name || "appInsights"; // provide non default instance name through snipConfig name value
let policyName = cfg.pn || "aiPolicy";
if (cfg.name || win[sdkInstanceName]) {
// Only set if supplied or another name is defined to avoid polluting the global namespace
win[sdkInstanceName] = aiName;
Expand Down Expand Up @@ -304,9 +305,44 @@ declare var cfg:ISnippetConfig;
loadFailed = false;
}

function create_policy() {
// Function to handle URL validation
function validateURL(urlString: string): string | null {
try {
const url = new URL(urlString);
if (url.host && url.host === "js.monitor.azure.com") {
return urlString;
} else {
handleInvalidURL(urlString);
}
} catch {
handleInvalidURL(urlString);
}
}

// Function to handle reporting failures
function handleInvalidURL(urlString: string) {
_reportFailure("AI policy blocked URL: " + urlString);
}

return (window as any).trustedTypes?.createPolicy(policyName, {
createScriptURL: validateURL
});
}


const _createScript = (src: string) => {
let scriptElement : HTMLElement = doc.createElement(scriptText);
(scriptElement as any)["src"] = src;
if (cfg.pl){
if (cfg.ttp && cfg.ttp.createScript) {
(scriptElement as any)["src"] = cfg.ttp.createScriptURL(src);
} else {
(scriptElement as any)["src"] = create_policy().createScriptURL(src);
}
} else {
(scriptElement as any)["src"] = src;
}

if (integrity){
// Set the integrity attribute to the script tag if integrity is provided
(scriptElement as any).integrity = integrity;
Expand Down
22 changes: 22 additions & 0 deletions tools/applicationinsights-web-snippet/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export interface SdkLoaderConfig {
cr?: boolean;
dle?: boolean;
sri?: boolean;
pl?: boolean;
pn?: string;
ttp?: TrustedTypePolicy;
}

export abstract class TrustedTypePolicy {
readonly name: string;
createHTML?: ((input: string, ...args: any[]) => string) | undefined;
createScript?: ((input: string, ...args: any[]) => string) | undefined;
createScriptURL?: ((input: string, ...args: any[]) => string) | undefined;
}

export interface ISnippetConfig {
Expand All @@ -25,6 +35,18 @@ export interface ISnippetConfig {
cr?: boolean; // cdn retry would be proceed if ture
dle?: boolean; // Custom optional value to disable sdk load error to be sent
sri?: boolean; // Custom optional value to specify whether fetching the snippet from integrity file and do integrity check
/**
* Custom optional value to specify whether to enable the trusted type policy check on snippet
*/
pl?: boolean;
/**
* Custom optional value to specify the name of the trusted type policy that would be implemented on the snippet, default is 'aiPolicy'
*/
pn?: string;
/*
* Custom optional value to specify the trusted type policy that would be applied on the snippet src
*/
ttp?: TrustedTypePolicy;
}

export interface Fields {
Expand Down
62 changes: 62 additions & 0 deletions tools/applicationinsights-web-snippet/trustedTypeSupport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Trust Type Support

We offer two methods for implementing Trusted Type policy checks. Choose the one that best suits your needs.

## Method 1: Using require-trusted-types-for 'script'
If your page utilizes require-trusted-types-for 'script' to enforce script injection policies, configure your snippet as follows:
### Configuration Options
```js
/**
* Custom optional value to specify whether to enable the trusted type policy check on snippet
*/
pl?: boolean;
/**
* Custom optional value to specify the name of the trusted type policy that would be implemented on the snippet, default is 'aiPolicy'
*/
pn?: string;
/*
* Custom optional value to specify the trusted type policy that would be applied on the snippet src
*/
ttp?: TrustedTypePolicy;
```
### Automatic Policy Creation
To have the policy automatically created, set pl to true. You can optionally specify a policy name with pn.
Example usage:
```html
<script>
!(function (cfg) ....)({
src: "https://js.monitor.azure.com/scripts/b/ai.3.gbl.min.js",
pl: true,
pn: "aiPolicy",
cfg: {
connectionString: ""
}
});
</script>
```
### Using a Custom Trusted Type Policy
If you prefer to pass your own Trusted Type Policy, create it and then apply it using the ttp option.

Example:
```html
<script>
const myTrustedTypePolicy = trustedTypes.createPolicy('myTrustedTypePolicy', {
createScriptURL: (url) => {
console.log('Trusted Type Policy: myTrustedTypePolicy called with URL:', url);
return url;
}
});
!(function (cfg) ....)({
src: "https://js.monitor.azure.com/scripts/b/ai.3.gbl.min.js",
pl: true,
ttp: myTrustedTypePolicy,
cfg: {
connectionString: ""
}
});
</script>
```
### Test
Your could also check our [test](./Tests/manual/cspUsePolicyTest.html)

## Method 2: Using Nonce Tag and script-src