# Security Model for Survey Submissions This document explains the protections used during form rendering, submission, and result storage. It covers the checks performed in `@@viewer`, `@@get-form-json`, and `@@save-poll`, plus the configuration surface at both global and per-survey levels. ## Scope These safeguards apply to: - Rendering forms for end users (`@@viewer` and `@@get-form-json`). - Submitting survey results (`@@save-poll`). - Storing results in ZODB or SQL backends. ## Global Configuration (Plone Registry) All options below live in `IFormsSettings` and affect every survey. ### Authenticity token (JWT) options These settings require a short-lived, signed token for submissions. Tokens are embedded in the rendered form and must be sent back on submit. - `authenticity_token_enabled` (Bool, default: `True`) - When enabled, submissions must include a valid JWT in `auth_token`. - When disabled, JWTs are not required. - `authenticity_token_secret` (Password, default: empty) - HMAC secret used to sign and verify JWTs. - Required if JWT enforcement is enabled. - `authenticity_token_ttl_seconds` (Int, default: `600`, min: `60`) - Token lifetime in seconds. - Enforced via the `exp` claim. - `authenticity_token_issuer` (Text, default: `zopyx.surveyjs`) - Expected `iss` claim value. - `authenticity_token_audience` (Text, default: `zopyx.surveyjs`) - Expected `aud` claim value. - `authenticity_token_cache_path` (Text, default: `var/token_cache.db`) - Filesystem location for diskcache. - Tracks issued and received tokens for replay prevention. ### Result storage options (security relevant) - `result_storage_backend` (Choice, default: `zodb`) - Selects the storage backend. Validation is unchanged, but persistence and operational controls differ. - `database_uri` (Text, default: `sqlite:///var/surveyjs-results.db`) - Database URL for relational result storage. ## Survey-Specific Configuration These options are set on each Survey content item. ### Form access policy - `access_mode` (Choice, default: `public`) - `public`: Form is accessible without a URL access token. - `trusted`: Form requires a trusted access token for rendering and submit. - `trusted_access_ttl_hours` (Int, default: `168`, min: `1`) - Lifetime of trusted access tokens in hours. - Drives diskcache expiration and `expires_at` metadata. ### Submission validation and limits - `max_payload_size_mb` (Int, default: `1`) - Maximum request payload size (bytes derived from MB). - Checked before JSON parsing. - `force_server_side_validation` (Bool, default: `True`) - Runs an external validator on every submission. - Failed validation rejects the submission. ## Security Measures: How They Work ### 1. CSRF protection (Plone default) Submission requests include the `_authenticator` token (Plone CSRF). The client sends it and Plone validates it to block cross-site request forgery for authenticated sessions. Anonymous submissions may still succeed if the view is public and Plone accepts the authenticator. ### 2. Trusted access token (URL-based access control) #### Purpose Limits *form rendering and submission* to users who possess a trusted URL token. #### Token type and storage - Opaque short token generated server-side. - Stored in diskcache under `trusted:` with metadata: - `form_id`: survey identifier - `form_version`: version at issuance time - `issued_at`: ISO timestamp - `expires_at`: ISO timestamp - `state`: currently `ISSUED` #### Enforcement points - `@@get-form-json`: checks for `access_token` in query or form. - `@@save-poll`: checks for `access_token` in form data. - PDF submission path: same trusted access check. #### Failure behavior - Missing or invalid token -> HTTP 403 with JSON error. - Cache unavailable -> HTTP 503 with JSON error. - Form mismatch -> HTTP 403 with JSON error. ### 3. Authenticity token (JWT with replay protection) #### Purpose Confirms submissions were rendered by the server (short-lived JWT) and reduces replay risk. #### Token creation - Generated for each form render via `build_auth_token`. - Embedded in the page (`AUTH_TOKEN` JS variable). - Claims include: - `iss` / `aud` - `exp` / `nbf` / `iat` - `form_id` - `form_version` - `jti` (token id) #### Validation - `@@save-poll` and PDF submission path verify JWT signature and claims. - Invalid or expired tokens reject the submission with HTTP 403. #### Replay protection - diskcache keys: - `issued:` set at issuance - `received:` set at first submission - If `received:` is present, submission is rejected as replay. - Cache TTL: 24 hours for authenticity tokens. ### 4. Payload size enforcement - `Content-Length` and raw payload size are checked. - Requests above `max_payload_size_mb` are rejected with HTTP 413. - Protects against oversized payloads and memory pressure. ### 5. Server-side validation - If `force_server_side_validation` is enabled, an external validator runs on every submission. - Validation failures reject the submission (status per validator). ## Token Lifetimes and Defaults Authenticity JWT (global): - Default: 600 seconds (10 minutes). - Min: 60 seconds. Trusted access token (per survey): - Default: 168 hours (7 days). - Min: 1 hour. Diskcache TTL: - Authenticity JWT replay cache: 24 hours. - Trusted access tokens: per-survey TTL.