Skip to main content

Security

At a glance

LayerWhat we do
HostingAWS Sydney (ap-southeast-2). Data never leaves AU.
Encryption in transitHTTPS everywhere, valid certificates, HSTS enabled.
Encryption at restAWS-managed keys for database + file storage.
AuthenticationEmail + password (BCrypt-hashed) or Google SSO.
SessionsHTTP-only secure cookies, 14-day TTL, signed.
AuthorisationRole-based (Admin / PM / Read-only) per agency.
Multi-tenant isolationDatabase-level row-level security — agencies cannot see each other's rows even if they tried.
BackupsDaily automated, 14-day point-in-time recovery, manual snapshots before risky ops.
Audit logEvery action that touches a tenant / owner / property is logged with who + what + when.
Magic linksSingle-use, 14-day expiry, hashed at rest.

Login + session security

When you log in:

  • Your password is hashed with BCrypt before being stored. We literally cannot see your password.
  • A signed session token (JWT) is issued and stored in an HTTP-only, Secure, SameSite=Lax cookie. The token can't be read by JavaScript and can't be sent on cross-origin requests.
  • Sessions last 14 days. After that, you log in again.

Google SSO works the same way under the hood — we trust Google's identity assertion and issue our own session token.

What attackers can't do

A few specific concerns we've designed for:

  • Reading another agency's data. Database-level row-level security means even if you somehow got our database credentials, you'd still have to identify as a specific agency to read any rows. Bypassing that requires Postgres superuser access, which neither the application nor support staff have.
  • Reading a tenant's data without authorisation. Tenants identify via single-use submission tokens. Tokens are scoped to one property and expire after 14 days.
  • Hijacking a contractor's magic link. Magic-link tokens are 32 random bytes (256-bit entropy). They're hashed before storage so even our own database doesn't know the plaintext token. Brute force is computationally infeasible.
  • Replay attacks on session cookies. Session JWT signatures use a rotating signing key (per-deployment). Old cookies stop working after a deploy.

What we monitor

  • Failed login attempts — too many in a row triggers a backoff.
  • Unusual access patterns — e.g. an admin downloading every tenant's data in one session — flagged for review.
  • Magic-link reuse — magic links can only be opened from the same general region they were originated. Sudden cross-continent use triggers an alert.

What we don't do

  • Two-factor authentication (2FA) — not yet implemented. On the roadmap. Workaround: use Google SSO with 2FA enabled on the Google account.
  • IP allowlisting — not yet implemented. On the roadmap for enterprise customers.
  • Single sign-on for big agencies (SAML / OIDC) — not yet implemented. Available as a custom integration for agencies with 100+ team members.

These are real gaps. We're not pretending they're features. They're on the roadmap and will land as we hire dedicated security engineering.

How we treat security incidents

If we discover a vulnerability:

  1. We patch it. Most patches deploy within hours.
  2. We work out which agencies were affected, if any.
  3. We tell you. Even if you weren't affected. Even if it's embarrassing.
  4. We publish a postmortem on the docs site with timeline + what we changed.

We don't bury security issues. We've never had a serious one. Aiming to keep it that way.

How to report a security issue

If you think you've found a vulnerability, email support@pmfriend.com with subject "SECURITY:". We'll respond within 24 hours.

We don't have a formal bug bounty program yet — we're a small team. We will, however, add a sincere "thanks" line to our public security page if you find something material.

Going deeper