Skip to content

nginx/csp: allow frontend to include itself in an iframe - #2155

Draft
alxndrsn wants to merge 10 commits into
getodk:nextfrom
alxndrsn:csp-download-submissions
Draft

nginx/csp: allow frontend to include itself in an iframe#2155
alxndrsn wants to merge 10 commits into
getodk:nextfrom
alxndrsn:csp-download-submissions

Conversation

@alxndrsn

@alxndrsn alxndrsn commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Closes #2080

What has been done to verify that this works as intended?

  • a whole new test
  • all the existing tests

Why is this the best possible solution? Were any other approaches considered?

  • confirm: restricting frame-ancestor to more specific routes is impractical/impossible
  • not terrible to allow inclusion of self

How does this change impact users? Describe intentional behavior changes from code updates. What are the regression risks?

Should just fix a bug - low risk of creating new ones.

Does this change require updates to documentation? If so, please file an issue here and include the link below.

No.


// when
await page.evaluate(() => {
/* global document */

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

eslint complains if this is scoped to every evaluate block

'disposition': 'enforce',
'blocked-uri': 'inline',
'line-number': 5,
'line-number': 4,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

unclear why this changes when the CSP changes 馃し

@alxndrsn
alxndrsn marked this pull request as ready for review August 24, 2026 13:15
@alxndrsn
alxndrsn requested a review from matthew-white August 24, 2026 13:15

@matthew-white matthew-white left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think frame-ancestors needs to change in a different part of the CSP. The new tests look great though! I think those are still useful.

"default-src 'report-sample' 'none'; connect-src 'self' https:; font-src 'self' data:; form-action 'self'; frame-ancestors 'self'; frame-src 'self' https://getodk.github.io/central/; img-src blob: data: https:; manifest-src 'self'; media-src blob:; object-src 'none'; script-src 'report-sample' 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; worker-src 'report-sample' blob: data:; report-uri /csp-report";

default
"default-src 'report-sample' 'none'; connect-src 'self' ${SENTRY_DSN_FRONTEND_ROOT} https://translate.google.com https://translate.googleapis.com; font-src 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'self' https://getodk.github.io/central/; img-src data: https:; manifest-src 'self'; media-src 'none'; object-src 'none'; script-src 'report-sample' 'self'; style-src 'report-sample' 'self'; style-src-attr 'unsafe-inline'; worker-src 'report-sample' blob:; report-uri /csp-report";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

/blank.html is already allowed to be shown in an iframe:

add_header Content-Security-Policy "default-src 'report-sample' 'none'; connect-src https://translate.google.com https://translate.googleapis.com; form-action 'self'; frame-ancestors 'self'; img-src http://${DOMAIN}/favicon.ico https://translate.google.com; report-uri /csp-report" always;

It's more so that we need to show Backend JSON in an iframe. That's because the iframe form submits to a Backend URL. So I think it's this line that needs to change:

"" "default-src 'report-sample' 'none'; form-action 'none'; frame-ancestors 'none'; img-src http://${DOMAIN}/favicon.ico; report-uri /csp-report";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The Backend JSON is only shown in an error case (e.g., for a wrong password). In the non-error case, Backend returns the .csv/.zip response: the download begins.

});

test.describe('bug: https://github.com/getodk/central/issues/2080', () => {
test('allows central-frontend to show itself in a frame', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
test('allows central-frontend to show itself in a frame', async ({ page }) => {
test('allows central-backend to show itself in a frame', async ({ page }) => {

// when
await page.evaluate(() => {
const frame = document.createElement('iframe');
frame.src = '/';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it should be a Backend URL instead, e.g.:

Suggested change
frame.src = '/';
frame.src = '/v1/projects';

@alxndrsn

Copy link
Copy Markdown
Contributor Author

I've adapted the tests here and expanded them to cover a wider range of CSPs & scenarios. PR at #2159. Once that's merged, this one can be updated to be clearer on what it's changing and why.

@alxndrsn
alxndrsn marked this pull request as draft August 25, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

There is no error message informing user about entering wrong password when downloading submissions from encrypted project

2 participants