Saltcorn: The as_link Field View Puts Any URL Scheme into the href
The HTML filter neutralises a dangerous scheme only inside a tag; a bare URL string passes through unchanged.
Advisory ID: TP-2026-094
Product: Saltcorn (no-code builder for database applications, Node.js)
Vulnerability type: Stored cross-site scripting (CWE-79)
CVE: requested (GitHub CNA), assignment pending
CVSS 3.1: 9.3 (Critical) · CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Affected versions: <= 1.6.0
Vendor advisory: GHSA-j86v-82rx-c23w
Fixed in: 1.6.1 and 1.5.9
Reported: 27 June 2026
Summary
Saltcorn offers the as_link field view for text fields, which shows the stored value as a link. The value goes into the href attribute with no validation of the URL scheme. Anyone who may write the field stores javascript: plus a payload, and as soon as another user opens a view that renders the field that way, the page contains such a link. One click runs the script in Saltcorn's origin, and the application sends no Content-Security-Policy. Write access can be anonymous, because a public edit form on a table whose write role is set accordingly accepts submissions without a login.
Root cause
The as_link field view builds a({ href: text(s || "") }, ...) from the stored value, with the href assignment on line 1062 (packages/saltcorn-data/base-plugin/types.ts:1058-1067). The text() function used there is the js-xss filter (packages/saltcorn-markup/extra_tags.ts:47), which strips a dangerous URL scheme only when it appears inside a recognised HTML tag attribute. A bare URL string carries no tag, so the filter returns it unchanged. The attribute serialiser then escapes only ", < and >, not the scheme. Nothing on this path checks whether the scheme is permitted, and the application sends no Content-Security-Policy by default.
Proof of Concept
# 1. a table with a text field, min_role_write set to the public role,
# plus an edit view and a show or list view that renders
# the field with the as_link field view.
# 2. submit anonymously, as the field value:
javascript:fetch('/useradmin/set-random-password/1')
# 3. the view renders:
<a href="javascript:fetch('/useradmin/set-random-password/1')">...</a>
# one click by the administrator runs the script in their session.
Not affected are fields in the default as_text field view, which outputs the value HTML-escaped.
Impact
- Script execution in Saltcorn's origin in the clicking user's session, with no account on the attacker's side.
- Account takeover through the user administration endpoints once the victim is an administrator.
- Persistence, since the value sits in the database and is rendered again on every visit to the view.
- Typical affected applications are public submission forms, link directories and a profile field for one's own website.
References
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.
