Skip to content

Demos: Add Content Security Policy (CSP) validation#32731

Open
EugeniyKiyashko wants to merge 24 commits intoDevExpress:26_1from
EugeniyKiyashko:26_1_csp_in_demos
Open

Demos: Add Content Security Policy (CSP) validation#32731
EugeniyKiyashko wants to merge 24 commits intoDevExpress:26_1from
EugeniyKiyashko:26_1_csp_in_demos

Conversation

@EugeniyKiyashko
Copy link
Contributor

No description provided.

@EugeniyKiyashko EugeniyKiyashko self-assigned this Feb 27, 2026
@EugeniyKiyashko EugeniyKiyashko changed the title Demos: run testing with testcafe Demos: run testing Feb 27, 2026
@EugeniyKiyashko EugeniyKiyashko changed the title Demos: run testing Demos: check CSP Feb 27, 2026
@EugeniyKiyashko EugeniyKiyashko changed the title Demos: check CSP Demos: run CSP testing Mar 4, 2026
@EugeniyKiyashko EugeniyKiyashko marked this pull request as ready for review March 4, 2026 09:10
@EugeniyKiyashko EugeniyKiyashko requested a review from a team as a code owner March 4, 2026 09:10
Copilot AI review requested due to automatic review settings March 4, 2026 09:10
@EugeniyKiyashko EugeniyKiyashko changed the title Demos: run CSP testing Demos: Add Content Security Policy (CSP) validation Mar 4, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds automated CSP (Content Security Policy) “report-only” validation for the demos, so CI can detect CSP violations across jQuery + framework demos and publish a consolidated report.

Changes:

  • Introduces an Express-based CSP report-only server for demos, plus scripts to run CSP checks and summarize violations.
  • Extends TestCafe visual tests to inject a CSP violation listener and write violations into a report file when enabled.
  • Adds GitHub Actions jobs to run CSP checks per framework and generate a merged step summary + artifacts.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package.json Adds a root-level script to start demos with the CSP server.
apps/demos/utils/visual-tests/inject/csp-listener.js Captures securitypolicyviolation events in the browser during tests.
apps/demos/utils/server/csp-server.js Implements CSP Report-Only middleware, report endpoints, and nonce injection for framework demos.
apps/demos/utils/server/csp-report-summary.js Adds a CLI summarizer for CSP violation reports.
apps/demos/utils/server/csp-check.js Adds a headless Chrome crawler to visit demo pages and query CSP violations from the server.
apps/demos/testing/common.test.ts Optionally injects the CSP listener and writes JSONL violation output during TestCafe runs.
apps/demos/package.json Adds scripts to run CSP server/check/report and a CSP-enabled TestCafe run.
apps/demos/.gitignore Ignores generated csp-reports/ output.
.github/workflows/visual-tests-demos.yml Adds CI jobs to run CSP checks per framework and publish a consolidated report.

