Professional
Corporate platform | Client onboarding and collections
Public site, internal portal, client due diligence and accounts receivable. The same image is deployed as two services with different permissions.
- Security
- Full Stack
- Organisation
- Intercargo Panamá — via Kaizen Apps CR
- Role
- Development and security model
- Period
- 2026-07 — Present
Stack
- Next.js
- React
- TypeScript
- MySQL
- Google Cloud Run
- Docker
- Vitest
- Playwright
- Gemini
Context
The platform brings together the public site, the internal portal, client due diligence and accounts receivable. I joined to build on it and to define its security model.
Problem
The site ran on a platform the team did not administer. The client was asking for a user portal holding critical information, and that piece needed guarantees only owned infrastructure can hold.
The proposal to rebuild came from the team. Moving to configurable infrastructure made it possible to support the portal with the guarantees it required and redesign the public site at the same time. It also opened the way to automating due diligence, which until then meant reading documents by hand.
Technical decisions
A marketing site and a portal holding client documents have opposite exposures. Serving them from the same process would turn any failure on the first into access to the second.
The boundary is a deployment boundary, not a code boundary. A single image is published as two services with separate identities, and the public service has its database access removed.
The second rule is that server code is never imported from the browser, not even for types. When the client imported a constant from a server module, the dependency graph pulled in the model SDK: 269 KB of dead JavaScript on every visit. No credential was exposed, because the framework only injects variables with the public prefix, but the weight was real. The rule is now checked by a command inside the pipeline.
Architecture
The two services share an image and a binary. What they do not share is the identity they run under, and everything else follows from that.
The same image is deployed twice under different identities. The public service has its database access removed, so a failure on the open site has no route to what the portal holds.
There is no open sign-up and no passwords of our own. Entry is by invitation through a single identity provider. Sessions are revoked from the server, not merely expired.
File size, requests per origin and type validation are applied before anything reaches the model. The server sets the limit; the model does not decide how much it may ask for.
The pipeline runs types and tests, builds the image and scans it. A critical vulnerability aborts the deployment before a new service exists.
A document uploaded by a client passes the size, type and rate limits before it reaches the model. What the model returns is validated against the schema before it is written, so a malformed extraction never reaches the database.
Result
The platform serves the public site, the internal portal, due diligence and accounts receivable from two services with different permissions.
The suite holds 2,243 unit tests across 144 files and 27 end-to-end suites. Four of those are dedicated attack suites, one per module: authentication, accounts receivable, due diligence and the public site.
What I learned
Separation by deployment costs more than a check inside the code: two services to configure, two identities to maintain, and a deployment that can end up split between versions.
In return, the guarantee does not depend on the application behaving as expected: if the public site ever ran arbitrary code, it would still have no database credentials to use.