Please report vulnerabilities privately via GitHub Security Advisories rather than opening a public issue. You should get a response within a week.
This service processes payslips β personal data under GDPR. It is designed to run self-hosted (locally or on your own server); no data is sent to third parties beyond the SMTP provider you configure, and β only if you opt in to AI matching β recipient names, email addresses and payslip filenames sent to the Anthropic API (never payslip contents or NI numbers).
What the code guarantees (see src/prepare-payslips.js, server.js):
- NI numbers are never stored, logged, or returned by any API β they are
used only at the moment of PDF encryption. They are passed to
qpdfvia a private (mode 600) argfile that is deleted straight away, so they never appear in process arguments, andqpdferrors are not propagated. - Raw (unprotected) PDFs are deleted from disk as soon as 256-bit AES protection completes; partial outputs are removed on failure.
- Protected payslips are deleted once sent (
PAYSLIP_DELETE_AFTER_SEND, default on), and whole payslip runs are purged afterPAYSLIP_RETENTION_DAYS(default 7) unless recipients are still pending. - Attachments are deleted after a campaign is cleaned up.
- Recipient email addresses are not written to the send log.
- SMTP host allowlist (
SMTP_HOST_ALLOWLIST): the UI cannot repoint the service at an arbitrary server to capture the stored app password, and changing the host requires re-entering the password. SMTP_PASSand the Anthropic key are stored only in the local SQLite settings (or read from the environment); the API never echoes them back β the UI only ever sees a "key is set" boolean.
β οΈ Always setAPP_PASSWORDon any internet-facing deployment β without it the UI and API are completely unauthenticated.- Serve behind HTTPS (reverse proxy) if exposed beyond localhost, with
TRUST_PROXY=1so the login throttle sees real client IPs. - Restrict network access to known IPs (the Terraform deploy allows only
office_cidrs). - Treat the
data/directory (database + uploads) as confidential and back it up accordingly β or purge it after each payroll run.
Only the latest commit on main is supported.