Copilot AI review requested due to automatic review settings March 4, 2026 09:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Copilot AI review requested due to automatic review settings March 4, 2026 10:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Comment on lines +51 to +64
function visitPage(url) {
return new Promise((resolve) => {
const child = execFile(CHROME_PATH, [
'--headless=new',
'--no-sandbox',
'--disable-gpu',
'--disable-software-rasterizer',
'--disable-dev-shm-usage',
'--screenshot=/dev/null',
'--window-size=100,100',
url,
], { timeout: 30000 }, () => resolve());
child.on('error', () => resolve());
});
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visitPage resolves even if Chrome fails to start or returns an error (both the execFile callback and the error event unconditionally call resolve()). This can lead to false-green CSP checks (no pages actually loaded → no reports collected). Consider rejecting on process errors / non-zero exit codes and failing the run (or at least tracking failed navigations and setting a non-zero exit code).

Copilot uses AI. Check for mistakes.
@pharret31 pharret31 requested a review from a team as a code owner March 4, 2026 11:57
Copilot AI review requested due to automatic review settings March 4, 2026 12:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Copilot AI review requested due to automatic review settings March 4, 2026 14:20
@dmlvr dmlvr requested a review from a team as a code owner March 4, 2026 14:20
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comment on lines +1203 to +1211
- name: Start CSP Server
run: node apps/demos/utils/server/csp-server.js 8080 &

- name: Run CSP Check
working-directory: apps/demos
env:
CSP_FRAMEWORKS: ${{ matrix.FRAMEWORK }}
CHROME_PATH: google-chrome-stable
run: node utils/server/csp-check.js
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSP server is started in the background and the next step immediately runs the checker. Without a readiness check, this can race on slower runners and cause flaky failures. Add a short wait/health-check loop (e.g., poll /csp-violations until it responds) before running csp-check.js.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +55
const cspReportDir = join(__dirname, '..', 'csp-reports');
const cspReportFile = join(cspReportDir, 'csp-violations.jsonl');

const writeCspReport = (testName: string, framework: string, violations: any[]) => {
if (!violations.length) return;
if (!existsSync(cspReportDir)) {
mkdirSync(cspReportDir, { recursive: true });
}
for (const v of violations) {
const entry = {
test: testName,
framework,
...v,
};
appendFileSync(cspReportFile, `${JSON.stringify(entry)}\n`);
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSP reporting appends to a fixed csp-violations.jsonl file but never clears it, so rerunning tests can mix stale violations into a new run. Consider truncating/removing the file once at startup when CSP_REPORT=true (and batching writes per test to reduce synchronous I/O in large runs).

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 4, 2026 19:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

res.locals.cspNonce = nonce;
}

res.setHeader('Content-Security-Policy', buildCspHeader(demoKey, nonce, framework));
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server logs "CSP Report-Only mode enabled", but the middleware sets the enforcing Content-Security-Policy header. This is either misleading output or will unintentionally block resources during CSP runs. Use Content-Security-Policy-Report-Only if the intent is validation-only, or update the startup logs/comment to reflect enforced mode.

Suggested change
res.setHeader('Content-Security-Policy', buildCspHeader(demoKey, nonce, framework));
res.setHeader('Content-Security-Policy-Report-Only', buildCspHeader(demoKey, nonce, framework));

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +94
const child = execFile(CHROME_PATH, [
'--headless=new',
'--no-sandbox',
'--disable-gpu',
'--disable-software-rasterizer',
'--disable-dev-shm-usage',
'--dump-dom',
'--virtual-time-budget=5000',
'--window-size=100,100',
url,
], { timeout: 30000 }, () => resolve());
child.on('error', (err) => {
reject(new Error(`Failed to launch Chrome at "${CHROME_PATH}": ${err.message}`));
});
});
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visitPage resolves successfully even when Chrome exits with an error (non-zero exit code, navigation failure, timeout). Because the execFile callback ignores the error argument, CSP checks can silently pass while pages fail to load. Reject the promise when the callback receives an error so the job fails on real page-load issues.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +55
const isCspEnabled = () => process.env.CSP_REPORT === 'true';

const cspReportDir = join(__dirname, '..', 'csp-reports');
const cspReportFile = join(cspReportDir, 'csp-violations.jsonl');

const writeCspReport = (testName: string, framework: string, violations: any[]) => {
if (!violations.length) return;
if (!existsSync(cspReportDir)) {
mkdirSync(cspReportDir, { recursive: true });
}
for (const v of violations) {
const entry = {
test: testName,
framework,
...v,
};
appendFileSync(cspReportFile, `${JSON.stringify(entry)}\n`);
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSP report file is only appended to and never cleared/truncated when CSP reporting is enabled. Re-running the TestCafe suite locally (or in any environment that reuses the workspace) will accumulate stale violations and make the summary inaccurate. Consider deleting/truncating csp-violations.jsonl once at the start of the run (when CSP_REPORT=true) before appending entries.

Copilot uses AI. Check for mistakes.
Comment on lines +344 to +349
const fileSystemPath = resolve(demosBaseDir, widget, name, approach, indexFileName);

if (!fileSystemPath.startsWith(demosBaseDir)) {
response.status(403).send('Forbidden');
return;
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path traversal guard uses fileSystemPath.startsWith(demosBaseDir), which can be bypassed by paths like .../DemosX/... when .. segments are used in params (prefix match). Prefer a path.relative(demosBaseDir, fileSystemPath) check that rejects absolute paths and any relative path starting with .. to robustly prevent escaping the demos directory.

Copilot uses AI. Check for mistakes.
Comment on lines +1203 to +1212
- name: Start CSP Server
run: node apps/demos/utils/server/csp-server.js 8080 &

- name: Run CSP Check
working-directory: apps/demos
env:
CSP_FRAMEWORKS: ${{ matrix.FRAMEWORK }}
CHROME_PATH: google-chrome-stable
run: node utils/server/csp-check.js

Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in the jQuery job: the CSP server is started in the background and the check runs immediately. Add a readiness wait (poll an endpoint) to avoid intermittent connection errors in CI.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants