---
url: /personal-data.md
---
# Personal data

The personal data an app gets from Trek's generators: where it's kept, why, for how long, what deletes it, and which third parties receive some of it. It's what a GDPR record of processing activities (Art. 30) and security questionnaires ask for. The app adds its own models to it, and the organization running it decides the legal bases and informs the people concerned.

## In the database

| Table | Personal data | Why | Kept | Deleted by |
| --- | --- | --- | --- | --- |
| `users` | Name, email, role, language and theme; password hash; last activity; the current and previous sign-ins (date, IP address, user agent); failed sign-ins and lockout; password changes | Signing in to the admin, and showing users their sign-ins to spot one that wasn't theirs | While the account exists | Deleting the user |
| `user_sessions` | IP address and its host name, user agent, last activity | One row per signed-in browser, listed to its user, who can end it | Until signing out, or 48 hours idle, 30 days in all | Signing out; [`Trek::PurgeExpiredSessionsJob`](/background-jobs#scheduled-jobs-cron), every night; deleting the user |
| `user_passkeys` | The browser it was added from, its provider, last use | Signing in with a passkey | While the passkey exists | Its user removing it; deleting the user |
| `user_previous_passwords` | Hashes of the 4 previous passwords | Refusing a password used recently | Until replaced by newer ones | Changing the password (only the last ones stay); deleting the user |
| `audit_logs` | The author (type and id), IP address, user agent, path, the parameters' names | Investigating an incident or a misuse (see [Audit log › Personal data](/reference/generators/audit-log#personal-data)) | 6 months | [`Trek::AuditLog::PurgeJob`](/reference/generators/audit-log#purging-old-rows), every night |

With [public accounts](/reference/generators/public-accounts) (`trek:public_accounts Member`):

| Table | Personal data | Why | Kept | Deleted by |
| --- | --- | --- | --- | --- |
| `members` | Email, name, last activity; with passwords, the password hash, an email waiting for confirmation, failed sign-ins and lockout | The account | While the account exists | Its holder deleting it; the app (see [below](#retention-to-decide)) |
| `member_sessions` | IP address and its host name, user agent, last activity | One row per signed-in browser or device | Until signing out, or 30 days idle, 1 year in all | Signing out; `Trek::PurgeExpiredSessionsJob`, every night; deleting the account |
| `member_sign_in_codes` | The email a code was sent to | Signing in with a code | 10 minutes of validity | The next code requested for that email, which also deletes every expired one; `Trek::PurgeExpiredSignInCodesJob`, every night; deleting the account |

## In the browser

* `session_id` (and `member_session_id` with public accounts): a signed cookie naming the session, until signing out or the session's expiry.
* `trek_known_users`: an encrypted cookie holding the ids of the last 20 users who signed in from this browser, for 13 months after the last of those sign-ins, to email a user when they sign in from a new browser. It serves to secure signing in.

## Uploads

Files are stored in the S3-compatible bucket, with metadata in the record's column: the original file name, size, MIME type, and for an image its dimensions and [blurhash](https://blurha.sh). An image's own metadata (EXIF, with the GPS location and the camera's serial number, XMP, IPTC) is stripped on upload, as the file is public: see [Uploaders](/reference/uploaders#image-metadata). Images uploaded before Trek stripped it still carry it, until `bin/rails trek:attachments:strip_image_metadata` strips them in place; their [sizes](/reference/uploaders#sizes) don't.

A file is deleted with its record, and with it its sizes (thumbnails…), stored under `derivatives/`; the sizes of images deleted before Trek did that stay in the bucket, at URLs nobody links to any more. A file uploaded to the cache but never attached, e.g. from a form left without saving, stays in the bucket's `cache/` prefix: a lifecycle rule on the bucket deleting `cache/` objects after a day or two takes care of it.

## Deleting a user or an account

Deleting a user deletes their sessions, passkeys and previous passwords. Their audit log rows stay, with the id the user had but not their name, until the purge deletes them: they record what was done, which an investigation still needs. Pages, fragments and their versions record no author.

Deleting a public account deletes its sessions and sign-in codes; its audit log rows stay until the purge, as for a user.

## Third parties

What a default app sends, and to whom:

| Service | Receives | When |
| --- | --- | --- |
| The hosting | Everything: the database, the logs (which hold the requests' IP addresses) | Always |
| S3-compatible storage | The uploaded files | Always |
| [Postmark](https://postmarkapp.com) | The emails sent: their address and content, e.g. password reset links, sign-in codes, and the new sign-in alert, which gives the date, the browser and the IP address | When an email is sent |
| [AppSignal](https://www.appsignal.com) | Errors and performance: the request's path and parameters as filtered by `filter_parameters` (passwords, emails, tokens…), and the signed-in user's id; CSP violation reports (a page's path, a blocked origin, the file and line that caused it, the start of a blocked inline script, the browser's user agent) | On every request it samples, and on every error |
| The DNS resolver | A session's IP address, for its host name (reverse lookup) | After each sign-in |
| [Have I Been Pwned](https://haveibeenpwned.com/API/v3#PwnedPasswords) | The first 5 characters of the new password's SHA-1 hash, never the password | When a password is set, if `password_check_pwned` is on (off by default) |

The admin's fonts are served by the app itself: its pages make no request to a font provider.

## Retention to decide

What Trek leaves to each app:

* **Inactive accounts.** Users and public accounts are kept until deleted. `Member.inactive_since(time)` lists the accounts unused since `time`, to delete them on the schedule the organization sets.
* **The uploads' cache**, as [above](#uploads).
* **The logs**, kept by the hosting: set their retention there.
