Skip to content

Commit 60903fe

Browse files
tommoorclaude
andauthored
Allow passing CSP nonce to exported html (outline#12088)
* Allow passing CSP nonce to exported html * test: Add nonce regression test, drop options from tags Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent cbb5328 commit 60903fe

File tree

4 files changed

+45
-2
lines changed

4 files changed

+45
-2
lines changed

server/middlewares/csp.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,18 @@ const getBucketOrigin = () => {
3333
}
3434
};
3535

36+
interface CSPOptions {
37+
/** Additional origins to allow as script sources. */
38+
extraScriptSrc?: string[];
39+
}
40+
3641
/**
3742
* Create a Content Security Policy middleware for the application.
43+
*
44+
* @param options Optional configuration for the CSP middleware.
45+
* @returns A Koa middleware function that applies the CSP headers.
3846
*/
39-
export default function createCSPMiddleware() {
47+
export default function createCSPMiddleware(options?: CSPOptions) {
4048
// Construct scripts CSP based on options in use
4149
const defaultSrc: string[] = ["'self'"];
4250
const scriptSrc: string[] = [];
@@ -83,6 +91,7 @@ export default function createCSPMiddleware() {
8391
styleSrc,
8492
scriptSrc: [
8593
...uniq(scriptSrc),
94+
...(options?.extraScriptSrc ?? []),
8695
env.DEVELOPMENT_UNSAFE_INLINE_CSP
8796
? "'unsafe-inline'"
8897
: `'nonce-${ctx.state.cspNonce}'`,

server/models/helpers/DocumentHelper.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,32 @@ describe("DocumentHelper", () => {
100100
expect(result).toContain('<p dir="auto">This is a test paragraph</p>');
101101
});
102102

103+
it("should apply the cspNonce to the injected mermaid script", async () => {
104+
const document = await buildDocument({
105+
text: "```mermaid\ngraph TD;\nA-->B;\n```",
106+
});
107+
const result = await DocumentHelper.toHTML(document, {
108+
includeTitle: false,
109+
includeStyles: false,
110+
includeMermaid: true,
111+
cspNonce: "test-nonce-123",
112+
});
113+
expect(result).toMatch(/<script[^>]*nonce="test-nonce-123"/);
114+
expect(result).toContain('window.status = "ready"');
115+
});
116+
117+
it("should not set a nonce attribute when cspNonce is not provided", async () => {
118+
const document = await buildDocument({
119+
text: "```mermaid\ngraph TD;\nA-->B;\n```",
120+
});
121+
const result = await DocumentHelper.toHTML(document, {
122+
includeTitle: false,
123+
includeStyles: false,
124+
includeMermaid: true,
125+
});
126+
expect(result).not.toMatch(/<script[^>]*nonce="/);
127+
});
128+
103129
it("should render diff classes when changes provided", async () => {
104130
const doc1 = await buildDocument({ text: "Hello world" });
105131
const doc2 = await buildDocument({ text: "Hello modified world" });

server/models/helpers/DocumentHelper.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ type HTMLOptions = {
6868
baseUrl?: string;
6969
/** Changes to highlight in the document */
7070
changes?: readonly ExtendedChange[];
71+
/** CSP nonce to apply to injected inline scripts */
72+
cspNonce?: string;
7173
};
7274

7375
@trace()
@@ -257,12 +259,12 @@ export class DocumentHelper {
257259
centered: options?.centered,
258260
baseUrl: options?.baseUrl,
259261
changes: options?.changes,
262+
cspNonce: options?.cspNonce,
260263
});
261264

262265
addTags({
263266
collectionId: model instanceof Collection ? model.id : undefined,
264267
documentId: !(model instanceof Collection) ? model.id : undefined,
265-
options,
266268
});
267269

268270
if (options?.signedUrls) {

server/models/helpers/ProsemirrorHelper.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export type HTMLOptions = {
5050
baseUrl?: string;
5151
/** Changes to highlight in the document */
5252
changes?: readonly ExtendedChange[];
53+
/** CSP nonce to apply to injected inline scripts */
54+
cspNonce?: string;
5355
};
5456

5557
export type MentionAttrs = {
@@ -558,6 +560,10 @@ export class ProsemirrorHelper extends SharedProsemirrorHelper {
558560
const element = dom.window.document.createElement("script");
559561
element.setAttribute("type", "module");
560562

563+
if (options?.cspNonce) {
564+
element.setAttribute("nonce", options.cspNonce);
565+
}
566+
561567
// Inject Mermaid script
562568
if (mermaidElements.length) {
563569
element.innerHTML = `

0 commit comments

Comments
 (0)