Repository navigation
fix(forms): prevent stored XSS in admin submissions view and render it in the admin layout - #1100
Open
josevenceslau wants to merge 1 commit into
Conversation
…t in the admin layout
The GET /admin/forms/:id/submissions handler built a bare HTML page and
interpolated submission_data — public, user-supplied form input — directly
into the page via `${JSON.stringify(JSON.parse(sub.submission_data))}`, an
unescaped sink. A visitor can submit a form field containing markup/script
that then executes in the admin's browser when they open the submissions
list (stored XSS in the admin UI).
Move the page into a dedicated template (admin-forms-submissions.template.ts)
that renders inside the Catalyst admin layout like the other forms pages, and
escape every key and value (and the form display name) via the existing
escapeHtml util. Also surfaces the submission number and status columns that
were already stored but not shown.
Adds tests covering the escaping (script/img/attribute payloads), the
non-JSON fallback, and the empty state.
josevenceslau
requested a deployment
to
external
September 16, 2026 16:03 — with
GitHub Actions
Waiting
This branch is waiting to be deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GET /admin/forms/:id/submissions(packages/core/src/routes/admin-forms.ts) builds a bare HTML page and interpolatessubmission_data— public, user-supplied form input — straight into the page:This is an unescaped sink. A visitor can submit a form field containing markup/script (e.g. a
messagevalue of<img src=x onerror=…>or<script>…</script>), and it then executes in the admin's browser when they open the submissions list — a stored XSS in the admin UI, reachable by any anonymous form submitter. The page is also unstyled (a standalone<!DOCTYPE html>with inline CSS, no admin chrome), unlike every other admin forms page.Fix
admin-forms-submissions.template.ts, rendered inside the Catalyst admin layout viarenderTable— consistent withadmin-forms-list.template.tsand the rest of the admin.submission_data(and the form display name) through the existingescapeHtmlutil. Non-JSONsubmission_datafalls back to an escaped raw string.submission_numberandstatuscolumns that were already stored but never displayed.The route handler now just calls the template (net −34 lines).
Tests
src/__tests__/templates/admin-forms-submissions.test.ts(4 tests):<script>/<img onerror>/ attribute payloads in values, keys, and the form name;submission_datafallback;tsc --noEmitand ESLint pass; no existing tests reference the old page.Notes
No existing issue tracked this — happy to file one to link if the maintainers prefer. I have a follow-up (submission status workflow, delete, filters/search, audit) built on top of this in a downstream project; glad to open it as a separate feature PR if there's interest.