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
7 changes: 7 additions & 0 deletions examples/test/text/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
<head>
<title>Text</title>
<meta name="description" content="Text - A-Frame">
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self' https://cdn.aframe.io;
script-src 'self' https://aframe.io;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://cdn.aframe.io;"
/>
<script src="../../../dist/aframe-master.js"></script>
</head>
<body>
Expand Down
23 changes: 7 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"stats-gl": "^3.6.0",
"super-animejs": "^3.1.0",
"three": "npm:super-three@0.181.0",
"three-bmfont-text": "dmarcos/three-bmfont-text#8f38cb02a07321d2369d1234e9b9087c284543ed"
"three-bmfont-text": "dmarcos/three-bmfont-text#c3eec235b9188aa0d8ed949101236e6d5fccf071"
},
"devDependencies": {
"@babel/core": "^7.24.0",
Expand Down
35 changes: 35 additions & 0 deletions tests/csp/csp-host.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<!--
Host page for tests/csp/no-unsafe-eval.test.js. Loaded inside an iframe to
evaluate the built A-Frame bundle under a Content-Security-Policy without
'unsafe-eval' (as in examples/test/text/index.html). 'unsafe-inline' is
allowed only so the inline collector below can run; it does not affect
whether eval()/new Function() are blocked, so the only script-src eval
violation this page can produce is from eval()/new Function().

Served as a real document (not an iframe srcdoc) so that 'self' and URL
resolution behave consistently across Chrome and Firefox.
-->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;"
/>
<script>
window.__cspViolations = [];
document.addEventListener('securitypolicyviolation', function (e) {
window.__cspViolations.push(
(e.effectiveDirective || e.violatedDirective || '') + '|' +
(e.blockedURI || '') + '|' +
(e.sourceFile || '') + ':' + (e.lineNumber || 0) +
(e.sample ? ' sample=[' + e.sample + ']' : ''));
});
</script>
<script src="/base/dist/aframe-master.js"></script>
</head>
<body></body>
</html>
80 changes: 80 additions & 0 deletions tests/csp/no-unsafe-eval.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* global suite, test, teardown, setTimeout, document */

/**
* Real-browser regression test for Content-Security-Policy 'unsafe-eval'.
*
* Loading the A-Frame bundle must not require 'unsafe-eval', i.e. it must not
* call eval() or the Function constructor (which a CSP without 'unsafe-eval'
* blocks, reporting a `securitypolicyviolation`). This is generic: it does not
* matter where an eval()/new Function() is pulled in from, this test catches it.
*
* It was motivated by three-bmfont-text (a dependency of the text component),
* whose text layout built its property getters with `new Function(...)` at load
* time, which broke pages such as examples/test/text/index.html that ship a CSP
* without 'unsafe-eval'.
*
* The bundle is loaded inside an iframe pointing at tests/csp/csp-host.html,
* which enforces such a CSP. It loads the built dist bundle (not the in-memory
* karma-webpack bundle, which is istanbul-instrumented under TEST_ENV=ci and
* that instrumentation itself uses new Function()). CI rebuilds dist via
* `npm run dist` before tests; run it locally too for an accurate result. The
* test uses the real browser like CI does in Chrome and Firefox.
*/
suite('A-Frame bundle Content-Security-Policy', function () {
var iframe;

teardown(function () {
if (iframe && iframe.parentNode) { iframe.parentNode.removeChild(iframe); }
iframe = null;
});

test('loading the A-Frame bundle does not require unsafe-eval', function (done) {
this.timeout(15000);

iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = '/base/tests/csp/csp-host.html';
document.body.appendChild(iframe);

// securitypolicyviolation events are dispatched asynchronously and the
// bundle takes a moment to evaluate. Poll until an eval violation is
// reported or the bundle finished evaluating (it assigns the global AFRAME).
var step = 50;
var elapsed = 0;
var deadline = 12000;
function check () {
var win = iframe.contentWindow;
var violations = (win && win.__cspViolations) || [];
// Each entry is "<directive>|<blockedURI>|<source>". Only eval() /
// new Function() produce a script-src violation whose blockedURI is the
// "eval"/"wasm-eval" keyword (inline and same-origin script loads are
// allowed by the host page's CSP, so they do not show up here).
var evalViolations = violations.filter(function (entry) {
var parts = entry.split('|');
var directive = parts[0] || '';
var blockedURI = parts[1] || '';
return directive.indexOf('script-src') !== -1 &&
(blockedURI === 'eval' || blockedURI === 'wasm-eval');
});
if (evalViolations.length) {
return done(new Error(
'Loading the A-Frame bundle triggered a CSP unsafe-eval violation: ' +
evalViolations.join(', ') + '. The bundle (or one of its ' +
'dependencies) must not use eval() / new Function().'));
}
if (win && win.AFRAME) {
// Bundle fully evaluated with no eval violation: success.
return done();
}
elapsed += step;
if (elapsed >= deadline) {
return done(new Error(
'A-Frame did not finish loading inside the CSP iframe and no ' +
'violation was reported. Was dist/aframe-master.js built ' +
'(npm run dist)?'));
}
setTimeout(check, step);
}
setTimeout(check, step);
});
});
9 changes: 8 additions & 1 deletion tests/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ var webpackConfiguration = require('../webpack.common.cjs');
var FILES = [
// Serve test assets.
'tests/__init.test.js',
{pattern: 'tests/assets/**/*', included: false, served: true}
{pattern: 'tests/assets/**/*', included: false, served: true},
// CSP host page + the built A-Frame bundle it loads, used by
// tests/csp/no-unsafe-eval.test.js. The bundle must be the non-instrumented
// dist build (CI rebuilds it via `npm run dist` before tests): the in-memory
// karma-webpack bundle is istanbul-instrumented under TEST_ENV=ci, and that
// instrumentation itself uses new Function(), which would trip the test.
{pattern: 'tests/csp/csp-host.html', included: false, served: true, watched: true},
{pattern: 'dist/aframe-master.js', included: false, served: true, watched: false}
];
if (process.env.TEST_FILE) {
glob.sync('tests/**/*.test.js').forEach(function (filename) {
Expand Down
Loading