Site builder: security & data-loss hardening + unified asset picker + audit backlog #3

Merged
jknapp merged 61 commits from builder-hardening-2026-07 into main 2026-07-13 01:13:28 +00:00
2 changed files with 30 additions and 1 deletions
Showing only changes of commit f0728a9070 - Show all commits
@@ -41,3 +41,25 @@ describe('ContactForm.toHtml relay wiring', () => {
expect(html).toContain('Name'); expect(html).toContain('Name');
}); });
}); });
describe('ContactForm.toHtml successMessage', () => {
// The published form-sender relay (form-sender/app/submit.php) delivers
// success via a full-page 303 redirect to thankYouUrl or a hosted
// thanks.php page -- there is no in-page JS to reveal an inline success
// element. So successMessage is emitted as a forward-compatible data
// attribute for a future AJAX/JS submission mode, not a live DOM element.
test('with successMessage set: emits it as an escaped data attribute on the form', () => {
const { html } = toHtml({ successMessage: "We'll be in touch!", fields: [] }, '');
expect(html).toContain('data-whp-success-message="We'll be in touch!"');
});
test('without successMessage: no data attribute emitted', () => {
const { html } = toHtml({ fields: [] }, '');
expect(html).not.toContain('data-whp-success-message');
});
test('escapes attribute-breakout attempts in successMessage', () => {
const { html } = toHtml({ successMessage: 'x" onerror="alert(1)', fields: [] }, '');
expect(html).not.toContain('onerror="alert(1)"');
});
});
+8 -1
View File
@@ -211,8 +211,15 @@ ContactForm.craft = {
const { marker, actionAttr, honeypot } = relayFormWiring(props.recipientEmail, props.thankYouUrl, props.formAction); const { marker, actionAttr, honeypot } = relayFormWiring(props.recipientEmail, props.thankYouUrl, props.formAction);
// The form-sender relay delivers success via a full-page 303 redirect
// (to thankYouUrl or a hosted thanks.php page) -- there is no in-page JS
// that reveals an inline success element today. Emit successMessage as a
// forward-compatible data attribute so a future AJAX/JS submission mode
// can read it, without implying a live mechanism that doesn't exist yet.
const successAttr = props.successMessage ? ` data-whp-success-message="${escapeAttr(props.successMessage)}"` : '';
return { return {
html: `${marker}<form action="${actionAttr}" method="POST"${formStyle ? ` style="${formStyle}"` : ''}> html: `${marker}<form action="${actionAttr}" method="POST"${successAttr}${formStyle ? ` style="${formStyle}"` : ''}>
${honeypot ? ` ${honeypot}\n` : ''}${fieldsHtml ? ` ${fieldsHtml}\n` : ''} <button type="submit"${btnStyle ? ` style="${btnStyle}"` : ''}>${escapeHtml(props.submitText || 'Send Message')}</button> ${honeypot ? ` ${honeypot}\n` : ''}${fieldsHtml ? ` ${fieldsHtml}\n` : ''} <button type="submit"${btnStyle ? ` style="${btnStyle}"` : ''}>${escapeHtml(props.submitText || 'Send Message')}</button>
</form>`, </form>`,
}; };