Skip to content
SECURITY

Security and data protection

How PenaltyProof is built, what it stores, and what it does not do.

Claims on this page have been verified against the application source code. Where a claim cannot be verified from code, that is stated explicitly. Last updated: 11 June 2026.

Authentication

Magic-link only — no passwords stored

PenaltyProof has no password-based login. You sign in by entering your email address; a signed link is emailed to you, and clicking it starts a session. The application stores no password, password hash, or credential of any kind. There is nothing to leak if the database is read.

Magic-link design

  • Links are signed with HMAC (itsdangerous URLSafeTimedSerializer) and expire after 72 hours.
  • Each link carries a single-use JTI (JWT ID). On first use, the JTI is inserted into a consumed_token table. A duplicate insert (replay attempt) is rejected immediately.
  • After a successful click, a 30-day rolling session cookie is issued.

Session cookie

  • HttpOnly: yes — not accessible from JavaScript.
  • Secure: yes in production (requires HTTPS); off in local development.
  • SameSite: Lax. Strict would drop the cookie when clicking the magic link from an email client (a cross-site navigation); Lax was chosen so the first post-click request carries the session.
  • Session version: each accountant row carries a session_version integer. Incrementing it server-side immediately invalidates all outstanding cookies.
  • Cookie max age: 30 days, rolling (re-issued on each authenticated request).

Request hardening

CSRF defence

All state-changing requests (POST, PUT, PATCH, DELETE) that are not signed webhooks or bearer-auth cron endpoints are required to carry an Origin or Referer header matching the application's allowed-host list. Requests without a same-origin header are rejected with HTTP 403. The session cookie's SameSite=Lax provides a complementary browser-level defence.

Security headers

  • Content-Security-Policy: per-request nonces on scripts; frame-ancestors 'none'; object-src 'none'; default-src 'self'.
  • Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-Policy: camera, microphone, and geolocation disabled

Rate limiting

Default limits: 200 requests/hour and 30 requests/minute per IP address, enforced by SlowAPI middleware. In production the counter store is Redis so limits are enforced across all application instances. The magic-link send endpoint carries its own tighter limit. Cron and webhook endpoints are exempt.

Trusted-host enforcement

Requests with a Host header not in the configured allow-list are rejected at the middleware layer before reaching any route handler.

What data we hold

PenaltyProof stores:

  • Your email address (the account identifier)
  • Company numbers you add to your monitor list
  • Per-company obligation settings (VAT stagger group, CT period end, pension staging date — entered by you)
  • Filing deadline data fetched from the public Companies House API
  • Alert send history (which emails were sent, when)
  • Subscription and billing status (plan name, Stripe customer ID, subscription ID)
  • Email address and company number submitted via the /check result capture form (pre-signup; stored as a lead capture record in the check_captures table)
  • Name and email submitted via the Practice tier waitlist form (stored in the practice_waitlist table)
  • Team workspace metadata for Practice workspace features: workspace memberships, roles, invited email addresses, invited roles, inviter ids, invitation token hashes, expiry timestamps, accepted timestamps, and revoked timestamps. An invitation token is stored as a hash while pending, then cleared when accepted, revoked, or superseded.

PenaltyProof does not store passwords, client financial data, accounting records, HMRC agent portal credentials, or any data from your clients directly. It reads only the public Companies House API.

Audit log

Significant account events (sign-in, account deletion, company add/remove) are written to an audit log. IP addresses and user agent strings in the log are one-way hashed using SHA-256 with a server-side pepper (AUDIT_LOG_PEPPER). The hash output is truncated to 32 hex characters. The pepper can be rotated to sever historical correlation. Audit log rows are deleted after 365 days via the nightly purge job.

Team lifecycle events (team_invited, team_invite_revoked, team_member_removed, and team_invite_accepted) are included in the same generic audit log with acting account id and optional request-context hashes. The request context uses hashed IP and hashed user-agent values when available. The current audit log does not store target member/invite ids, before/after values, or a product-visible object history, so full activity history is not yet available.

Soft-delete and hard purge

When you delete your account, the record is soft-deleted (a deleted_at timestamp is set; the row is immediately excluded from all application queries). After 90 days, a nightly scheduled job hard-purges the row and all its associated data (companies, alerts, snapshots) via database cascade. This 90-day window is described in the Privacy Policy.

Alert and email send log retention

Alert sentinel rows are deleted after two years. Email send log rows (which record that an email was dispatched) are deleted after 90 days. Both deletions happen in the nightly purge job.

Snapshot data

Companies House snapshot data (daily CH API responses cached per company) is deleted after 12 calendar months.

Payments

All card payments are handled entirely by Stripe's hosted checkout and payment links. Card numbers, CVV codes, and bank details never pass through PenaltyProof's servers. The application receives a Stripe customer ID, subscription ID, and plan status from Stripe webhooks — no payment card data is stored or transmitted through our infrastructure.

Stripe's compliance status (PCI DSS, SOC 2) applies to Stripe's payment infrastructure, not to PenaltyProof. PenaltyProof itself does not hold or process cardholder data.

Account deletion and data subject requests

You can request deletion of your account from your account settings. A signed confirmation link is emailed to you; clicking it soft-deletes your account immediately. Hard purge of all associated data occurs within 90 days of soft-delete, as above.

For data subject access requests (DSAR) under UK GDPR, contact privacy@penaltyproof.co.uk. We aim to respond within 30 days. We are registered with the Information Commissioner's Office: Reg. No. ZC142025.

Subprocessors

