mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-10-30 18:34:19 +00:00
Bulk rename across 989 files: - Package names: posthog-js → @hanzo/insights, posthog-node → @hanzo/insights-node, etc. - Repository URLs: posthog/posthog-js → hanzoai/insights-js - Brand names: PostHog → Insights - File renames: 259 reference JSON files, source files, workflow files - Directory rename: eslint-plugin-posthog-js → eslint-plugin-insights Remaining: 8 refs to @posthog/rrweb-* (real npm deps, need fork+publish)
735 lines
27 KiB
JavaScript
735 lines
27 KiB
JavaScript
require('dotenv').config()
|
|
const express = require('express')
|
|
const path = require('path')
|
|
const app = express()
|
|
const PORT = 8080
|
|
|
|
const removeTrailingSlash = (str) => str.replace(/\/$/, '')
|
|
|
|
if (!process.env.INSIGHTS_PROJECT_API_KEY || !process.env.INSIGHTS_API_HOST) {
|
|
throw new Error('INSIGHTS_PROJECT_API_KEY and INSIGHTS_API_HOST must be set')
|
|
}
|
|
|
|
// UPDATE YOUR TOKEN!!!
|
|
const INSIGHTS_PROJECT_API_KEY = process.env.INSIGHTS_PROJECT_API_KEY
|
|
const INSIGHTS_API_HOST = removeTrailingSlash(process.env.INSIGHTS_API_HOST)
|
|
const INSIGHTS_UI_HOST = removeTrailingSlash(process.env.INSIGHTS_UI_HOST || INSIGHTS_API_HOST)
|
|
const INSIGHTS_USE_SNIPPET = process.env.INSIGHTS_USE_SNIPPET === 'true' || process.env.INSIGHTS_USE_SNIPPET === '1'
|
|
|
|
const INSIGHTS_SCRIPT = INSIGHTS_USE_SNIPPET
|
|
? `<script>
|
|
!function(t,e){var o,n,p,r;e.__SV||(window.hi=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.insights.com","-assets.i.insights.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="insights",u.people=u.people||[],u.toString=function(t){var e="insights";return"insights"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init bs ws ge fs capture De calculateEventProperties $s register register_once register_for_session unregister unregister_for_session Is getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty xs Ss createPersonProfile Es gs opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing ys debug ks getPageViewId captureTraceFeedback captureTraceMetric".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.hi||[]);
|
|
insights.init("${INSIGHTS_PROJECT_API_KEY}", {
|
|
api_host: "${INSIGHTS_API_HOST}",
|
|
ui_host: "${INSIGHTS_UI_HOST}",
|
|
})
|
|
</script>`
|
|
: `<script src="/dist/insights.js"></script>`
|
|
|
|
const CSP_REPORT_URI = `${INSIGHTS_API_HOST}/report/?token=${INSIGHTS_PROJECT_API_KEY}`
|
|
const USE_REPORT_TO = INSIGHTS_API_HOST.startsWith('https://')
|
|
|
|
const CSP_RULES = {
|
|
'default-src': "'self'",
|
|
'script-src': `'self' ${INSIGHTS_API_HOST} ${INSIGHTS_USE_SNIPPET ? "'unsafe-inline'" : "'nonce-123'"}`,
|
|
'connect-src': `'self' ${INSIGHTS_API_HOST} ${INSIGHTS_UI_HOST} https://*.insights.com`,
|
|
'img-src': "'self' data:",
|
|
'style-src': `'self' ${INSIGHTS_UI_HOST}`,
|
|
'report-uri': CSP_REPORT_URI + '&type=report-uri',
|
|
...(USE_REPORT_TO
|
|
? {
|
|
'report-to': 'insights', // easier to debug with report-uri
|
|
}
|
|
: {}),
|
|
}
|
|
|
|
const CSP_HEADER = Object.entries(CSP_RULES)
|
|
.map(([key, value]) => `${key} ${value}`)
|
|
.join('; ')
|
|
const REPORTING_HEADER = `insights="${CSP_REPORT_URI}&type=report-to"`
|
|
|
|
app.use('/dist', express.static(path.join(__dirname, 'dist')))
|
|
app.use('/static', express.static(path.join(__dirname, 'static')))
|
|
|
|
app.use((req, res, next) => {
|
|
// Set CSP as headers, we can probably make this a bit more configurable for the playground
|
|
res.setHeader('Content-Security-Policy-Report-Only', CSP_HEADER)
|
|
if (USE_REPORT_TO) {
|
|
res.setHeader('Reporting-Endpoints', REPORTING_HEADER)
|
|
}
|
|
next()
|
|
})
|
|
|
|
// Home page - displays links to violation examples
|
|
app.get('/', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>CSP Violation Playground</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>CSP Violation Playground</h1>
|
|
|
|
<div class="card">
|
|
<h2>CSP Violation Examples</h2>
|
|
<p>Click the links below to see pages that trigger different types of CSP violations:</p>
|
|
|
|
<a href="/inline-script" class="button">Inline Script Violation</a>
|
|
<a href="/external-script" class="button">External Script Violation</a>
|
|
<a href="/external-img" class="button">External Image Violation</a>
|
|
<a href="/external-style" class="button">External Style Violation</a>
|
|
<a href="/xhr-violation" class="button">XHR Violation</a>
|
|
<a href="/eval" class="button">Eval</a>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Report-To Debug Cases</h2>
|
|
<p>Test cases for debugging report-to directive issues:</p>
|
|
|
|
<a href="/debug-enabled" class="button">Debug Enabled (with ?debug=true)</a>
|
|
<a href="/invalid-content-type" class="button">Invalid Content Type</a>
|
|
<a href="/report-uri-only" class="button">Report-URI Only</a>
|
|
<a href="/report-to-only" class="button">Report-To Only</a>
|
|
<a href="/both-report-directives" class="button">Both Report Directives</a>
|
|
<a href="/malformed-reporting-endpoints" class="button">Malformed Reporting Endpoints</a>
|
|
<a href="/sampling-test/default" class="button">Sampling Test (Multiple Violations)</a>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>CSP Report Endpoint</h2>
|
|
<p>This playground is configured to send CSP violation reports to:</p>
|
|
<pre>${CSP_REPORT_URI}</pre>
|
|
<p>Current CSP policy:</p>
|
|
<pre>${JSON.stringify(CSP_RULES, null, 2)}</pre>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
// Inline script violation
|
|
app.get('/inline-script', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Inline Script Violation</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Inline Script Violation</h1>
|
|
<div class="card">
|
|
<p>This page contains an inline script that violates CSP.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP -->
|
|
<script>
|
|
console.log("This inline script violates CSP");
|
|
document.body.style.backgroundColor = "#f8fafc";
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
// External script violation
|
|
app.get('/external-script', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>External Script Violation</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
<!-- This external script violates CSP -->
|
|
<script src="https://example.com/script.js"></script>
|
|
</head>
|
|
<body>
|
|
<h1>External Script Violation</h1>
|
|
<div class="card">
|
|
<p>This page loads a script from example.com which violates CSP.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
// External image violation
|
|
app.get('/external-img', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>External Image Violation</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>External Image Violation</h1>
|
|
<div class="card">
|
|
<p>This page loads an image from example.com which violates CSP.</p>
|
|
<!-- This image violates CSP -->
|
|
<img src="https://example.com/image.jpg" alt="CSP Violation Image" width="300">
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
// External style violation
|
|
app.get('/external-style', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>External Style Violation</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
<!-- This external stylesheet violates CSP -->
|
|
<link rel="stylesheet" href="https://example.com/styles.css">
|
|
</head>
|
|
<body>
|
|
<h1>External Style Violation</h1>
|
|
<div class="card">
|
|
<p>This page loads a stylesheet from example.com which violates CSP.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
// XHR violation with auto-executing JS
|
|
app.get('/xhr-violation', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>XHR Violation</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>XHR Violation</h1>
|
|
<div class="card">
|
|
<p>This page attempts to make an XHR request to example.com which violates CSP.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
<div id="result"></div>
|
|
</div>
|
|
<!-- We use nonce so that this script is allowed to run despite being inline-->
|
|
<script nonce="123">
|
|
// The XHR request will violate CSP
|
|
setTimeout(() => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('GET', 'https://example.com/api');
|
|
xhr.send();
|
|
|
|
document.getElementById('result').innerHTML =
|
|
'XHR request sent to example.com. Check browser console for CSP violation.';
|
|
}, 1000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/eval', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Eval Violation</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>XHR Violation</h1>
|
|
<div class="card">
|
|
<p>This page attempts to execute code using eval() which violates CSP.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
<div id="result"></div>
|
|
</div>
|
|
<!-- We use nonce so that this script is allowed to run despite being inline-->
|
|
|
|
<script nonce="123">
|
|
// The eval will violate CSP
|
|
setTimeout(() => {
|
|
eval('console.log("Hello, world! - sent from my eval")');
|
|
}, 1000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
// Debug enabled test - adds debug=true parameter to CSP report endpoint
|
|
app.get('/debug-enabled', (req, res) => {
|
|
const debugCSPRules = {
|
|
...CSP_RULES,
|
|
'report-uri': CSP_REPORT_URI + '&type=report-uri&debug=true',
|
|
}
|
|
const debugCSPHeader = Object.entries(debugCSPRules)
|
|
.map(([key, value]) => `${key} ${value}`)
|
|
.join('; ')
|
|
|
|
res.setHeader('Content-Security-Policy-Report-Only', debugCSPHeader)
|
|
if (USE_REPORT_TO) {
|
|
res.setHeader('Reporting-Endpoints', `insights="${CSP_REPORT_URI}&type=report-to&debug=true"`)
|
|
}
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Debug Enabled Test</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Debug Enabled Test</h1>
|
|
<div class="card">
|
|
<p>This page has debug=true parameter enabled in CSP report endpoints.</p>
|
|
<p>CSP reports will include verbose debug logging.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP and should trigger debug logs -->
|
|
<script>
|
|
console.log("Debug enabled violation - this should generate detailed logs");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/debug-case-insensitive', (req, res) => {
|
|
const debugCSPRules = {
|
|
...CSP_RULES,
|
|
'report-uri': CSP_REPORT_URI + '&type=report-uri&DEBUG=true',
|
|
}
|
|
const debugCSPHeader = Object.entries(debugCSPRules)
|
|
.map(([key, value]) => `${key} ${value}`)
|
|
.join('; ')
|
|
|
|
res.setHeader('Content-Security-Policy-Report-Only', debugCSPHeader)
|
|
if (USE_REPORT_TO) {
|
|
res.setHeader('Reporting-Endpoints', `insights="${CSP_REPORT_URI}&type=report-to&DEBUG=true"`)
|
|
}
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Debug Case Insensitive Test</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Debug Case Insensitive Test</h1>
|
|
<div class="card">
|
|
<p>This page has DEBUG=true parameter (uppercase) to test case insensitive handling.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP -->
|
|
<script>
|
|
console.log("Case insensitive debug test violation");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/invalid-content-type', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Invalid Content Type Test</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Invalid Content Type Test</h1>
|
|
<div class="card">
|
|
<p>This page will attempt to send CSP reports with invalid content type.</p>
|
|
<p>This should trigger error logging for invalid content type.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP -->
|
|
<script>
|
|
console.log("Invalid content type test violation");
|
|
|
|
// Manually send a CSP report with wrong content type
|
|
setTimeout(() => {
|
|
fetch('${CSP_REPORT_URI}&type=manual&debug=true', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'text/plain', // Wrong content type
|
|
},
|
|
body: JSON.stringify({
|
|
'csp-report': {
|
|
'document-uri': window.location.href,
|
|
'violated-directive': 'script-src',
|
|
'blocked-uri': 'inline',
|
|
'source-file': window.location.href,
|
|
'line-number': 1,
|
|
'column-number': 1
|
|
}
|
|
})
|
|
}).catch(console.error);
|
|
}, 1000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/report-uri-only', (req, res) => {
|
|
const reportUriOnlyRules = {
|
|
'default-src': "'self'",
|
|
'script-src': `'self' ${INSIGHTS_API_HOST} 'nonce-123'`,
|
|
'connect-src': `'self' ${INSIGHTS_API_HOST} ${INSIGHTS_UI_HOST} https://*.insights.com`,
|
|
'img-src': "'self' data:",
|
|
'style-src': `'self' ${INSIGHTS_UI_HOST}`,
|
|
'report-uri': CSP_REPORT_URI + '&type=report-uri-only&debug=true',
|
|
}
|
|
const reportUriHeader = Object.entries(reportUriOnlyRules)
|
|
.map(([key, value]) => `${key} ${value}`)
|
|
.join('; ')
|
|
|
|
res.setHeader('Content-Security-Policy-Report-Only', reportUriHeader)
|
|
// Explicitly NOT setting Reporting-Endpoints header
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Report-URI Only Test</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Report-URI Only Test</h1>
|
|
<div class="card">
|
|
<p>This page uses only report-uri directive (no report-to).</p>
|
|
<p>CSP Policy: <code>${reportUriHeader}</code></p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP -->
|
|
<script>
|
|
console.log("Report-URI only violation");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/report-to-only', (req, res) => {
|
|
const reportToOnlyRules = {
|
|
'default-src': "'self'",
|
|
'script-src': `'self' ${INSIGHTS_API_HOST} 'nonce-123'`,
|
|
'connect-src': `'self' ${INSIGHTS_API_HOST} ${INSIGHTS_UI_HOST} https://*.insights.com`,
|
|
'img-src': "'self' data:",
|
|
'style-src': `'self' ${INSIGHTS_UI_HOST}`,
|
|
'report-to': 'insights-debug',
|
|
}
|
|
const reportToHeader = Object.entries(reportToOnlyRules)
|
|
.map(([key, value]) => `${key} ${value}`)
|
|
.join('; ')
|
|
|
|
res.setHeader('Content-Security-Policy-Report-Only', reportToHeader)
|
|
res.setHeader('Reporting-Endpoints', `insights-debug="${CSP_REPORT_URI}&type=report-to-only&debug=true"`)
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Report-To Only Test</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Report-To Only Test</h1>
|
|
<div class="card">
|
|
<p>This page uses only report-to directive (no report-uri).</p>
|
|
<p>CSP Policy: <code>${reportToHeader}</code></p>
|
|
<p>Reporting Endpoints: <code>insights-debug="${CSP_REPORT_URI}&type=report-to-only&debug=true"</code></p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP -->
|
|
<script>
|
|
console.log("Report-To only violation");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/both-report-directives', (req, res) => {
|
|
const bothReportRules = {
|
|
'default-src': "'self'",
|
|
'script-src': `'self' ${INSIGHTS_API_HOST} 'nonce-123'`,
|
|
'connect-src': `'self' ${INSIGHTS_API_HOST} ${INSIGHTS_UI_HOST} https://*.insights.com`,
|
|
'img-src': "'self' data:",
|
|
'style-src': `'self' ${INSIGHTS_UI_HOST}`,
|
|
'report-uri': CSP_REPORT_URI + '&type=both-report-uri&debug=true',
|
|
'report-to': 'insights-both',
|
|
}
|
|
const bothReportHeader = Object.entries(bothReportRules)
|
|
.map(([key, value]) => `${key} ${value}`)
|
|
.join('; ')
|
|
|
|
res.setHeader('Content-Security-Policy-Report-Only', bothReportHeader)
|
|
res.setHeader('Reporting-Endpoints', `insights-both="${CSP_REPORT_URI}&type=both-report-to&debug=true"`)
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Both Report Directives Test</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Both Report Directives Test</h1>
|
|
<div class="card">
|
|
<p>This page uses both report-uri and report-to directives.</p>
|
|
<p>CSP Policy: <code>${bothReportHeader}</code></p>
|
|
<p>Reporting Endpoints: <code>insights-both="${CSP_REPORT_URI}&type=both-report-to&debug=true"</code></p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP -->
|
|
<script>
|
|
console.log("Both report directives violation");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/malformed-reporting-endpoints', (req, res) => {
|
|
const malformedRules = {
|
|
'default-src': "'self'",
|
|
'script-src': `'self' ${INSIGHTS_API_HOST} 'nonce-123'`,
|
|
'connect-src': `'self' ${INSIGHTS_API_HOST} ${INSIGHTS_UI_HOST} https://*.insights.com`,
|
|
'img-src': "'self' data:",
|
|
'style-src': `'self' ${INSIGHTS_UI_HOST}`,
|
|
'report-to': 'malformed-endpoint',
|
|
}
|
|
const malformedHeader = Object.entries(malformedRules)
|
|
.map(([key, value]) => `${key} ${value}`)
|
|
.join('; ')
|
|
|
|
res.setHeader('Content-Security-Policy-Report-Only', malformedHeader)
|
|
// Malformed Reporting-Endpoints header (missing quotes, invalid format)
|
|
res.setHeader(
|
|
'Reporting-Endpoints',
|
|
`malformed-endpoint=${CSP_REPORT_URI}&type=malformed&debug=true, invalid=syntax`
|
|
)
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Malformed Reporting Endpoints Test</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Malformed Reporting Endpoints Test</h1>
|
|
<div class="card">
|
|
<p>This page has malformed Reporting-Endpoints header.</p>
|
|
<p>CSP Policy: <code>${malformedHeader}</code></p>
|
|
<p>Malformed Reporting Endpoints: <code>malformed-endpoint=${CSP_REPORT_URI}&type=malformed&debug=true, invalid=syntax</code></p>
|
|
<p>This should trigger error logging for malformed headers.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
</div>
|
|
<!-- This inline script violates CSP -->
|
|
<script>
|
|
console.log("Malformed reporting endpoints violation");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
app.get('/sampling-test/:path?', (req, res) => {
|
|
// Get the number of different URLs from query parameter, default to 20
|
|
const numUrls = parseInt(req.query.urls) || 20
|
|
const useRandomDomains = req.query.random === 'true'
|
|
const testPath = req.params.path || 'default'
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Sampling Test - ${testPath}</title>
|
|
<link href="/static/styles.css" rel="stylesheet">
|
|
<script src="/dist/main.js"></script>
|
|
${INSIGHTS_SCRIPT}
|
|
</head>
|
|
<body>
|
|
<h1>Sampling Test - ${testPath}</h1>
|
|
<div class="card">
|
|
<p>This page generates multiple CSP violations to test sampling behavior.</p>
|
|
<p>Test path: <strong>${testPath}</strong></p>
|
|
<p>Generating <strong>${numUrls}</strong> violations with ${useRandomDomains ? 'random domains' : 'sequential URLs'}.</p>
|
|
<p>Each URL includes random query parameters and hashes to bypass URL-based sampling.</p>
|
|
<p>Some reports may be sampled out and should trigger sampling logs.</p>
|
|
<p><a href="/">Back to Home</a></p>
|
|
<div id="violation-count">Violations generated: 0</div>
|
|
<div id="config">
|
|
<p>Configuration options:</p>
|
|
<a href="/sampling-test/test1?urls=10" class="button">Test1 - 10 URLs</a>
|
|
<a href="/sampling-test/test2?urls=50" class="button">Test2 - 50 URLs</a>
|
|
<a href="/sampling-test/test3?urls=100" class="button">Test3 - 100 URLs</a>
|
|
<a href="/sampling-test/random?urls=20&random=true" class="button">Random - 20 Domains</a>
|
|
<a href="/sampling-test/stress?urls=200&random=true" class="button">Stress - 200 Random</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Multiple inline scripts to generate many violations -->
|
|
<script nonce="123">
|
|
let violationCount = 0;
|
|
const countElement = document.getElementById('violation-count');
|
|
const numUrls = ${numUrls};
|
|
const useRandomDomains = ${useRandomDomains};
|
|
const testPath = '${testPath}';
|
|
|
|
const randomDomains = [
|
|
'example.com',
|
|
'test-domain.org',
|
|
'random-site.net',
|
|
'fake-cdn.io',
|
|
'malicious-site.co',
|
|
'untrusted-source.dev',
|
|
'external-resource.app',
|
|
'third-party.xyz',
|
|
];
|
|
|
|
const getRandomDomain = () => {
|
|
return randomDomains[Math.floor(Math.random() * randomDomains.length)];
|
|
};
|
|
|
|
const getDomain = (index) => {
|
|
return useRandomDomains ? getRandomDomain() : 'example.com';
|
|
};
|
|
|
|
const getPath = (index) => {
|
|
if (useRandomDomains) {
|
|
const paths = ['script', 'resource', 'asset', 'file', 'content'];
|
|
const path = paths[Math.floor(Math.random() * paths.length)];
|
|
const id = Math.floor(Math.random() * 1000);
|
|
const hash = Math.random().toString(36).substring(2, 15);
|
|
const queryParams = [
|
|
\`v=\${Math.floor(Math.random() * 100)}\`,
|
|
\`t=\${Date.now()}\`,
|
|
\`r=\${Math.random().toString(36).substring(2, 8)}\`,
|
|
\`path=\${testPath}\`
|
|
].join('&');
|
|
return \`/\${path}\${id}?\${queryParams}#\${hash}\`;
|
|
}
|
|
const hash = Math.random().toString(36).substring(2, 15);
|
|
const queryParams = [
|
|
\`v=\${Math.floor(Math.random() * 100)}\`,
|
|
\`t=\${Date.now() + index}\`,
|
|
\`r=\${Math.random().toString(36).substring(2, 8)}\`,
|
|
\`path=\${testPath}\`
|
|
].join('&');
|
|
return \`/script\${index}?\${queryParams}#\${hash}\`;
|
|
};
|
|
|
|
// Generate multiple violations rapidly
|
|
for (let i = 0; i < numUrls; i++) {
|
|
setTimeout(() => {
|
|
const domain = getDomain(i);
|
|
const scriptPath = getPath(i);
|
|
const imagePath = getPath(i + 1000);
|
|
|
|
try {
|
|
// This will violate CSP
|
|
eval(\`console.log("Sampling test violation #\${i + 1} from \${domain} (path: \${testPath})");\`);
|
|
violationCount++;
|
|
countElement.textContent = \`Violations generated: \${violationCount}\`;
|
|
} catch (e) {
|
|
// Expected to fail due to CSP
|
|
}
|
|
|
|
// Try to load external scripts from different domains/paths
|
|
const script = document.createElement('script');
|
|
script.src = \`https://\${domain}\${scriptPath}.js\`;
|
|
document.head.appendChild(script);
|
|
|
|
// And external images from different domains/paths
|
|
const img = document.createElement('img');
|
|
img.src = \`https://\${domain}\${imagePath}.jpg\`;
|
|
img.style.display = 'none';
|
|
document.body.appendChild(img);
|
|
|
|
// Also try some CSS violations
|
|
const link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.href = \`https://\${domain}\${getPath(i + 2000)}.css\`;
|
|
document.head.appendChild(link);
|
|
}, i * 50); // Faster generation for more realistic sampling test
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`)
|
|
})
|
|
|
|
// Start the server
|
|
app.listen(PORT, () => {
|
|
console.log(`CSP Violation Playground running at http://localhost:${PORT}`)
|
|
})
|