Compare commits

..
3 Commits
4 changed files with 29 additions and 6 deletions
+14 -2
View File
@@ -1,6 +1,16 @@
/** Field block types that don't carry a submittable value. */ const NON_DATA_BLOCKS = new Set([ /** Field block types that don't carry a submittable value. */ const NON_DATA_BLOCKS = new Set([
'message' 'message'
]); ]);
/**
* Keys injected by the captcha widget itself, not by the form definition.
* Cloudflare Turnstile adds a hidden <input name="cf-turnstile-response"> after
* a successful challenge; reCAPTCHA adds 'g-recaptcha-response'. Since the
* plugin drives Turnstile end-to-end, these are legitimate artifacts — they
* must not count as "unknown fields" and trip the anti-tampering check.
*/ const CAPTCHA_KEYS = new Set([
'cf-turnstile-response',
'g-recaptcha-response'
]);
/** Hard ceiling on a single field's length, independent of the form config. */ const MAX_FIELD_LENGTH = 5000; /** Hard ceiling on a single field's length, independent of the form config. */ const MAX_FIELD_LENGTH = 5000;
/** /**
* Checks submitted data against the form's own definition, rather than trusting * Checks submitted data against the form's own definition, rather than trusting
@@ -60,8 +70,10 @@
} }
} }
// Reject outright if the payload carried keys the form doesn't define — a // Reject outright if the payload carried keys the form doesn't define — a
// sign the request wasn't produced by the rendered form. // sign the request wasn't produced by the rendered form. Captcha keys are
const unknownKeys = Object.keys(data).filter((k)=>!known.has(k)); // exempt: the widget injects them into the rendered form, so they're expected,
// not tampering.
const unknownKeys = Object.keys(data).filter((k)=>!known.has(k) && !CAPTCHA_KEYS.has(k));
if (unknownKeys.length > 0) { if (unknownKeys.length > 0) {
return { return {
kind: 'unknown_fields', kind: 'unknown_fields',
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@intecion/ipal-kit", "name": "@intecion/ipal-kit",
"version": "1.0.2", "version": "1.0.3",
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.", "description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
+13 -2
View File
@@ -34,6 +34,15 @@ export type FormValidationResult =
/** Field block types that don't carry a submittable value. */ /** Field block types that don't carry a submittable value. */
const NON_DATA_BLOCKS = new Set(['message']) const NON_DATA_BLOCKS = new Set(['message'])
/**
* Keys injected by the captcha widget itself, not by the form definition.
* Cloudflare Turnstile adds a hidden <input name="cf-turnstile-response"> after
* a successful challenge; reCAPTCHA adds 'g-recaptcha-response'. Since the
* plugin drives Turnstile end-to-end, these are legitimate artifacts — they
* must not count as "unknown fields" and trip the anti-tampering check.
*/
const CAPTCHA_KEYS = new Set(['cf-turnstile-response', 'g-recaptcha-response'])
/** Hard ceiling on a single field's length, independent of the form config. */ /** Hard ceiling on a single field's length, independent of the form config. */
const MAX_FIELD_LENGTH = 5000 const MAX_FIELD_LENGTH = 5000
@@ -95,8 +104,10 @@ export async function validateSubmission(
} }
// Reject outright if the payload carried keys the form doesn't define — a // Reject outright if the payload carried keys the form doesn't define — a
// sign the request wasn't produced by the rendered form. // sign the request wasn't produced by the rendered form. Captcha keys are
const unknownKeys = Object.keys(data).filter((k) => !known.has(k)) // exempt: the widget injects them into the rendered form, so they're expected,
// not tampering.
const unknownKeys = Object.keys(data).filter((k) => !known.has(k) && !CAPTCHA_KEYS.has(k))
if (unknownKeys.length > 0) { if (unknownKeys.length > 0) {
return { kind: 'unknown_fields', ok: false, reason: 'invalid' } return { kind: 'unknown_fields', ok: false, reason: 'invalid' }
} }