Work · 05

Hardening a multi-tenant platform: isolation, uploads, SSO, API keys

When large customers asked how secure the platform is, the answer was backed by code, not by promises: isolation between customers, hardened sign-in, access keys with a lifecycle.

Security

SSO + 2FAhardened across the platform
Context
Enterprise and public-sector customers with security questionnaires, SSO and 2FA requirements, and an attack surface that grew with every integration.
What we did
Tenant isolation, upload validation, SSO and 2FA hardening, API key lifecycle, encryption at rest, internal tooling with minimal dependencies.
Result
Questionnaire answers backed by code, not by promises. Not an audit under Romanian OUG 155/2024; the engineering behind one.
Stack
Symfony Security, SAML, OAuth, MySQL

Start with your own audit

The work started with a review of the whole platform, run by the people who know it best. Findings were grouped by severity and fixed in small, reviewable changes, not in one large security release. Every change could be switched off on its own, so a fix that misbehaved in production could be withdrawn without withdrawing the rest.

A security fix must not break a customer

One rule governed the work. A fix that changes what a paying customer's integration can do is a product decision, not a security fix. Where a fix would have changed the contract customers build against, we kept the contract and narrowed what sits behind it. Hardening that breaks integrations gets rolled back, and then nothing is more secure.

Guards that fail by default

The durable result is not a list of patches. It is a set of guards that refuse by default and allow only what has been explicitly permitted. Something new starts closed and has to be opened on purpose. When a step is forgotten, the result is an error during testing, not an exposure in production.

Tenant isolation that cannot be forgotten

On a multi-tenant platform, the question behind every request is whose data this is. The answer is decided on the server side, never taken from what the client sends. Data access is written so that the customer it belongs to has to be supplied, and permission checks look at the actual record and who owns it, not only at the role of the person asking. Customers who need it run in a dedicated environment of their own.

Sign-in, sessions and keys

Single sign-on and two-factor authentication were hardened against reuse: a proof of identity that has been used once is not accepted again. Sessions end when they should. Keys used by integrations are kept apart from the sessions of people and carry narrower powers, so an integration key is not a way into an account.

Uploads and outbound calls

Every uploaded file goes through the same checks, in the same order, before anything in the platform tries to read it, and the platform decides what a file is by looking at the file, not at its name. Rejections stay neutral, so an error does not explain how to get past it. Calls the platform makes to customer systems are signed, so the receiver can verify where they came from.

Sensitive data and secrets

Sensitive fields are encrypted in the database itself, field by field, and secrets that belong to a customer are encrypted per customer. Encryption keys are kept apart from the data they protect, in a dedicated vault, and never in the repository.

Checked by tooling, not by memory

Several of these rules are verified automatically. Tests guard the permission model and the list of public pages, so neither can change by accident. Static analysis bans the patterns that cause trouble, dependencies are scanned for known vulnerabilities, and an automated reviewer flags data access that is not tied to a customer.

Two things we would tell any team

  • Never load a record by an id the client sent and then trust that record for the rest of the request. Tie every request to the account of the person asking.
  • Never ship a safety switch that fails closed without shipping its setting with it. The first deploy turns the feature off for everyone.

Contact

Let's talk

Tell us what you have, in your own words. You do not need to know which technology the system runs on; we will work that out. You get back questions and a written proposal.

Write to usor directly at [email protected]