We only actively support the latest version of the software. All bug fixes and security updates will be made by patching or releasing new versions against the current latest version. No backporting will be carried out. With that said, the community is welcome to backport fixes and security updates to older versions, and we will be happy to review and release them.
Fresco is a single-tenant, single-trust-level application. Understanding this is important when reasoning about the codebase:
- Administrators authenticate (a password, optionally with TOTP as a second factor, or a passkey instead of a password) and are all fully trusted — there is no role hierarchy. Anyone who can sign in can manage every protocol, participant, and interview. "Any logged-in user can do X" is therefore by design, not a privilege-escalation flaw.
- Two-factor authentication can be made mandatory. Setting the
REQUIRE_TWO_FACTORenvironment variable totruerequires TOTP on every password-mode account. It is an environment variable rather than an app setting because there is no role hierarchy: a requirement any administrator could switch off from the dashboard would guarantee nothing. The User Management card only reports it.lib/auth/twoFactorPolicy.tsdecides per account whether the gate holds (the variable is set, setup is complete, the account has a password, and no verified TOTP credential exists), and the guards inlib/auth/guards.tsenforce it everywhere a session is checked:requirePageAuthredirects a gated account to/signin/two-factor-setupinstead of rendering any dashboard page, andrequireApiAuthrefuses every Server Action and session-authenticated route handler except the two TOTP setup actions (enableTotp,verifyTotpSetup), which opt in explicitly. Code that merely notices a signed-in researcher — the interview page, which lets a researcher past the participant-only restrictions, and the upload middleware — reads the session throughgetAdmittedSession, which returns nothing for a gated account. The gate waits for setup to complete because the first account is created part-way through the setup wizard, whose remaining steps run under that session; that account is gated on its way to the dashboard instead.disableTotprefuses while the variable is set. Administrator recovery (resetTotpForUser,resetAuthForUser) is deliberately still allowed — it is the way back in for a colleague who has lost their authenticator and recovery codes — and the reset account is gated again at its next sign-in. Passkey-mode accounts are exempt: a passkey replaces the password entirely, and Fresco treats it as satisfying the requirement.lib/auth/webauthn.tsrequires user verification (requireUserVerification: true), so a passkey already combines two distinct factors — possession of the authenticator and a biometric or PIN check performed by it — without a separate TOTP step. - Participants take interviews without authenticating. A participant is authorized purely by possession of their interview URL, which contains an unguessable interview id. Treat interview URLs as secrets: do not post them publicly, and Fresco does not write them to logs.
- Server Actions are session-gated, not network-gated. Next.js dispatches a Server Action by the function a request names — through its
Next-Actionheader, or through amultipart/form-databody when a plain HTML form is submitted without JavaScript — independent of which URL the request is sent to, and independent of whether that URL matches any route Fresco defines. An institutional reverse proxy that restricts researcher paths by URL (see the IT FAQ) does not on its own stop a valid session cookie from reaching a researcher-only action through a path it left public — the same limitation the FAQ already documents for/api/uploadthing, generalized to every Server Action.requireApiAuth()still gates the action itself; a stolen or leaked session, not the request's network origin, is what determines whether it succeeds. - Account creation has two paths. The unauthenticated setup signup is only available before the app is configured — once configured, the signup actions reject new accounts (the check is enforced inside the actions, not only on the setup page). Separately, authenticated administrators can create additional accounts at any time from the dashboard settings (User Management).
- Passwords are hashed with scrypt; recovery codes are stored hashed and are single-use; API tokens are stored only as a SHA-256 hash (the plaintext is shown once, at creation). TOTP secrets must remain readable to verify codes, so they are stored as-is — a copy of the database exposes the seed for future codes, which is part of why database-at-rest encryption matters (see below).
- Storage credentials (S3 access key/secret, the UploadThing token) must remain reversible for the app to use them, so when configured through the setup UI they are stored unencrypted in the database. If database-at-rest exposure is part of your threat model, configure storage through environment variables instead —
STORAGE_PROVIDER,S3_ENDPOINT,S3_PUBLIC_URL,S3_BUCKET,S3_REGION,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY, orUPLOADTHING_TOKEN. Env-provided values are read from the environment and never written to the database, and the setup UI locks the corresponding fields. See thedocker-compose.*.ymldeployment files.
Responses carry X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Strict-Transport-Security, and Referrer-Policy: strict-origin-when-cross-origin on every route, including /interview/* and /onboard/*. Those URLs carry the interview id, which is the participant's unauthenticated access capability, and this policy keeps the path inside the origin: a cross-origin request carries only the scheme and host in its Referer header, an HTTPS→HTTP downgrade carries nothing, and the full URL is sent only to same-origin requests, which already know the id. Sending the origin is also what lets researchers use URL-restricted Mapbox tokens with Fresco — Mapbox evaluates URL restrictions from the Referer header and rejects requests that omit it, so a stricter no-referrer policy would make every Geospatial stage fail with such a token while protecting nothing more. User-uploaded assets are served with a validated content type and are forced to download (rather than render inline) for script-capable types such as SVG/HTML. Terminate TLS in front of the app. The bundled production Compose files do this with Traefik and enforce Strict-Transport-Security across the entire HTTPS entrypoint, including proxy-generated redirects and storage responses.
To let us know about a security issue you have found. please email info@networkcanvas.com. Although we do not have the resources to offer any kind of reward, we would be extremely grateful for any instances of responsible disclosure that enable the community of network researchers to be more safe when collecting data using our software.