> Technical security measures in EthicsPortal, including encryption, anonymity, access control, audit logging, and infrastructure details for compliance review.

Source: https://ethicsportal.eu/security/
Updated: 2026-08-24

---

# Security

EthicsPortal handles sensitive whistleblower data. This page documents the specific technical and organizational measures we have in place. It is written for compliance officers, DPOs, and legal teams evaluating the platform.

Last updated: 2026-05-17.

---

## Data encryption

All sensitive fields are encrypted at rest using Rails ActiveRecord Encryption with **non-deterministic encryption** (each encryption produces a unique ciphertext, preventing pattern analysis).

| Field | Encrypted | Deterministic |
|---|---|---|
| Report description | Yes | No |
| Reporter name | Yes | No |
| Reporter contact details | Yes | No |
| Structured intake answers (relationship, source, timing, prior reporting, retaliation concern) | Yes | No |
| Message body (reporter--handler communication) | Yes | No |
| Internal case notes | Yes | No |
| Handler two-factor secrets and backup codes | Yes | No |

Non-deterministic encryption means these fields cannot be queried by value at the database level. Even with full database access, an attacker cannot search for a specific reporter name across records.

All connections to EthicsPortal use **HTTPS/TLS**. Unencrypted HTTP requests are redirected.

---

## Anonymity and privacy

### IP anonymization

Portal routes (report submission, case lookup, messaging) use a one-way SHA256 hash of the request IP solely for rate limiting. The hash is not reversible --- it is not possible to recover the original IP from the stored value.

At the application layer, the reporter's raw IP address is not stored in the database, and application logs for portal routes are scrubbed to protect whistleblower anonymity.

### File metadata stripping

Uploaded files are automatically stripped of identifying metadata **before** storage:

| File type | Metadata removed | Method |
|---|---|---|
| Images (JPEG, PNG, TIFF, WebP) | EXIF data: GPS coordinates, camera model, device serial number, author, timestamps | Vips image processing |
| PDF documents | Author, creator application, modification history | exiftool in the standard production setup |
| Video files | GPS, device info, recording software | exiftool in the standard production setup |
| Audio files | Recording device, GPS, software tags | exiftool in the standard production setup |

Metadata removal is performed server-side before storage. For file types handled by `exiftool`, this depends on the standard production tooling being present.

### Virus scanning

