Security
Last updated 4 October 2026
Peoplume holds salaries, bank details and identity documents, so this page describes how it is built rather than reassuring you that it is secure. The last section lists what it does not do, because that is usually the part you are trying to find out.
One company cannot see another
Every record in the database carries the company it belongs to, and every query filters on it. There is no shared pool of employees that a forgotten filter could expose, and company scope alone is not treated as sufficient — reads are also checked against the relationship that entitles you to them: your own record, your own direct report, your own document.
Identifiers arriving in a request are never trusted as targets. A department, branch, manager or role referenced by a form is looked up within your company before anything is written, so an identifier belonging to another customer simply does not resolve.
Passwords and sessions
- Passwords are hashed with bcrypt. They are never stored or logged in readable form.
- The session cookie is HTTP-only, same-site, and marked secure in production. It carries only a user identifier — role, company, permissions and branch scope are re-derived from the database on every single request, so a role change takes effect immediately and no privilege can be smuggled inside a token.
- Changing or resetting a password signs out every other device, as does an explicit “sign out everywhere”. Tokens issued before that moment stop being accepted.
- Password resets, invitations, interview invitations and API keys are stored only as SHA-256 hashes. Reading the database yields no working link or key.
- Sign-in, sign-up and every public form are rate-limited per account, per address and globally.
- A new company exists only after its administrator confirms their email address: the sign-up form stores a pending request, and the company, the account and the trial are created when the emailed link is confirmed — by a button, not on arrival, so a mail scanner opening the link creates nothing.
Two-step verification
Any user can turn on TOTP two-step verification from their own settings, using any standard authenticator app. Enrolment only takes effect once a code has been accepted, so an abandoned set-up leaves the account exactly as it was, and turning it off again requires a current code — a stolen session cannot quietly remove it.
Ten single-use recovery codes are issued once and stored hashed. If someone loses both their authenticator and their codes, an administrator can clear two-step verification for them; that is recorded in the audit log, and nobody can clear their own.
An administrator can require it for everyone with console access — people who see salaries and run payroll. Portal accounts are excluded on purpose: they include staff who clock in at a shared tablet precisely because they have no smartphone, and a rule they cannot satisfy is a lockout rather than a control. Enabling the requirement is refused unless the administrator has already enrolled themselves.
Single sign-on
A company can connect its own identity provider over OpenID Connect — Microsoft Entra ID, Google Workspace, Okta and others. The ID token is validated against the provider’s published keys, and the flow uses PKCE with a state and nonce bound to the browser that started it.
Two limits worth knowing before you plan a rollout. Signing in this way requires an existing Peoplume account: we do not create users from an assertion, because that would let a misconfigured group mapping become a stranger with a login. And an address outside the email domain the company registered is refused however well-signed it is.
Who can see what
Permissions are individual rather than a handful of fixed roles: viewing salaries is separate from editing them, and both are separate from running payroll. A role can be scoped to one branch, in which case its holder sees only that branch’s employees — enforced in the queries themselves, not by hiding menu items.
Roles are contained: you can only grant permissions you hold yourself, so an administrator cannot mint an account more powerful than their own. Salary changes, approvals, deletions and permission changes are written to an append-only audit log naming who did them.
Money moves need two people
Nobody approves their own leave, expense claim, timesheet or pay proposal — enforced on the server in both directions, not by removing a button. Expense claims above a threshold you set need a second approver who is not the first. An offer needs a signature from someone other than the person who drafted it. Approved payroll runs are frozen snapshots, so editing a salary changes next month’s pay and not last month’s.
Files, integrations and outbound requests
- Uploads are limited by type and size, path-traversal guarded, and served only through routes that re-check who is asking — portal users can reach their own documents and nobody else’s.
- Webhook URLs must be HTTPS and are refused if they resolve to loopback, private, link-local or internal addresses; redirects are not followed, and the address is re-checked at send time rather than only when saved. This is to stop a webhook being used to reach inside our own network.
- API keys carry explicit scopes checked on the server, are rate limited, and revoke immediately.
- Push notifications, where a person turns them on for a device, carry only a title, one line of text and an in-app link — never a figure — and are encrypted to that device, so the browser vendor’s push service relays bytes it cannot read. A device’s subscription is a credential: it is stored for its own account only, never shown or exported, and deleted the moment the push service reports it revoked.
What the browser is told
Every response carries a Content-Security-Policy, HTTP Strict Transport Security for a year, nosniff, X-Frame-Options: DENY, a referrer policy and a permissions policy. The permissions policy switches off camera, microphone, payment and the motion sensors outright, and allows location only for our own pages — the portal’s check-in button asks for it, and nothing else does.
One honest limitation in that policy. Scripts are restricted to our own origin, so an injected <script src="…"> pointing somewhere else will not load, and the directives that usually turn an injection into stolen data — where forms may post, what may be embedded, what a base tag may rewrite — are locked down. But inline script is still permitted, because the framework puts its own inline script in every page. Removing that needs a per-request token and a change to how pages are rendered, which is a deliberate piece of work rather than a setting, and we have not done it yet.
Backups
Backups are taken with an integrity check, verified by checksum, and copied off-site to object storage. Restores are scripted and default to a dry run, so recovering does not begin by overwriting anything.
They are encrypted at rest by the storage provider, not by us before they leave. The difference matters: it means Cloudflare could technically read a backup, where client-side encryption would mean it could not. We would rather state which of the two this is than let the stronger one be assumed.
What Peoplume does not do yet
Stated plainly, because you would find out eventually and it is better that you find out now:
- No SAML. Single sign-on is supported over OpenID Connect, which every major identity provider speaks — but if your procurement requires SAML specifically, we do not have it. That is a deliberate choice rather than a gap we have not reached: SAML security rests on XML signature verification, whose failure mode is accepting an assertion it should have rejected, and we would rather say no than implement it badly.
- No independent security certification. Peoplume has not been through a SOC 2 or ISO 27001 audit and has not had an external penetration test. If your procurement process requires either, we do not have it.
- Almost no automatic deletion. Rejected job applicants can be purged on a schedule the employer sets, and a former employee’s personal data can be erased on request; everything else stays until someone deletes it. See Privacy.
- The public API is read-only. Write access would need the permission and branch-scoping model re-expressed for callers without a session, which is its own piece of work rather than a flag we have chosen not to set.
Reporting a vulnerability
If you believe you have found a security problem, write to [email protected] before telling anyone else, and give us a reasonable window to fix it. We would rather hear about it awkwardly than read about it later.