The following third-party services process data on behalf of PenaltyProof:

  • Railway — application hosting and managed PostgreSQL database. Railway operates infrastructure in the United States and other regions. The specific server region in use for this application is not independently verifiable from our configuration files; we do not make a data-residency claim for Railway.
  • Stripe — subscription billing, payment processing, and customer portal. Stripe processes payment card data under its own PCI DSS compliance.
  • Resend — transactional email delivery. Alert emails and magic links are sent via Resend's API. Email send status webhooks are received from Resend.
  • Cloudflare Turnstile — bot detection on the signup form. Turnstile challenges are rendered client-side; the token is verified server-side via Cloudflare's API. No personal data is submitted to Cloudflare beyond the Turnstile challenge token.
  • Plausible Analytics — privacy-focused web analytics. Plausible markets itself as cookieless and GDPR-compliant by design; we load their script and also post conversion events server-side from webhooks. We rely on Plausible's published privacy posture for analytics data handling — this is a third-party claim, not one we can independently verify.
  • Sentry — error monitoring. Sentry receives exception reports including request paths and error messages. PII scrubbing is applied before sending: request bodies, cookies, and user email addresses are stripped from Sentry events.
  • Companies House Public Data API — source of filing deadline data. This is a public API operated by Companies House; no personal data is submitted to it beyond company numbers.

What we do not have

The following are not in place and we do not claim otherwise:

  • No SOC 2 or ISO 27001 certification. PenaltyProof is a small product operated by a solo developer. We have not obtained third-party security certification.
  • No guaranteed response-time SLA. We monitor application health via Sentry and a public statuspage at status.penaltyproof.co.uk. We do not publish a commercial SLA for support response times.
  • No UK/EU data residency guarantee. Railway may host data in non-UK regions. We cannot verify the physical location of our database from our configuration.
  • No penetration testing certificate. We have not commissioned a third-party penetration test.
  • No HMRC API integration. PenaltyProof calculates HMRC deadlines from rules and client-supplied configuration (VAT stagger group, CT period end, etc.). It does not connect to HMRC systems or hold HMRC credentials.

Security contact

To report a security issue: security@penaltyproof.co.uk. We aim to acknowledge reports within two working days. A security.txt file is available at the standard location.

Procurement FAQ

Common questions from practices evaluating PenaltyProof before subscribing.

How long is my data retained?

All retention periods are verified against the nightly purge job in the application code:

  • Audit log — deleted after 365 days.
  • Alert sentinels (duplicate-prevention records) — deleted after 2 years.
  • Email send log — deleted after 90 days.
  • Companies House snapshot data — deleted after 12 months.
  • ECCTA readiness metadata — readiness status, target date, source URL, evidence-requested timestamp, last-reviewed timestamp, reviewer id, and optional internal notes are retained with the relevant account and monitored company while active, then removed through company deletion or account hard-purge. PenaltyProof does not store personal codes, identity documents, GOV.UK One Login state, ACSP evidence packs, filing credentials, authentication codes, or official Companies House verification status.
  • Team workspace metadata — workspace memberships are retained while the workspace/account relationship exists. Invitation rows are retained while the workspace/account remains active; invitation token hashes are cleared when an invite is accepted, revoked, or superseded. Team lifecycle audit rows follow the 365-day audit-log retention window.
  • Deleted account data — hard-purged (all associated companies, alerts, snapshots) within 90 days of soft-delete. The 90-day window is described in the Privacy Policy.
  • Pre-signup lead captures (check_captures and practice_waitlist) — no automated scheduled purge is currently in place. These records are retained until deleted manually or on receipt of a DSAR erasure request. Email privacy@penaltyproof.co.uk to request erasure.
How do I delete my account or request my data?

Account deletion: available from your account settings. A signed confirmation link is emailed to you; clicking it soft-deletes the account immediately. Hard purge of all data occurs within 90 days.

Data subject access request (DSAR): email privacy@penaltyproof.co.uk. We aim to respond within 30 days, as required by UK GDPR.

ICO registration: Reg. No. ZC142025.

Which third parties process my data?

The following subprocessors handle data on behalf of PenaltyProof:

  • Railway — application hosting and managed PostgreSQL database.
  • Stripe — subscription billing and payment processing (PCI DSS compliant).
  • Resend — transactional email delivery (alert emails, magic links).
  • Cloudflare Turnstile — bot detection on the signup form.
  • Plausible Analytics — privacy-focused web analytics.
  • Sentry — error monitoring (PII scrubbed before sending).
  • Companies House Public Data API — source of filing deadline data. No personal data is submitted beyond company registration numbers.

A data processing agreement is available at /dpa.

What happens if there is a security breach?

PenaltyProof is a solo-operated product. Application errors are monitored in real time via Sentry. Operational health is tracked on a public statuspage at status.penaltyproof.co.uk.

In the event of a personal data breach meeting the UK GDPR threshold, we are required by law to notify the ICO within 72 hours and notify affected users without undue delay.

Honest posture: we do not have a commercial SLA for breach response, no dedicated security team, no SOC 2 or ISO 27001 certification, and no penetration test certificate. These are the real constraints of a small product operated by a solo developer.

Where is my data stored?

PenaltyProof is hosted on Railway, which operates infrastructure in the United States and other regions. The specific server region in use for this application is not independently verifiable from our configuration files.

We do not make a UK or EU data-residency claim for Railway. If UK or EU data residency is a hard requirement for your practice, PenaltyProof may not be the right choice at this time.

What support is available and how quickly do you respond?

Support is provided by email at hello@penaltyproof.co.uk.

PenaltyProof is a solo-operated product. We do not offer a staffed help desk, live chat, or phone support. We do not publish a response-time SLA for general enquiries.

For security issue reports, we aim to acknowledge within two working days. Operational health is visible at status.penaltyproof.co.uk.

Privacy Policy → Data Processing Agreement → Pricing → ← Back to penaltyproof.co.uk