All uploaded files are automatically scanned for malware using [ClamAV](https://www.clamav.net), an open-source antivirus engine. Scanning happens server-side in a background process after upload. Files that have not passed scanning are blocked from delivery, and infected files are removed automatically.

Files are scanned on EthicsPortal infrastructure --- no file data is sent to third-party scanning services.

### Handler anonymity

Whistleblowers never see the real names or email addresses of the people handling their report. All messages from handlers are displayed as **"Case handler"**. This protects handler identity and prevents social engineering.

### No tracking

EthicsPortal does not use third-party tracking cookies, advertising pixels, or fingerprinting scripts. We use [Cloudflare Web Analytics](https://www.cloudflare.com/web-analytics/) on marketing pages only --- it is cookie-free, collects no personal data, and is fully GDPR-compliant. The whistleblower portal itself has no analytics.

### Current assurance status

EthicsPortal does not currently claim accredited ISO 27001 certification on this site. It also does not currently publish an independent third-party audit of the anonymity architecture. If that changes, the scope and date will be published here.

### Security review materials

Customers that require procurement or legal review materials can request them during procurement. Available materials may include a signed DPA, registry and tax evidence, a completed security questionnaire, and written answers covering backup and restore procedures, privileged production access, and incident-response handling.

---

## Access control

Authorization is enforced at the application level using [Pundit](https://github.com/varvet/pundit) policies.

| Role | Can view reports | Can manage organization settings | Can assign handlers |
|---|---|---|---|
| Admin | All reports | Yes | Yes |
| Handler | Reports they are assigned to or participating in | No | No |
| Viewer | Read-only, for auditors and legal counsel | No | No |

- Handlers cannot see reports they are neither assigned to nor participating in. Participants are explicitly added by an admin or the primary assignee (for example, looping in legal or HR).
- Reporters have no user account --- they access their report via a Case ID (`WB-XXXX-XXXX`) plus a 6-digit passcode they choose at submission.
- Every controller action checks authorization. Unauthorized access attempts are blocked and logged.

### Two-factor authentication

Handler and admin accounts can enable TOTP-based two-factor authentication via any standard authenticator app (Google Authenticator, 1Password, Authy, and compatible alternatives). Once enabled, sign-in requires both the primary credential and a rotating 6-digit code.

Reporters authenticate with two factors as well: the Case ID (something they hold) and the passcode they chose at submission (something they know). The passcode is stored only as a bcrypt digest and cannot be recovered. The follow-up inbox and message-posting are session-gated behind this check, so a leaked Case ID alone cannot read the report or impersonate the reporter.

### Session lifecycle

Each authenticated session records `last_seen_at` on every request (debounced). Users can review their active sessions, see when each was last active, revoke any session individually, or sign out of all other sessions at once from the account settings.

Sessions expire automatically after **14 days of inactivity**. The next request from an idle session destroys the server-side record, clears the cookie, and forces re-authentication via a fresh magic link. A nightly job sweeps abandoned sessions on the same timeout, so `user_agent` and `ip_address` are not retained beyond the idle window even when the user never returns.

Magic-link authentication limits the blast radius of long-lived sessions: a stolen session cookie does not yield a reusable credential, and re-authentication requires email access.

### Member access and offboarding

Organization access is enforced at the request boundary. When a member is deactivated:

- Access to the organization is rejected immediately, including on previously bookmarked URLs.
- Open report assignments are unassigned.
- Participantships are removed.
- The audit-log history attributable to the member is preserved.
- The deactivated member is notified.
- Reactivation does not automatically restore prior case access.

The organization owner, the last active admin, and the portal's designated compliance officer cannot be deactivated. All deactivation and reactivation events are written to the append-only audit log.

Removing a member deactivates the membership rather than deleting it, so the audit trail and report history stay resolvable. A membership is hard-deleted only when the member's own account or the organization itself is erased.

### Account erasure

A member who closes their own account has their email address replaced with a non-routable placeholder and their two-factor credentials, sessions, API tokens, and preferences permanently removed; the account can no longer authenticate or be contacted. If the account never acted inside an organization, the record is deleted outright. If it did, the member's name and profile picture are retained and their memberships deactivated, so the case notes, messages, and audit entries attributed to them stay attributable to a named person --- retention required by <a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32019L1937" target="_blank" rel="noopener noreferrer">Art. 18</a>
 of Directive 2019/1937 and permitted by Article 17(3)(b) GDPR.

A scrubbed account cannot authenticate by any route: magic link, SSO assertion, or API token.

### Rate limiting

Public portal endpoints are rate-limited to prevent abuse and enumeration attacks:

| Endpoint | Limit |
|---|---|
| Report submission | 5 per 10 minutes per anonymized IP |
| Case lookup (Case ID + passcode) | 10 per 3 minutes per anonymized IP |
| Message submission | 10 per 3 minutes per anonymized IP |

Rate limiting uses the one-way IP hash described above --- no actual IP is stored.

---

## Audit and compliance

### Append-only audit trail

Every action in EthicsPortal is logged with:

- **Timestamp** (UTC)
- **Actor** (which user or system process performed the action)
- **Action type** (report created, status changed, message sent, handler assigned, report viewed, report exported, report deleted, etc.)

Audit log entries are append-only. They cannot be edited or deleted by any user, including organization admins. The full audit trail is included in PDF case exports for regulatory review.

### Data retention

Organizations configure their own retention period: **12, 24, 36, 48, or 60 months** after a report is closed. When the retention period expires, the report and all associated data (messages, attachments, audit log entries) are automatically and permanently deleted by a background job.

This satisfies GDPR storage limitation requirements (Art. 5(1)(e)) and Directive 2019/1937 record-keeping obligations (Art. 17--18).

### CSRF protection

All form submissions are protected against cross-site request forgery using Rails' built-in CSRF tokens.

---

## Secure development lifecycle

EthicsPortal follows a documented development lifecycle for changes that touch the Service. The stages are stated here so a procurement reviewer can map them to ISO/IEC 27001:2022 controls A.8.25--A.8.29 (see the [control map](/iso-27001/) for the full mapping).

| Stage | Practice |
|---|---|
| Architecture and design | Features that introduce new personal-data flows, sub-processors, or authorization scopes are evaluated against the encryption, access-control, and audit-trail commitments documented on this page before implementation. |
| Change review | Every production change is checked against a written checklist (encryption coverage, authorization scope, audit-log emission, input validation, secret handling) before deploy. Enforcement is automated rather than discretionary: the full test suite and static analysis run on every change and block the deploy on failure. The suite runs on a hosted CI runner, or on a developer workstation with the completed run recorded as a commit status the pipeline verifies before it will skip the hosted run. Change-approval roles are described during procurement review. |
| Secure coding | The codebase uses framework-level defenses by default --- parameterized queries via ActiveRecord, strong parameters, output escaping in views, CSRF tokens, attribute-level encryption, Pundit authorization at the controller boundary. Deviations require a written justification. |
| Security testing in development | Static analysis ([Brakeman](https://brakemanscanner.org), [bundler-audit](https://github.com/rubysec/bundler-audit), `importmap audit`) runs on every change. Tests cover authorization paths, encryption-at-rest invariants, audit-log emission, and rate-limit enforcement. See [dependency and patch management](#dependency-and-patch-management) for the full toolchain. |
| Environment separation | Production and non-production environments are isolated. No production personal data is used outside production; staging and development use synthetic fixtures. |
| Vulnerability response | Reports acknowledged within 2 business days (see [responsible disclosure](#responsible-disclosure)). Targets: critical issues remediated within 7 days, high within 30, medium within 90. Confirmed issues affecting deployed customers are reported through the [incident register](/incidents/) when they meet the register's scope criteria. |

---

## Dependency and patch management

EthicsPortal does not deploy end-of-life software components. The application runs on actively supported releases of Rails, Ruby, PostgreSQL, and the underlying operating system; upstream security releases are applied on a rolling basis.

Dependencies are scanned continuously in continuous integration:

- **[Brakeman](https://brakemanscanner.org)** flags Rails-specific vulnerabilities on every change.
- **[bundler-audit](https://github.com/rubysec/bundler-audit)** checks the Gemfile against the Ruby Advisory Database on every change and again on a daily schedule, so newly disclosed advisories are caught even when no code has changed.
- **`importmap audit`** scans JavaScript imports for known vulnerabilities on every change and again on a daily schedule.
- **[Dependabot](https://docs.github.com/en/code-security/dependabot)** opens pull requests weekly for outdated Ruby gems and GitHub Actions, grouped by minor/patch updates.

Components reaching end-of-life upstream are replaced or upgraded before their support window closes.

---

## Infrastructure

| Component | Provider | Location |
|---|---|---|
| Application server and database | [Hetzner](https://www.hetzner.com) | Nuremberg, Germany (EU) |
| File storage | [Hetzner Object Storage](https://www.hetzner.com/storage/object-storage) | Nuremberg, Germany (EU) |
| Transactional email | [Mailjet](https://www.mailjet.com) | France (EU) |
| Payment processing | [Stripe](https://stripe.com) | EU |

- All primary data processing occurs within the European Union.
- No credit card numbers or payment credentials are stored on EthicsPortal servers. All payment data is handled by Stripe.
- Mailjet is used for transactional email (handler notifications, not whistleblower-facing). Mailjet is based in France and processes all data within the EU.
- The marketing site is served via Cloudflare (CDN, United States); the reporting and handler portals are not. See the [subprocessors](/subprocessors/) page for the full list and transfer safeguards.

---

## Backups and restore

EthicsPortal operates two complementary backup layers, both retained within the EU:

| Layer | What | Where | Retention |
|---|---|---|---|
| Database | Daily encrypted PostgreSQL dumps via a Kamal accessory | Hetzner Object Storage, Nuremberg (EU) | 21 days for current versions; deleted versions expire after 7 additional days |
| Server | Full disk snapshots of the application host | Hetzner Cloud, Nuremberg (EU) | 7 days |

**Recovery objectives.** Recovery point objective (RPO) is 24 hours. Recovery time objective (RTO) is 4 hours. These objectives also appear in the [service level agreement](/sla/#recovery-objectives).

**Restore testing.** A restore drill runs automatically every month via a CI workflow that restores the latest dump into a disposable environment, and on demand via `bin/backup-restore-test`. Backup freshness is monitored continuously (alert if the most recent dump is older than 36 hours).

**Encryption.** Database dumps are encrypted at rest by Hetzner Object Storage; application-layer fields encrypted under Rails ActiveRecord Encryption remain encrypted in the dump.

---

## Operational review

Some operational materials are shared during procurement review rather than published in full on the open web, because they contain infrastructure and response detail that is more appropriate for controlled disclosure.

Topics available on request during procurement include:

- Privileged production-access summary
- Incident-response workflow and escalation contacts
- Business continuity and customer offboarding/export responses

---

## Responsible disclosure

If you discover a security vulnerability in EthicsPortal, please report it to [security@ethicsportal.eu](mailto:security@ethicsportal.eu). We ask that you:

1. Do not publicly disclose the vulnerability before we have had a chance to address it.
2. Provide enough detail for us to reproduce and fix the issue.
3. Do not access or modify other customers' data.

We will acknowledge your report within 2 business days and aim to resolve confirmed vulnerabilities promptly.
