Forms Public Submissions
Public form submissions are anonymous by default and use a separate abuse protection path from authenticated app routes.
- IP addresses are never stored in plaintext. The submission path stores only a
salted
ipHashinform_responses.respondent_meta. userAgentandipHashare retained for 90 days for abuse investigation.form_respondent_meta_retention_cleanupruns daily and removesipHashanduserAgentfrom responses older than 90 days, preserving the response body and addingretentionRedactedAt.- Cloudflare Turnstile verification is always part of public submission.
The web container must receive the browser-visible
WEB_PUBLIC_TURNSTILE_SITE_KEYruntime key; the same environment-neutral image renders the widget with the staging or production value from its HTML bootstrap, sends the single-use token, and resets the widget after every submit attempt. Production requiresCLOUDFLARE_TURNSTILE_SECRET_KEYorTURNSTILE_SECRET_KEYat startup. Staging without either credential logs a deployment warning and rejects submissions with503 FORMS_CAPTCHA_UNAVAILABLE; local tests inject a fake verifier at the verification seam rather than bypassing captcha behavior.
Limits
Section titled “Limits”Forms use conservative defaults and allow environment overrides:
FORMS_MAX_PER_USER: active forms per owner, default50.FORMS_MAX_FIELDS_PER_FORM: active fields per form, default100.FORMS_MAX_RESPONSES_PER_FORM: hard cap per form, default10000.FORMS_PUBLIC_RESPONSE_BODY_LIMIT_BYTES: public submission body limit, default65536.
Display-only image fields count toward the active field limit. They must have
one managed imageAssetId, cannot be required, never accept an answer, and are
omitted from response CSV, Workbook, and response-PDF columns. A form may also
have one managed cover image. Blank-form PDF export embeds the cover and
display images from server-read bytes.
Availability and revocation
Section titled “Availability and revocation”Public form publishing is part of the normal form permission model and is not controlled by a deployment or build flag. Only the owner can publish, close, reopen, or rotate the public token. A form is anonymously readable only while it is published, unarchived, within its optional open/close schedule, and below its response cap. Closing, archiving, or rotating the token invalidates the old public entry point.
Immutable publication and response meaning
Section titled “Immutable publication and response meaning”The normalized Form and field rows are an editable draft. Publish creates an
immutable form_revisions definition and atomically advances
forms.published_revision_id. Public GET and answer validation read that
revision, so editing the draft after publication does not change the live
responder contract.
Every accepted response stores the exact published form_revision_id in the
same transaction as validation and response-count advancement. Response
detail, CSV, Workbook, PDF, analytics, and field-label resolution prefer that
bound definition. respondent_meta.fieldSnapshot remains a compatibility
fallback for legacy-incomplete evidence and is not removed by this rollout.
Republish creates a new immutable revision. Closing/reopening does not rewrite it. Restoring an older revision changes only the draft and keeps the current published revision live until an explicit Publish; no restore rewrites an existing response. See Office version history for backfill and retention semantics.
Managed images and author preview
Section titled “Managed images and author preview”Cover and display-image IDs are part of the immutable definition hash. Each
revision also pins its referenced assets in form_revision_image_assets, so
replacing a draft image cannot break the current public form or a historical
revision. Anonymous image delivery uses the public image-asset route but grants
access only when the asset belongs to the form scope, appears in the current
published revision, and the form passes the same publication, archive,
schedule, and response-cap checks as the public schema endpoint.
Authenticated editors use
GET /api/forms/{id}/preview?revision=draft|published|<revision UUID>. The
response has the public schema shape plus bounded preview metadata. The Web
route /office/forms/{id}/preview and the public page share the same respondent
renderer. Preview submission is local-only, never inserts form_responses,
and does not increment public request or rejection metrics. Legacy-incomplete
response revisions are rejected rather than rendered as a misleading form.
Monitoring
Section titled “Monitoring”Public forms expose bounded-label Prometheus counters for launch dashboards and alerts:
arinova_public_form_requests_total{endpoint,outcome}tracks schema and submit traffic for QPS and success/reject/discard ratios.arinova_public_form_rejects_total{reason}tracks rejection reasons without form IDs or user identifiers.arinova_public_form_honeypot_hits_totaltracks silent honeypot discards.arinova_public_form_response_rate_alerts_totalincrements when a single form crosses the per-minute abnormal response-rate threshold; the server also emits apublic_form_response_rate_alertwarning with the form ID for incident triage.
Build a7f47a5ca54ddcf7806cd48b81ce1b9827042766